У меня есть цикл, написанный на С++, который выполняется для каждого элемента большого целочисленного массива. Внутри цикла я маскирую некоторые биты целого числа, а затем нахожу значения min и max. Я слышал, что, если я использую инструкции SSE для этих операций, он будет работать намного быстрее по сравнению с обычным циклом, написанным с использованием побитовых условий И и if-else. Мой вопрос в том, должен ли я идти за этими инструкциями SSE? Кроме того, что произойдет, если мой код работает на другом процессоре? Будет ли он работать, или эти инструкции зависят от процессора?
Использование инструкций SSE
Ответ 1
- Инструкции SSE специфичны для процессора. Вы можете посмотреть, какой процессор поддерживает версию SSE на wikipedia.
- Если код SSE будет быстрее или не зависит от многих факторов: во-первых, конечно, связана ли проблема с привязкой к памяти или с привязкой к процессору. Если шина памяти является узким местом SSE, это не поможет. Попробуйте упростить вычисление целых чисел, если это ускорит работу кода, возможно, связано с CPU, и у вас есть хорошие шансы ускорить его.
- Имейте в виду, что писать SIMD-код намного сложнее, чем писать С++-код, и что полученный код намного сложнее изменить. Всегда обновляйте код С++, вы хотите, чтобы он был как комментарий, и чтобы проверить правильность кода ассемблера.
- Подумайте об использовании библиотеки, такой как IPP, которая реализует обычные низкоуровневые SIMD-операции, оптимизированные для разных процессоров.
Ответ 2
SIMD, примером которого является SSE, позволяет выполнять одну и ту же операцию на нескольких фрагментах данных. Таким образом, вы не получите никакого преимущества от использования SSE в качестве прямой замены целочисленных операций, вы получите только преимущества, если вы сможете выполнять операции с несколькими элементами данных одновременно. Это связано с загрузкой некоторых значений данных, которые смежны в памяти, выполняют требуемую обработку и затем переходят к следующему набору значений в массиве.
Проблемы:
1 Если путь кода зависит от обрабатываемых данных, SIMD становится намного сложнее реализовать. Например:
a = array [index];
a &= mask;
a >>= shift;
if (a < somevalue)
{
a += 2;
array [index] = a;
}
++index;
не так просто сделать, как SIMD:
a1 = array [index] a2 = array [index+1] a3 = array [index+2] a4 = array [index+3]
a1 &= mask a2 &= mask a3 &= mask a4 &= mask
a1 >>= shift a2 >>= shift a3 >>= shift a4 >>= shift
if (a1<somevalue) if (a2<somevalue) if (a3<somevalue) if (a4<somevalue)
// help! can't conditionally perform this on each column, all columns must do the same thing
index += 4
2 Если данные не являются смежными, тогда загрузка данных в инструкции SIMD громоздка
3 Код специфичен для процессора. SSE работает только на IA32 (Intel/AMD), и не все поддержка SSE с поддержкой IS32.
Вам нужно проанализировать алгоритм и данные, чтобы узнать, может ли он быть SSE'd, и для этого требуется знание того, как работает SSE. На сайте Intel есть много документации.
Ответ 3
Эта проблема является прекрасным примером того, где необходим хороший профилировщик низкого уровня. (Что-то вроде VTune) Это может дать вам гораздо более обоснованное представление о том, где лежат ваши горячие точки.
Мое предположение, из того, что вы описываете, является то, что ваша точка доступа, вероятно, будет ошибкой прогнозирования ветвления, возникающей в результате вычислений min/max, используя if/else. Поэтому, используя встроенные функции SIMD, вы должны использовать инструкции min/max, однако, возможно, стоит попробовать вместо этого использовать нераспределенную min/max caluculation. Это может обеспечить большую часть выигрышей с меньшей болью.
Что-то вроде этого:
inline int
minimum(int a, int b)
{
int mask = (a - b) >> 31;
return ((a & mask) | (b & ~mask));
}
Ответ 4
Если вы используете инструкции SSE, вы, очевидно, ограничены процессорами, которые их поддерживают. Это означает, что x86, относящийся к Pentium 2 или около того (не помню точно, когда они были введены, но это было давно)
SSE2, который, насколько я помню, является тем, который предлагает целочисленные операции, является несколько более недавним (Pentium 3? Хотя первые процессоры AMD Athlon их не поддерживали)
В любом случае у вас есть два варианта использования этих инструкций. Либо напишите весь блок кода в сборке (возможно, это плохая идея. Это делает практически невозможным компилятор для оптимизации вашего кода, и человеку очень сложно писать эффективный ассемблер).
В качестве альтернативы используйте встроенные функции, доступные с вашим компилятором (если используется память, они обычно определяются в xmmintrin.h)
Но опять же производительность может не улучшиться. Код SSE создает дополнительные требования к обрабатываемым им данным. В основном, нужно иметь в виду, что данные должны быть выровнены на 128-битных границах. Также должно быть немного или нет зависимостей между значениями, загружаемыми в один и тот же регистр (128-битный SSE-регистр может содержать 4 интервала. Добавление первого и второго вместе не является оптимальным. Но добавление всех четырех int к соответствующим 4 ints в другой регистр будет быстрым)
Может возникнуть соблазн использовать библиотеку, которая обертывает все низкоуровневые SSE-скрипты, но это может также испортить любую потенциальную выгоду.
Я не знаю, насколько хорошо поддерживается SSE целая операция, так что это также может быть фактором, который может ограничить производительность. SSE в основном нацелен на ускорение операций с плавающей запятой.
Ответ 5
Если вы намерены использовать Microsoft Visual С++, вы должны прочитать следующее:
Ответ 6
Мы внедрили некоторый код обработки изображений, похожий на то, что вы описываете, но на массив байтов, в SSE. Ускорение по сравнению с C-кодом значительно, в зависимости от точного алгоритма, более чем в 4 раза, даже в отношении компилятора Intel. Однако, как вы уже упоминали, у вас есть следующие недостатки:
-
портативность. Код будет работать на каждом процессоре Intel, например AMD, но не на других процессорах. Для нас это не проблема, потому что мы контролируем целевое оборудование. Проблемой может быть переключение компиляторов и даже на 64-разрядную ОС.
-
У вас крутая кривая обучения, но я обнаружил, что после понимания принципов написания новых алгоритмов это не так сложно.
- ремонтопригодность
. Большинство программистов на C или С++ не знают сборки /SSE.
Мой совет вам будет идти на это, только если вам действительно нужно улучшить производительность, и вы не можете найти функцию для своей проблемы в библиотеке, такой как Intel IPP, и если вы можете жить с проблемами переносимости.
Ответ 7
Я могу сказать по моему опыту, что SSE приносит огромную (4 раза и выше) скорость по сравнению с простой версией кода c (без встроенного asm, без использования встроенных средств), но ассемблер с ручным оптимизацией может бить сборку, созданную компилятором, если компилятор не может понять, что программист намеревался (верьте мне, компиляторы не охватывают все возможные комбинации кода, и они никогда не будут). О, и, компилятор не может каждый раз компоновать данные, которые он запускает с максимально возможной скоростью. Но вам нужно много опыта для ускорения работы над компилятором Intel (если возможно).
Ответ 8
Инструкции SSE изначально были только на чипах Intel, но в последнее время (с Athlon?) AMD поддерживает их также, поэтому, если вы делаете код с набором инструкций SSE, вы должны быть переносимыми для большинства процессов x86.
При этом не стоит тратить время на изучение кодирования SSE, если вы уже не знакомы с ассемблером на x86 - проще всего проверить ваши документы компилятора и посмотреть, есть ли варианты, позволяющие компилятору автогенерирующий код SSE для вас. Некоторые компиляторы делают очень хорошо векторизовать петли таким образом. (Вы, вероятно, не удивлены, услышав, что компиляторы Intel хорошо справляются с этим:)
Ответ 9
Введите код, который поможет компилятору понять, что вы делаете. GCC будет понимать и оптимизировать код SSE, например:
typedef union Vector4f
{
// Easy constructor, defaulted to black/0 vector
Vector4f(float a = 0, float b = 0, float c = 0, float d = 1.0f):
X(a), Y(b), Z(c), W(d) { }
// Cast operator, for []
inline operator float* ()
{
return (float*)this;
}
// Const ast operator, for const []
inline operator const float* () const
{
return (const float*)this;
}
// ---------------------------------------- //
inline Vector4f operator += (const Vector4f &v)
{
for(int i=0; i<4; ++i)
(*this)[i] += v[i];
return *this;
}
inline Vector4f operator += (float t)
{
for(int i=0; i<4; ++i)
(*this)[i] += t;
return *this;
}
// Vertex / Vector
// Lower case xyzw components
struct {
float x, y, z;
float w;
};
// Upper case XYZW components
struct {
float X, Y, Z;
float W;
};
};
Просто не забывайте иметь -msse -msse2 для ваших параметров сборки!
Ответ 10
Хотя верно, что SSE специфичен для некоторых процессоров (SSE может быть относительно безопасным, SSE2 намного меньше в моем опыте), вы можете обнаружить процессор во время выполнения и динамически загружать код в зависимости от целевого CPU.
Ответ 11
Инициативы SIMD (такие как SSE2) могут ускорить работу такого рода, но использовать опыт для правильного использования. Они очень чувствительны к выравниванию и задержке трубопровода; небрежное использование может сделать производительность еще хуже, чем без них. Вы получите гораздо более легкое и быстрое ускорение, просто используя предварительную выборку кеша, чтобы убедиться, что все ваши функции находятся в L1, чтобы вы могли работать с ними.
Если ваша функция не нуждается в пропускной способности более 100 000 000 целых чисел в секунду, SIMD, вероятно, не стоит проблем для вас.
Ответ 12
Просто добавлю вкратце то, что было сказано ранее о том, что разные версии SSE доступны на разных процессорах: это можно проверить, посмотрев соответствующие флаги функций, возвращаемые инструкцией CPUID (см., например, документацию Intel для деталей).
Ответ 13
Посмотрите на встроенный ассемблер для C/С++, вот статья DDJ. Если вы на 100% не уверены, что ваша программа будет работать на совместимой платформе, вы должны следовать рекомендациям, которые многие здесь дали.
Ответ 14
Я согласен с предыдущими плакатами. Преимущества могут быть довольно большими, но для его получения может потребоваться много работы. Документация Intel по этим инструкциям составляет более 4 тыс. Страниц. Возможно, вы захотите проверить EasySSE (библиотеку обложек С++ по примерам intrinsics +) без Ocali Inc.
Я полагаю, что моя принадлежность к этому EasySSE понятна.
Ответ 15
Я не рекомендую делать это самостоятельно, если вы недостаточно разбираетесь в сборке. Использование SSE, скорее всего, потребует тщательной реорганизации ваших данных, поскольку Skizz указывает, и преимущество в лучшем случае может оказаться под вопросом.
Вероятно, вам будет намного лучше писать очень маленькие циклы и очень тщательно организовать ваши данные и просто полагаться на компилятор, делающий это для вас. Как Intel C Compiler, так и GCC (начиная с версии 4.1) могут автоматически векторизовать ваш код и, вероятно, будут работать лучше, чем вы. (Просто добавьте -tree-vectorize для вашего CXXFLAGS.)
Изменить. Еще одна вещь, которую я должен упомянуть, заключается в том, что несколько компиляторов поддерживают ассемблеры сборки, которые, вероятно, будут более удобными для использования, чем синтаксис asm() или __asm {}.