TL; DR: Почему данные умножения/литья в size_t
медленны и почему это зависит от платформы?Производительность от size_t до double
У меня возникают проблемы с производительностью, которые я не совсем понимаю. Контекст - это захват кадра камеры, где изображение 128x128 uint16_t считывается и обрабатывается с частотой 100 Гц.
В постобработке я генерирую гистограмму frame->histo
, которая составляет uint32_t
, и имеет thismaxval
= 2^16 элементов, в основном я подсчитываю все значения интенсивности. Используя эту гистограмму рассчитать сумму и сумму квадратов:
double sum=0, sumsquared=0;
size_t thismaxval = 1 << 16;
for(size_t i = 0; i < thismaxval; i++) {
sum += (double)i * frame->histo[i];
sumsquared += (double)(i * i) * frame->histo[i];
}
Профилирование кода с профилем я получил следующее (образцы, процент, код):
58228 32.1263 : sum += (double)i * frame->histo[i];
116760 64.4204 : sumsquared += (double)(i * i) * frame->histo[i];
или первая строка занимает 32 % от процессорного времени, вторая - 64%.
Я сделал некоторый бенчмаркинг, и это похоже на тип данных/литье, что проблематично. Когда я меняю код на
uint_fast64_t isum=0, isumsquared=0;
for(uint_fast32_t i = 0; i < thismaxval; i++) {
isum += i * frame->histo[i];
isumsquared += (i * i) * frame->histo[i];
}
он работает ~ 10 раз быстрее. Однако эта производительность также зависит от платформы. На рабочей станции процессор Core i7 950 @ 3.07GHz код в 10 раз быстрее. На моем Macbook8,1, который имеет Intel Core i7 Sandy Bridge 2.7 ГГц (2620M), код только в 2 раза быстрее.
Теперь я задаюсь:
- Почему исходный код так медленно и легко разогнал?
- Почему это зависит от платформы?
Update:
Я составил приведенный выше код с
g++ -O3 -Wall cast_test.cc -o cast_test
UPDATE2:
Я побежал оптимизированные коды через профайлер (Instruments на Mac, как Shark) и нашел две вещи:
1) В некоторых случаях сам цикл занимает значительное количество времени. thismaxval
имеет тип size_t
.
for(size_t i = 0; i < thismaxval; i++)
занимает 17% от общего времени выполнения моегоfor(uint_fast32_t i = 0; i < thismaxval; i++)
занимает 3,5%for(int i = 0; i < thismaxval; i++)
не появляется в профилировщике, я предполагаю, что это меньше, чем 0.1%
2) Типы данных и литья независимо от того, следующим образом:
sumsquared += (double)(i * i) * histo[i];
15% (сsize_t i
)sumsquared += (double)(i * i) * histo[i];
36% (сuint_fast32_t i
)isumsquared += (i * i) * histo[i];
13% (сuint_fast32_t i
,uint_fast64_t isumsquared
)isumsquared += (i * i) * histo[i];
11% (сint i
,uint_fast64_t isumsquared
)
Удивительно, но int
быстрее, чем uint_fast32_t
?
Update4:
я провел еще несколько тестов с различными типами данных и различных компиляторов, на одной машине. Результаты приведены ниже.
Для testd 0 - 2 соответствующий код
for(loop_t i = 0; i < thismaxval; i++)
sumsquared += (double)(i * i) * histo[i];
с sumsquared
двойным, и loop_t
size_t
, uint_fast32_t
и int
для тестов 0, 1 и 2.
Для испытаний 3-- 5 код
for(loop_t i = 0; i < thismaxval; i++)
isumsquared += (i * i) * histo[i];
с isumsquared
типа uint_fast64_t
и loop_t
снова size_t
, uint_fast32_t
и int
для испытаний 3, 4 и 5.
Составители я Используемые GCC 4.2.1, 4.4.7 GCC, GCC 4.6.3 и GCC 4.7.0. Тайминги находятся в процентах от общего времени процессора, поэтому они показывают относительную производительность, а не абсолютную (хотя время выполнения было довольно постоянным в 21 секунду). Время cpu для обеих двух строк, потому что я не совсем уверен, правильно ли профайлер разделяет две строки кода.
gcc: 4.2.1 4.4.7 4.6.3 4.7.0 ---------------------------------- test 0: 21.85 25.15 22.05 21.85 test 1: 21.9 25.05 22 22 test 2: 26.35 25.1 21.95 19.2 test 3: 7.15 8.35 18.55 19.95 test 4: 11.1 8.45 7.35 7.1 test 5: 7.1 7.8 6.9 7.05
или:
Исходя из этого, кажется, что литье дорого, независимо от того, какого типа целое я использую.
Кроме того, кажется, что gcc 4.6 и 4.7 не могут правильно оптимизировать петлю 3 (size_t и uint_fast64_t).
Вы также можете попробовать его с помощью 'uint_fast32_t'? Дикая догадка заключается в том, что она быстрее из-за того, что второй тип данных имеет ту же длину бит, что и машинные команды (64-разрядные). Угадаю, что у вас есть 64-битная машина. Я бы ожидал, что fast32 также будет медленнее. [edit] можете ли вы также проверить размер как 'uint_fast32_t', так и' uint_fast64_t'? Я предполагаю, что 32 на самом деле 64 бит. – Yuri
Вы имеете в виду 'uint_fast32_t isum'? Я мог бы попробовать, хотя я думаю, что это может переполняться, поэтому я использовал uint_fast64_t. – Tim
Ну, для 1 .: Причина почему-то диктует, что литье ints должно плавать, а операции float должны быть медленнее, чем делать операции int непосредственно (хотя int-to-float не должно быть таким же злым, как float-to-int), еще больше поэтому с не оптимальным стеком x87. Вы скомпилируете его с поддержкой SSE? –