32x32 Умножь и добавь оптимизацию - PullRequest
3 голосов
/ 27 июля 2010

Я работаю над оптимизацией приложения.Я обнаружил, что мне нужно оптимизировать внутренний цикл для повышения производительности.rgiFilter - это 16-битный массив.

for (i = 0; i < iLen; i++) {
    iPredErr = (I32)*rgiResidue;
    rgiFilter = rgiFilterBuf;
    rgiPrevVal = rgiPrevValRdBuf + iRecent;
    rgiUpdate = rgiUpdateRdBuf + iRecent;

    iPred = iScalingOffset;

    for (j = 0; j < iOrder_Div_8; j++) {


                 iPred += (I32) rgiFilter[0] * rgiPrevVal[0]; 
                 rgiFilter[0] += rgiUpdate[0];

                 iPred += (I32) rgiFilter[1] * rgiPrevVal[1]; 
                 rgiFilter[1] += rgiUpdate[1];

                 iPred += (I32) rgiFilter[2] * rgiPrevVal[2]; 
                 rgiFilter[2] += rgiUpdate[2];

                 iPred += (I32) rgiFilter[3] * rgiPrevVal[3]; 
                 rgiFilter[3] += rgiUpdate[3];

                 iPred += (I32) rgiFilter[4] * rgiPrevVal[4]; 
                 rgiFilter[4] += rgiUpdate[4];

                 iPred += (I32) rgiFilter[5] * rgiPrevVal[5]; 
                 rgiFilter[5] += rgiUpdate[5];

                 iPred += (I32) rgiFilter[6] * rgiPrevVal[6]; 
                 rgiFilter[6] += rgiUpdate[6];

                 iPred += (I32) rgiFilter[7] * rgiPrevVal[7]; 
                 rgiFilter[7] += rgiUpdate[7];

                    rgiFilter += 8;
        rgiPrevVal += 8;
                    rgiUpdate += 8;



}

здесь

Ответы [ 9 ]

5 голосов
/ 27 июля 2010

Ваша единственная ставка состоит в том, чтобы выполнять более одной операции за раз, и это означает один из следующих трех вариантов:

  1. Инструкции SSE (SIMD).Вы обрабатываете несколько областей памяти с помощью одной инструкции
  2. Многопоточность (MIMD).Это лучше всего работает, если у вас более 1 ядра процессора.Разделите ваш массив на несколько полос одинакового размера, которые не зависят друг от друга (зависимость будет значительно усложнять эту опцию до такой степени, что будет медленнее, чем последовательный расчет всего, если вам нужно много блокировок).Обратите внимание, что массив должен быть достаточно большим, чтобы компенсировать дополнительные издержки на переключение контекста и синхронизацию (он довольно мал, но не пренебрежимо мал).Лучше всего для 4 ядер или более.
  3. Оба одновременно.Если ваш массив действительно большой, вы можете получить много, комбинируя оба.
3 голосов
/ 27 июля 2010

Если rgiFilterBuf, rgiPrevValRdBuf и rgiUpdateRdBuf являются параметрами функций, которые не являются псевдонимами, объявите их с квалификатором restrict. Это позволит компилятору оптимизировать работу более агрессивно.

Как прокомментировали некоторые другие, ваш внутренний цикл выглядит так, как будто он хорошо подходит для инструкций векторной обработки (например, SSE, если вы используете x86). Проверьте свойства вашего компилятора.

2 голосов
/ 27 июля 2010

Я не думаю, что вы можете многое сделать, чтобы оптимизировать его в C. Ваш компилятор может иметь опции для генерации SIMD-кода, но вам, вероятно, нужно просто пойти и написать свой собственный код SIMD-сборки, если производительность критична ...

1 голос
/ 27 июля 2010

Внутренний цикл можно заменить очень небольшим количеством встроенных функций SSE2

см. [_Mm_madd_epi16] [1], чтобы заменить восемь

iPred += (I32) rgiFilter[] * rgiPrevVal[];

и [_mm_add_epi16] [2] или _ [mm_add_epi32] [3] для замены восьми

rgiFilter[] += rgiUpdate[];

Вы должны увидеть хорошее ускорение с этим одним.

Эти свойства характерны для компиляторов Microsoft и Intel. Я уверен, что существуют эквиваленты для GCC, я просто не использовал их.

РЕДАКТИРОВАТЬ: основываясь на комментариях ниже, я бы изменил следующее ...

Если у вас смешанные типы, компилятор не всегда достаточно умен, чтобы понять это. Я хотел бы предложить следующее, чтобы сделать его более очевидным и дать ему больше шансов при автовекторизации.

  1. объявить rgiFilter [] битом I32 для цели этой функции. Вы заплатит один экземпляр.
  2. изменить iPred на iPred [] на I32 также
  3. сделать суммирование iPred [] вне внутренней (или даже внешней) петли

  4. Упакуйте аналогичные инструкции в группы по четыре

    iPred [0] + = rgiFilter [0] * rgiPrevVal [0];

    iPred [1] + = rgiFilter [1] * rgiPrevVal [1];

    iPred [2] + = rgiFilter [2] * rgiPrevVal [2];

    iPred [3] + = rgiFilter [3] * rgiPrevVal [3];

    rgiFilter [0] + = rgiUpdate [0];

    rgiFilter [1] + = rgiUpdate [1];

    rgiFilter [2] + = rgiUpdate [2];

    rgiFilter [3] + = rgiUpdate [3];

Этого должно быть достаточно, чтобы компилятор Intel понял это

0 голосов
/ 28 июля 2010

Существует множество оптимизаций, которые вы можете выполнить, включая введение целевого кода.Однако я буду придерживаться в основном общих вещей.

Во-первых, если вы собираетесь зацикливаться с ограничениями индекса, то вам обычно следует пытаться зацикливаться вниз.*

до

for (i = iLen-1; i <= 0; i--) {

Это может использовать тот факт, что многие распространенные процессоры по существу выполняют сравнение с 0 для результатов любой математической операции, поэтому вам не нужно делать явное сравнение.

Это работает только в том случае, если обратный ход цикла имеет те же результаты и если индекс подписан (хотя вы можете обойти это).

В качестве альтернативы вы можете попробовать ограничить с помощьюуказатель математика.Это может устранить необходимость в явной переменной индекса (счетчика), которая может ускорить процесс, особенно если регистров не хватает.

for (p = rgiFilter; p <= rgiFilter+8; ) {
     iPred += (I32) (*p) + *rgiPreval++;
     *p++ += *rgiUpdate++;

     ....

}

Это также избавляет от нечетного обновления в конце вашеговнутренний циклОбновление в конце цикла может запутать компилятор и заставить его создавать худший код.Вы также можете обнаружить, что развертывание цикла, которое вы делали, может привести к худшим или в равной степени таким же хорошим результатам, как если бы у вас было только два оператора в теле внутреннего цикла.Компилятор, вероятно, может принимать правильные решения о том, как этот цикл должен быть развернут / развернут.Или вы можете просто захотеть убедиться, что цикл развернут дважды, поскольку rgiFilter - это массив 16-битных значений, и посмотреть, может ли компилятор получить доступ к нему только дважды, чтобы выполнить два чтения и две записи - выполнение одной 32-битной загрузки.и одно 32-битное хранилище.

for (p = rgiFilter; p <= rgiFilter+8; ) {
     I16 x = *p;
     I16 y = *(p+1); // Hope that the compiler can combine these loads
     iPred += (I32) x + *rgiPreval++;
     iPred += (I32) y + *rgiPreval++;

     *p++ += *rgiUpdate++;
     *p++ += *rgiUpdate++; // Hope that the complier can combine these stores

     ....

}

Если ваш компилятор и / или целевой процессор поддерживает его, вы также можете попробовать выполнить инструкции предварительной выборки.Например, gcc имеет:

__builtin_prefetch (const void * addr)
__builtin_prefetch (const void * addr, int rw)
__builtin_prefetch (const void * addr, int rw, int locality)

. Они могут быть использованы, чтобы сообщить компилятору, что если у цели есть инструкции предварительной выборки, она должна использовать их, чтобы попытаться продолжить и получить addr в кэш.Оптимально это должно быть выдано один раз на шаг строки кэша для массива, с которым вы работаете.Аргумент rw указывает компилятору, хотите ли вы читать или писать по адресу.Локальность связана с тем, должны ли данные оставаться в кэше после того, как вы к ним получите доступ.Компилятор просто пытается сделать все возможное, чтобы выяснить, как сгенерировать правильные инструкции для этого, но если он не может сделать то, что вы просите, для определенной цели, он просто ничего не делает и ничего не вредит.

Кроме того, поскольку функции __builtin_ являются специальными, обычные правила, касающиеся переменного числа аргументов, на самом деле не применяются - это подсказка компилятору, а не вызов функции.

ВыТакже следует изучить любые векторные операции, которые поддерживает ваша цель, а также любые общие, специфические для платформы функции, встроенные функции или прагмы, которые ваш компилятор поддерживает для выполнения векторных операций.

0 голосов
/ 27 июля 2010

Довольно хороший код.

На каждом шаге вы в основном делаете три вещи: умножение и два сложения.

Другие предложения хороши. Кроме того, я иногда обнаруживал, что получаю более быстрый код, если разделяю эти действия на разные циклы, например

  • один цикл для умножения и сохранения во временный массив.

  • один цикл для суммирования этого массива в iPred.

  • один цикл для добавления rgiUpdate к rgiFilter.

При развертывании издержки цикла незначительны, но если количество различных операций внутри каждого цикла сведено к минимуму, компилятор иногда может лучше использовать свои регистры.

0 голосов
/ 27 июля 2010

Начните с того, что убедитесь, что данные расположены линейно в памяти, чтобы не было ошибок в кеше. Это не кажется проблемой, хотя.

Если вы не можете выполнить операции SSE (и если с ним не работает компилятор - посмотрите на сборку), попробуйте разделить на несколько различных циклов for, которые меньше (по одному на каждые 0 .. 8). Компиляторы, как правило, могут лучше оптимизировать циклы, которые выполняют меньшее количество операций (за исключением случаев, подобных этому, когда он может выполнять векторизацию / SSE).

16-битные целые числа более дороги для использования 32/64-битной архитектурой (если они не имеют определенных 16-битных регистров). Попробуйте преобразовать его в 32 бита перед выполнением цикла (большинство 64-битных архитектур также имеют 32-битные регистры).

0 голосов
/ 27 июля 2010

Развертывание цикла и векторизация должны быть оставлены компилятору.

См. Gcc Авто-векторизация

0 голосов
/ 27 июля 2010
  1. Убедитесь, что iPred хранится в регистре (не читается из памяти до и не записывается обратно в память после каждой операции + =).
  2. Оптимизация структуры памяти для кэша 1-го уровня. Убедитесь, что 3 массива не борются за одинаковые записи в кэше. Это зависит от архитектуры процессора и совсем не просто.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...