Какой самый быстрый метод, чтобы сравнить два u_int64[8]
в C/C++?
Массив 1 находится внутри элемента std::vector
(~ 10k), массив 2 находится внутри динамически распределенной структуры. (is memcmp()
здесь ложноположительно бесплатно?)
Моя (псевдо C) реализация:
typedef struct {
u_int64_t array[8];
}work_t;
/* alloc and fill array work_t* work = new (std::nothrow) work_t etc... */
for(u_int32_t i=0; i < some_std_vector.size(); i++) {
if((some_std_vector[i]->array[0] == work->array[0]) &&
(some_std_vector[i]->array[1] == work->array[1]) &&
(some_std_vector[i]->array[2] == work->array[2]) &&
(some_std_vector[i]->array[3] == work->array[3]) &&
(some_std_vector[i]->array[4] == work->array[4]) &&
(some_std_vector[i]->array[5] == work->array[5]) &&
(some_std_vector[i]->array[6] == work->array[6]) &&
(some_std_vector[i]->array[7] == work->array[7])) {
//...do some stuff...
}
}
Целевой платформой является Linux x86_64 gcc 4.9.2, цикл находится внутри pthread
, используется tcmalloc
, а код скомпилирован с помощью -O2
Вот несколько советов по улучшению скорости.
Вместо использования указателей, например, → operator, используйте локальные переменные или передайте переменные в качестве ссылок. Компилятор может генерировать дополнительный код для загрузки указателя в регистр, а затем разыгрывать регистр для получения значения.
Использование кэша данных процессора Большинство современных процессоров имеют кэш данных. Если вы можете загрузить несколько переменных с данными, а затем сравнить, вы можете вызвать кеш данных процессора.
Кроме того, создайте свои данные, чтобы эффективно входить в линию кэша данных. Это означает, что члены данных (включая массивы) должны быть рядом друг с другом или очень близко.
На самом низком уровне вы сравниваете много последовательных байтов. Как уже упоминалось, вы можете получить более высокую производительность, используя функцию сравнения памяти.
Еще одно предложение - помочь компилятору загрузить значения в отдельные переменные, сравнивая значения:
for (/*...*/)
{
//...
uint64_t a1 = some_std_vector[i]->array[0];
uint64_t a2 = some_std_vector[i]->array[1];
uint64_t a3 = some_std_vector[i]->array[2];
uint64_t a4 = some_std_vector[i]->array[3];
uint64_t b1 = work->array[0];
uint64_t b2 = work->array[1];
uint64_t b3 = work->array[2];
uint64_t b4 = work->array[3];
if ((a1 == b1) && (a2 == b2) && (a3 == b3) && (a4 == b4))
{
//...
}
}
Концепция здесь состоит в том, чтобы сначала загрузить переменные в несколько регистров, а затем сравнить регистры.
Со всеми методами, представленными в ответах, лучшим методом является кодирование одного, просмотр языка ассемблера и профиля. Не забудьте установить уровни оптимизации для высокой скорости.
Если у вашего процесса есть специальные инструкции, которые могут сделать это быстрее, вы хотите проверить, использует ли их компилятор, или есть основания для их не использования.
Я бы предположил, что единственным способом действительно ответить на этот вопрос было бы написать две процедуры: одну с помощью цикла, который вы предоставили, а другой - с помощью memcmp. Затем проанализируйте и посмотрите на сборку, чтобы увидеть, какая из них выглядит наиболее эффективной. (Вы также можете быть навязчивыми и использовать профилировщик.)
Можно также написать настраиваемую подпрограмму в сборке, чтобы сравнивать их напрямую (т.е. Пользовательскую версию memcmp, которая работает специально для сравнения именно того, что вы смотрите) и сравнить ее вместе с двумя другими.
В любом случае, я согласен с остальными, что все, вероятно, будет довольно близко (с современным компилятором); однако, если вы действительно хотели сохранить его, вам придется протестировать его с помощью профилировщика и/или иметь навыки, чтобы посмотреть созданную сборку и узнать, какой из них будет быстрее.
Я сделал несколько тестов и посмотрел на gcc memcmp, glibc memcmp и мой код выше. glibc-2.20 memcmp - это метод fastes, потому что он использует оптимизацию платформы (в моем случае).
gcc memcmp намного медленнее. (bug43052, скомпилировать с -fno-builtin-memcmp)
В зависимости от используемого вами устройства и используемого компилятора вы можете попробовать некоторые "конкретные" проблемы. Например, в некоторых компиляторах есть методы, которые позволяют выполнять большую нагрузку из памяти и, в результате, самые быстрые множественные сравнения. Также есть способы вручную развернуть цикл, чтобы они выполнялись быстрее. Но это зависит от компилятора. Вы всегда можете попробовать несколько способов и проверить код ассемблера, чтобы узнать, какой путь самый быстрый.
memcmp() == 0
всегда в порядке, так как это необработанное сравнение памяти. Это не только хорошо, но и хороший совет для оптимизации компилятора (SSE, конвейерная обработка). Но действительно хороший компилятор будет интерпретировать ваш код так, как если бы он былmemcmp()
любом случае.