Я внедрил brute-forcer для md5 как в C++, так и в Java, и у меня возникли вопросы о том, почему их эффективность отличалась так же, как и она.
Данные и график сложности (E ^ длина пароля) и время здесь: http://i.imgur.com/hckCe8f.png
Пароли были простыми буквами "b", чтобы заполнить длину
В C++ я использовал эту реализацию md5: zedwood.com/article/cpp-md5-функция
А на Java я использовал вторую реализацию на этом сайте: http://www.asjava.com/core-java/java-md5-example/
В рекурсивной реализации C++ мой код для цикла был выполнен в отдельном классе:
class bruteChar {
string charset;
char last_char;
string str;
string double_start;
char reverse_charset[256];
private:
string next(string s)
{
size_t length = s.size()-1;
if(length == 0)
{
if( s[0]==last_char)
return double_start;
return string(1, charset[reverse_charset[s[length]]+1]);
}
if(s[length] == last_char)
return next(s.substr(0,length))+charset[0];
else
return str.substr(0,length)+string(1, charset[reverse_charset[s[length]]+1]);
};
public:
void start (string chars)
{
charset = chars;
str=charset[0];
last_char=charset[charset.size()-1];
double_start=charset[0];
double_start+=charset[0];
for(size_t i = 0; i < charset.size(); ++i)
reverse_charset[charset[i]]=i;
reverse_charset[charset[charset.size()]]=0;
}
string next()
{
str=next(str);
return str;
}
};
В Java я использовал пользовательский класс
public class picochar {
public static char[] charset = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
int num;
int mod;
picochar(int init, int mod)
{
num = init%mod;
}
picochar(char init, int mod)
{
for(int i = 0; i < mod; i++)
{
if(charset[i] == init)
num = i;
}
}
public char get()
{
return charset[num];
}
public boolean equals(char ch)
{
return (get() == ch);
}
public void increment()
{
num++;
}
}
и следующий метод
public static String next(String s) {
int length = s.length();
picochar pc = new picochar(s.charAt(length - 1),mod);
if(pc.equals(picochar.charset[mod-1]))
return length > 1 ? next(s.substring(0, length - 1)) + 'a' : "aa";
pc.increment();
return s.substring(0, length - 1) + pc.get();
}
Почему Java намного эффективнее при вычислении хэшей, чем C++?
Я просто использовал эффективную реализацию MD5 для Java и плохой для C++?
Я предположил, что C++ будет намного быстрее, чем Java, поскольку Java должна запускать все через JVM, а C - изначально.
Однако Java намного превосходит решение C++. Если это было просто из-за плохого кодирования в моей программе C++, как бы я это исправить?
Отредактированный для удаления разнородной C++ программы, теперь оба решения цикла рекурсивно.
Я сделал некоторое время на то, сколько времени прошло, чтобы пройти без хэширования, и здесь Java в два раза быстрее, чем C, что было объяснено @Dunes. При перекодировке, чтобы не рекурсивно использовать substr() и вместо этого мутировать исходную строку, C был примерно в два раза быстрее, чем Java.
Я сделал несколько тестов, сколько времени занимает хэш "привет" 1 << 25 раз, и нашел что-то странное - Java, казалось, "разогревался" намного быстрее, и хотя медленнее сначала было бы быстро догнать реализацию C,
C++ будет иметь аналогичный прирост производительности после того, как хэширует в течение нескольких секунд, но выигрыш был нигде рядом с Java.
Так почему же разминка Java лучше?
Оказывается, я использовал неэффективную реализацию md5.
Здесь время, затраченное на вычисление 2 27 хешей по сравнению с временем, которое потребовалось openssl
openssl v1
17.4911
openssl v2
14.9546
custom
291.201
Похоже, что реализация c++ настолько медленная, потому что вы передаете строки по значению. Это каждый раз, когда вы вызываете метод со строковым аргументом или возвращаете строку, программа должна создать совершенно новую копию всей строки.
Принимая во внимание, что, поскольку Java имеет неизменяемые строки, он может уйти с прохождением разных видов одной и той же строки. String.substring
не копирует массив String.substring
символов. Вместо этого новый строковый объект просто отслеживает начальный индекс и длину по отношению к массиву опорных символов. Когда подстрока используется неразумно, это может привести к утечкам памяти - представление одного символа сохранит исходный миллионный массив поддержки массива до тех пор, пока он существует.