Быстрое умножение значений в массиве - PullRequest
6 голосов
/ 09 сентября 2010

Существует ли быстрый способ умножения значений массива с плавающей запятой в C ++, чтобы оптимизировать эту функцию (где count кратно 4):

void multiply(float* values, float factor, int count)
{
    for(int i=0; i < count; i++)
    {
        *value *= factor;
        value++;
    }
}

Решение должно работать на Mac OS X и Windows, Intel и не-Intel. Подумайте SSE, векторизация, компилятор (gcc против MSVC).

Ответы [ 7 ]

2 голосов
/ 09 сентября 2010

Вы думали об OpenMP?

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

См. статью Википедии об OpenMP .

2 голосов
/ 09 сентября 2010

Отказ от ответственности: очевидно, это не будет работать на iPhone, iPad, Android или их будущих аналогах.

#include <mmintrin.h>
#include <xmmintrin.h>

__m128 factor4 = _mm_set1_ps(factor);
for (int i=0; i+3 < count; i += 4)
{
   __m128 data = _mm_mul_ps(_mm_loadu_ps(values), factor4);
   _mm_storeu_ps(values, data);
   values += 4;
}
for (int i=(count/4)*4; i < count; i++)
{
   *values *= factor;
   value++;
}
2 голосов
/ 09 сентября 2010

Поскольку вы знаете, что count кратно 4, вы можете развернуть цикл ...

void multiply(float* values, float factor, int count)
{
    count = count >> 2; // count / 4
    for(int i=0; i < count ; i++)
    {
        *value *= factor;
        *(value+1) *= factor;
        *(value+2) *= factor;
        *(value+3) *= factor;
        value += 4;
    }
}
2 голосов
/ 09 сентября 2010

Если вы хотите, чтобы ваш код был кроссплатформенным, то вам либо придется писать независимый от платформы код, либо вам придется написать нагрузку #ifdef s.1003 * Вы пробовали какое-то ручное развертывание петли и посмотрите, имеет ли это какое-то значение?

0 голосов
/ 09 сентября 2010

Я думаю, что вы мало что можете сделать, что имеет большое значение. Может быть, вы можете немного ускорить его с помощью OpenMP или SSE. Но современные процессоры уже довольно быстрые. В некоторых приложениях пропускная способность / задержка памяти на самом деле является узким местом, и оно ухудшается. У нас уже есть три уровня кеша, и нам нужны интеллектуальные алгоритмы предварительной выборки, чтобы избежать огромных задержек. Поэтому имеет смысл подумать и о шаблонах доступа к памяти. Например, если вы реализуете такие multiply и add и используете их так:

void multiply(float vec[], float factor, int size)
{
  for (int i=0; i<size; ++i)
    vec[i] *= factor;
}

void add(float vec[], float summand, int size)
{
  for (int i=0; i<size; ++i)
    vec[i] += summand;
}

void foo(float vec[], int size)
{
  multiply(vec,2.f,size);
  add(vec,9.f,size);
}

вы в основном дважды проходите через блок памяти. В зависимости от размера вектора он может не помещаться в кэш L1, и в этом случае его повторное прохождение дважды добавляет дополнительное время. Это явно плохо, и вы должны стараться, чтобы доступ к памяти был «локальным». В этом случае один цикл

void foo(float vec[], int size)
{
  for (int i=0; i<size; ++i) {
    vec[i] = vec[i]*2+9;
  }
}

скорее всего будет быстрее. Как правило: попробуйте получить доступ к памяти линейно и попытаться получить доступ к памяти «локально», что я имею в виду, попробуйте повторно использовать данные, которые уже находятся в кэше L1. Просто идея.

0 голосов
/ 09 сентября 2010

Как вы упомянули, существует множество архитектур с SIMD-расширениями, и SIMD, вероятно, является лучшим выбором для оптимизации.Однако все они зависят от платформы, а C и C ++ как языки не поддерживают SIMD.

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

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

Наконец, по крайней мере, в ARM, но не уверен в Intel, имейте в виду, что меньшие целочисленные типы и числа с плавающей запятой допускают большее количество параллельных операций на одну инструкцию SIMD.

0 голосов
/ 09 сентября 2010

Лучшее решение состоит в том, чтобы сделать его простым и позволить компилятору оптимизировать его для вас. GCC знает о SSE, SSE2, altivec и о чем еще. Если ваш код слишком сложен, ваш компилятор не сможет оптимизировать его для всех возможных целей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...