Подтвердить что ты не робот

Использование инструкций SSE

У меня есть цикл, написанный на С++, который выполняется для каждого элемента большого целочисленного массива. Внутри цикла я маскирую некоторые биты целого числа, а затем нахожу значения min и max. Я слышал, что, если я использую инструкции SSE для этих операций, он будет работать намного быстрее по сравнению с обычным циклом, написанным с использованием побитовых условий И и if-else. Мой вопрос в том, должен ли я идти за этими инструкциями SSE? Кроме того, что произойдет, если мой код работает на другом процессоре? Будет ли он работать, или эти инструкции зависят от процессора?

4b9b3361

Ответ 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 в основном нацелен на ускорение операций с плавающей запятой.

Ответ 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 ​​{}.