Опять про movntps
Читал комментарии к прошлому посту про movntps, много думал. Вспоминал, что на i7-920 выигрыш от movntps был и значительный.
Пришлось потратить час-полтора на очередной набор микробенчмарок. Картинка получилась куда сложнее, чем я сначала подумал.
Код
Может быть три легко локализуемых случая:
- fill - данные как-то производятся из ничего и льются в память;
- copy - из одного места читаем, в другое пишем;
- stream - читаем данные, обрабатываем, пишем на старое место.
Соответственно, код для fill:
#define STORE(a,b) _mm_stream_ps(a,b)
#else
#define STORE(a,b) _mm_store_ps(a,b)
#endif
void fill( float *data, int sz)
{
int i;
float init[4] = {1,2,3,4};
__m128 d = _mm_loadu_ps(init);
for(i=0;i<sz;i++)
STORE(&data[i*4],d);
}
for(i=0;i<sz;i++)
STORE(&to[i*4],_mm_load_ps(&from[i*4]));
void stream(float *data, int sz) {
float init[4] = {1,2,3,4};
__m128 c = _mm_loadu_ps(init);
for(i=0;i<sz;i++){
__m128 d = _mm_load_ps(&data[i*4]);
d = _mm_add_ps(d,c);
STORE(&data[i*4],d);
}
Результаты
Вышеупомянутые сниппеты гонялись на 0.8-3.2Gb данных (в зависимости от машины) на четырех CPU: Core i7-AVX (@4.5Ghz), Core i7-920 (@3Ghz), Core2 Q9300 (@2.5) и Core2 T7500 (@2.2). Выборочная проверка показала, что для таких коротких сниппетов каких-то компиляторных взбрыков (как было у интела на более сложном коде) - нету.
Результаты сведены в таблицу. Там присутствуют и результаты для stream2/stream3 про которые подробно рассказано ниже.
Производительность, Mbyte/sec | ||||
Core I7 2600K @4.5Ghz DDR3 1800, 2chan |
Core I7-920 @3Ghz DDR3 1500, 3chan |
Core2 Q93000 @2.5Ghz DDR2 800, 2chan |
Core2 T7500 @2.2Ghz DDR2 667, 2chan? |
|
fill-MOVAPS | 12206 | 9484 | 2614 | 1920 |
fill-MOVNTPS | 22761 | 15122 | 6732 | 3588 |
copy-MOVAPS | 8122 | 5960 | 1946 | 1513 |
copy-MOVNTPS | 11673 | 8157 | 2889 | 1970 |
stream-MOVAPS | 11808 | 8936 | 2595 | 1920 |
stream-MOVNTPS | 10454 | 6535 | 3343 | 1920 |
stream2-MOVAPS | 10414 | 6149 | 2613 | - |
stream2-MOVNTPS | 10082 | 5836 | 3243 | - |
stream3-MOVAPS | 8936 | 4563 | 2674 | - |
stream3-MOVNTPS | 9407 | 4062 | 537 | - |
Жирным шрифтом в таблице выделены случаи, когда movntps-реализация получилась медленнее, чем movaps-реализация.
fill и copy
И для copy и для fill movntps очень полезен. До 2.5 раз быстрее (для fill) - это много.
Речь именно о больших объемах, готов допустить, что если результат влезает в L1-кэш, то разница меньше.
stream
С простой потоковой обработкой (добавление константы к значениям в памяти) ситуация сложнее: для Core2 movntps оказывается полезен (для более нового варианта) или, как минимум, не вреден (для старого мобильного варианта).
С Core i7 ситуация обратная: для нового i7-AVX есть небольшой вред (в районе 3.5%), а для старого i7-920 вред оказывается изрядным (25%).
Внимательный читатель спросит: как же так, ведь два дня назад в этом блоге писалось обратное: от movntps на старом Core2 был обнаружен большой вред.
Вред действительно есть, но только в случае сложной обработки:
stream2 и stream3
Stream3 - это уже знакомое нам преобразование цветов, написанное на SSE4.1:{
__m128 m0 = _mm_load_ps(mat[0]);
__m128 m1 = _mm_load_ps(mat[1]);
__m128 m2 = _mm_load_ps(mat[2]);
for(int i=0;i<sz;i++)
{
__m128 d0 = _mm_load_ps(&data[i*4]);
__m128 r0 = _mm_dp_ps(d0,m0,0xf1); // I1, m1
__m128 t0 = _mm_dp_ps(d0,m1,0xff); // I1, m2
__m128 t1 = _mm_dp_ps(d0,m2,0xff); // I1, m3
r0 = _mm_blend_ps(r0,t0,2);
r0 = _mm_blend_ps(r0,t1,4);
STORE(&data[i*4],r0);
}
}
Если удалить один blendps и один dpps, то получится stream2:
{
__m128 m0 = _mm_load_ps(mat[0]);
__m128 m1 = _mm_load_ps(mat[1]);
__m128 m2 = _mm_load_ps(mat[2]);
for(int i=0;i<sz;i++)
{
__m128 d0 = _mm_load_ps(&data[i*4]);
__m128 r0 = _mm_dp_ps(d0,m0,0xf1); // I1, m1
__m128 t0 = _mm_dp_ps(d0,m1,0xff); // I1, m2
r0 = _mm_blend_ps(r0,t0,2);
STORE(&data[i*4],r0);
}
}
У меня нет никаких разумных объяснений, отчего stream3 так тормозит на Core2. Как минимум, дело не в лишней dpps, потому что вариант на чистом SSE2 (по мотивам этого поста) тоже тормозит на Core2, если сохранять выход через movntps.
Мораль:
- Польза от movntps может быть и преизрядная.
- Но крайне желательно проверять наличие этой пользы, а то вдруг выяснится что есть вред.
Из прочего, меня сильно порадовал рост memory bandwidth на последних двух поколениях процессоров. В три раза, если считать с Q9300, за три с небольшим года с осени 2007 по январь 2011.