У меня есть следующий код поиска и интерполяции для оптимизации. (плавающий стол размером 128)
Он будет использоваться с компилятором Intel для Windows, GCC для OSX и GCC с неоновым OSX.
for(unsigned int i = 0 ; i < 4 ; i++)
{
const int iIdx = (int)m_fIndex[i];
const float frac = m_fIndex - iIdx;
m_fResult[i] = sftable[iIdx].val + sftable[iIdx].val2 * frac;
}
я все проверил с помощью sse / neon. (макросы конвертируются в инструкции sse / neon)
VEC_INT iIdx = VEC_FLOAT2INT(m_fIndex);
VEC_FLOAT frac = VEC_SUB(m_fIndex ,VEC_INT2FLOAT(iIdx);
m_fResult[0] = sftable[iIdx[0]].val2;
m_fResult[1] = sftable[iIdx[1]].val2;
m_fResult[2] = sftable[iIdx[2]].val2;
m_fResult[3] = sftable[iIdx[3]].val2;
m_fResult=VEC_MUL( m_fResult,frac);
frac[0] = sftable[iIdx[0]].val1;
frac[1] = sftable[iIdx[1]].val1;
frac[2] = sftable[iIdx[2]].val1;
frac[3] = sftable[iIdx[3]].val1;
m_fResult=VEC_ADD( m_fResult,frac);
я думаю, что доступ к таблице и перемещение в выровненную память — реальное узкое место здесь.
Я не очень разбираюсь в ассемблере, но есть много unpcklps и mov:
10026751 mov eax,dword ptr [esp+4270h]
10026758 movaps xmm3,xmmword ptr [eax+16640h]
1002675F cvttps2dq xmm5,xmm3
10026763 cvtdq2ps xmm4,xmm5
10026766 movd edx,xmm5
1002676A movdqa xmm6,xmm5
1002676E movdqa xmm1,xmm5
10026772 psrldq xmm6,4
10026777 movdqa xmm2,xmm5
1002677B movd ebx,xmm6
1002677F subps xmm3,xmm4
10026782 psrldq xmm1,8
10026787 movd edi,xmm1
1002678B psrldq xmm2,0Ch
10026790 movdqa xmmword ptr [esp+4F40h],xmm5
10026799 mov ecx,dword ptr [eax+edx*8+10CF4h]
100267A0 movss xmm0,dword ptr [eax+edx*8+10CF4h]
100267A9 mov dword ptr [eax+166B0h],ecx
100267AF movd ecx,xmm2
100267B3 mov esi,dword ptr [eax+ebx*8+10CF4h]
100267BA movss xmm4,dword ptr [eax+ebx*8+10CF4h]
100267C3 mov dword ptr [eax+166B4h],esi
100267C9 mov edx,dword ptr [eax+edi*8+10CF4h]
100267D0 movss xmm7,dword ptr [eax+edi*8+10CF4h]
100267D9 mov dword ptr [eax+166B8h],edx
100267DF movss xmm1,dword ptr [eax+ecx*8+10CF4h]
100267E8 unpcklps xmm0,xmm7
100267EB unpcklps xmm4,xmm1
100267EE unpcklps xmm0,xmm4
100267F1 mulps xmm0,xmm3
100267F4 movaps xmmword ptr [eax+166B0h],xmm0
100267FB mov ebx,dword ptr [esp+4F40h]
10026802 mov edi,dword ptr [esp+4F44h]
10026809 mov ecx,dword ptr [esp+4F48h]
10026810 mov esi,dword ptr [esp+4F4Ch]
10026817 movss xmm2,dword ptr [eax+ebx*8+10CF0h]
10026820 movss xmm5,dword ptr [eax+edi*8+10CF0h]
10026829 movss xmm3,dword ptr [eax+ecx*8+10CF0h]
10026832 movss xmm6,dword ptr [eax+esi*8+10CF0h]
1002683B unpcklps xmm2,xmm3
1002683E unpcklps xmm5,xmm6
10026841 unpcklps xmm2,xmm5
10026844 mulps xmm2,xmm0
10026847 movaps xmmword ptr [eax+166B0h],xmm2
При профилировании не так много преимуществ с версией sse на win.
Есть ли у вас какие-либо предложения, как улучшить?
Ожидаются ли какие-либо побочные эффекты с неоном / gcc?
В настоящее время я рассматриваю только то, как сделать первую часть векоризованной, а также выполнять разметку и интерполяцию таблиц в цикле, надеясь, что это выиграет от оптимизации компилятора.
OSX? Тогда это не имеет ничего общего с НЕОН.
Кстати, NEON все равно не может справиться с такими большими LUT. (Я не знаю о SSE по этому вопросу)
Сначала проверьте, может ли SSE обрабатывать LUT такого размера, если да, я предлагаю использовать другой компилятор, так как GCC имеет тенденцию делать взломы из встроенных.
Это один из самых худших коденов компилятора, который я когда-либо видел (при условии, что оптимизатор включен). Стоит подать ошибку против GCC.
Главные проблемы:
val
а также val2
для каждого поиска отдельно.val
а также val2
в георадар отдельно.Чтобы компиляторы могли генерировать лучший код (по одной загрузке для каждой строки таблицы), вам может потребоваться загрузить каждую строку таблицы, как если бы она была двойной, а затем привести ее к вектору двух чисел с плавающей запятой и перевернуть строки, чтобы получить однородность векторы. Как на NEON, так и на SSE, для этого потребуется только четыре загрузки и три или четыре распаковки (намного лучше, чем текущие восемь загрузок + шесть распаковок).
Избавиться от лишнего стека трафика может быть сложнее. Убедитесь, что оптимизатор включен. Устранение проблемы множественной загрузки уменьшит вдвое трафик стека, поскольку вы будете генерировать каждый индекс только один раз, но чтобы полностью избавиться от него, может потребоваться написать сборку вместо встроенных (или использовать более новую версию компилятора).
Одна из причин, по которой компилятор создает «прикольный» код (с большим количеством повторных загрузок), заключается в том, что для правильности он должен предполагать, что данные в sftable[]
массивы может поменяться. Чтобы сделать сгенерированный код лучше, реструктурируйте его так:
VEC_INT iIdx = VEC_FLOAT2INT(m_fIndex);
VEC_FLOAT frac = VEC_SUB(m_fIndex ,VEC_INT2FLOAT(iIdx);
VEC_FLOAT fracnew;
// make it explicit that all you want is _four loads_
typeof(*sftable) tbl[4] = {
sftable[iIdx[0]], sftable[iIdx[1]], sftable[iIdx[2]], sftable[iIdx[3]]
};
m_fResult[0] = tbl[0].val2
m_fResult[1] = tbl[1].val2;
m_fResult[2] = tbl[2].val2;
m_fResult[3] = tbl[3].val2;
fracnew[0] = tbl[0].val1;
fracnew[1] = tbl[1].val1;
fracnew[2] = tbl[2].val1;
fracnew[3] = tbl[3].val1;
m_fResult=VEC_MUL( m_fResult,frac);
m_fResult=VEC_ADD( m_fResult,fracnew);
frac = fracnew;
Это может иметь смысл (из-за чередование макет того, что у вас есть в sftable[]
) использовать встроенные функции, потому что оба векторных массива с плавающей точкой fResult
а также frac
вполне вероятно, загружаемые из tbl[]
с одной инструкцией (распаковать привет / ло в SSE, распаковать в неон). «Основной» просмотр таблицы не может быть векторизован без помощи чего-то вроде AVX2 VGATHER
инструкция, но это не должно быть больше, чем четыре нагрузки.