Надлежащий способ включить SSE4 для каждой функции / блока кода?
Для одной из моих программ OS X у меня есть несколько оптимизированных случаев, в которых используются инструкции SSE4.1. На машинах только для SSE3 неоптимизированная ветвь запускается:
// SupportsSSE4_1 returns true on CPUs that support SSE4.1, false otherwise
if (SupportsSSE4_1()) {
// Code that uses _mm_dp_ps, an SSE4 instruction
...
__m128 hDelta = _mm_sub_ps(here128, right128);
__m128 vDelta = _mm_sub_ps(here128, down128);
hDelta = _mm_sqrt_ss(_mm_dp_ps(hDelta, hDelta, 0x71));
vDelta = _mm_sqrt_ss(_mm_dp_ps(vDelta, vDelta, 0x71));
...
} else {
// Equivalent code that uses SSE3 instructions
...
}
Чтобы скомпилировать вышесказанное, мне нужно было установить CLANG_X86_VECTOR_INSTRUCTIONS
в sse4.1
,
Тем не менее, это, кажется, инструктирует Clang, что можно использовать ROUNDSD
Инструкция в любом месте моей программы. Следовательно, программа падает на компьютерах только с SSE3 с SIGILL: ILL_ILLOPC
,
Какова лучшая практика для включения SSE4.1 только для строк кода внутри истинной ветви SupportsSSE4_1()
если блок?
3 ответа
В настоящее время нет никакого способа нацелить различные расширения ISA на гранулярность блока / функции в clang. Вы можете сделать это только при гранулярности файла (поместите код SSE4.1 в отдельный файл и укажите этот файл для использования -msse4.1
). Если это важная функция для вас, пожалуйста, отправьте отчет об ошибке, чтобы запросить его!
Тем не менее, я должен отметить, что на самом деле DPPS
довольно мало в большинстве реальных сценариев (и с использованием DPPS
даже замедляет некоторые последовательности кода!). Если эта конкретная кодовая последовательность не является критической, и вы тщательно измерили эффект от использования DPPS, это может не стоить особых проблем для SSE4.1, даже если эта функция компилятора доступна.
Вы можете сделать диспетчер процессора. Вы можете сделать это в одном файле, но вам придется компилировать дважды. Сначала с SSE4.1, а затем без, а затем ссылка в объектном файле для SSE4.1. Когда вы в первый раз звоните myfunc
это вызывает функцию myfunc_dispatch
который определяет набор инструкций и устанавливает указатель на myfunc_SSE41
или же myfunc_SSE3
, В следующий раз, когда вы звоните myfunc
он переходит прямо к функции для вашего набора команд.
//clang -c -O3 -msse4.1 foo.cpp -o foo_sse41.o
//clang -O3 -msse3 foo.cpp foo_sse41.o
typedef float MyFuncType(float*);
MyFuncType myfunc, myfunc_SSE41, myfunc_SSE3, myfunc_dispatch;
MyFuncType * myfunc_pointer = &myfunc_dispatch;
#ifdef __SSE4_1__
float myfunc_SSE41(float* a) {
//SSE41 code
}
#else
float myfunc_SSE3(float *a) {
//SSE3 code
}
float myfunc_dispatch(float *a) {
if(SupportsSSE4_1()) {
myfunc_pointer = myfunc_SSE41;
}
else {
myfunc_pointer = myfunc_SSE3;
}
myfunc_pointer(a);
}
float myfunc(float *a) {
(*myfunc_pointer)(a);
}
int main() {
//myfunc(a);
}
#endif
В зависимости от ОС вы можете использовать что-то вроде функции Multiversioning в будущем. Я работаю над этой функцией прямо сейчас, но пройдет некоторое время, прежде чем она будет готова для использования в производственном компиляторе.
См. http://gcc.gnu.org/wiki/FunctionMultiVersioning для получения дополнительной информации.