Оптимизация изменяемой и неизменной векторной математики

Какой стиль кодирования лучше подходит для оптимизации компилятора? В частности, меня интересует 1) минимизация количества временных значений, которые отбрасываются немедленно, и 2) автоматическая векторизация, то есть генерирование SIMD-инструкций для арифметики.

Предположим, у меня есть эта структура:

#define FOR_EACH for (int i = 0; i < N; ++i)

template<typename T, unsigned N>
struct Vector {
void scale(T scalar) {
FOR_EACH v[i] *= scalar;
}

void add(const Vector<T, N>& other) {
FOR_EACH v[i] += other.v[i];
}

void mul(const Vector<T, N>& other) {
FOR_EACH v[i] *= other.v[i];
}

T v[N];
};

Пример использования этой структуры:

Vector<int, 3> v1 = ...;
Vector<int, 3> v2 = ...;
v1.scale(10);
v1.add(v2);
v1.mul(v2);

Это изменчивый подход.

Альтернативный неизменный подход может выглядеть так:

template<typename T, unsigned N>
struct Vector {
Vector(const Vector<T, N>& other) {
memcpy(v, other.v, sizeof(v));
}

Vector<T, N> operator+(const Vector<T, N>& other) const {
Vector<T, N> result(*this);
FOR_EACH result.v[i] += other.v[i];
return result;
}

Vector<T, N> operator*(T scalar) const {
Vector<T, N> result(*this);
FOR_EACH result.v[i] *= scalar;
return result;
}

Vector<T, N> operator*(const Vector<T, N>& other) const {
Vector<T, N> result(*this);
FOR_EACH result.v[i] *= other.v[i];
return result;
}

T v[N];
};

Пример использования:

Vector<int, 3> v1 = ...;
Vector<int, 3> v2 = ...;
auto result = (v1 * 10 + v2) * v2;

Теперь я не занимаюсь дизайном API в этом вопросе. Предположим, что оба решения являются жизнеспособными в этом отношении.

Кроме того, вместо int в примере кода это может быть float или же double также.

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

  • Вторая версия выдает много временных значений. Может ли компилятор избавиться от них, если он в конечном итоге встроит все операторные вызовы и увидит все арифметические выражения, содержащиеся внутри? (Я предполагаю, что без встраивания ни один компилятор не может устранить временные эффекты из-за возможных побочных эффектов)

  • Первая версия минимизирует количество временных, но строит строго последовательный расчет. Может ли компилятор по-прежнему определять намерение и переупорядочивать операции таким образом, чтобы минимизировать количество операций и учесть их распараллеливание (на уровне команд ЦП)?

  • Насколько сложно современному компилятору векторизовать циклы выше?

5

Решение

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

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

В некоторых архитектурах вычисления с плавающей точкой нелегко векторизовать из-за ограниченных модулей выполнения с плавающей точкой.

Во втором примере есть временные, которые могут быть устранены путем встраивания.

Полезные ссылки:

0

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]