vak: (Default)
[personal profile] vak
Во всех современных процессорах имеются арифметические инструкции для векторных вычислений (SIMD). Однако средний программист не имеет представления о них, и не использует. А зря: малыми усилиями можно ускорить код в три-четыре раза. Покажу, как это делается, на примере классического умножения матриц.

Напишем на Си умножение целочисленных матриц размера 4x4:
void matmul(int32_t result[4][4], const int32_t foo[4][4], const int32_t bar[4][4])
{
for (int r = 0; r < 4; r++) {
for (int c = 0; c < 4; c++) {
int sum = 0;
for (int k = 0; k < 4; k++) {
sum += foo[r][k] * bar[k][c];
}
result[r][c] = sum;
}
}
}

Intel и AMD предлагают набор инструкций, известный в узких кругах как SSE или AVX. В Си компиляторе имеются соответствующие встроенные типы и функции (intrinsics). Перепишем наше умножение матриц с этими интринсиками.
#if __x86_64__
#include <immintrin.h>

void matmul_simd(int32_t result[4][4], const int32_t foo[4][4], const int32_t bar[4][4])
{
for (int r = 0; r < 4; ++r) {

// Сумма для этой итерации: строка значений int32, изначально нули.
__m128i sum = _mm_setzero_si128();

for (unsigned k = 0; k < 4; k++) {

// Загружаем одно число первой матрицы, размножаем четыре копии.
const __m128i a = _mm_set1_epi32(foo[r][k]);

// Загружаем строку второй матрицы.
const __m128i b = _mm_loadu_si128((const __m128i *)&bar[k][0]);

// Умножаем поэлементно.
const __m128i product = _mm_mullo_epi32(a, b);

// Суммируем поэлементно.
sum = _mm_add_epi32(sum, product);
}

// Сохраняем строку результата.
_mm_storeu_si128((__m128i *)&result[r][0], sum);
}
}
#endif
В архитектуре ARM64 аналогичный набор инструкций называется Neon. Вот то же самое матричное умножение с интринсиками ARM Neon:
#if __aarch64__
#include <arm_neon.h>

void matmul_simd(int32_t result[4][4], const int32_t foo[4][4], const int32_t bar[4][4])
{
for (int r = 0; r < 4; ++r) {

// Сумма для этой итерации: строка значений int32, изначально нули.
int32x4_t sum = vmovq_n_s32(0);

for (unsigned k = 0; k < 4; k++) {

// Загружаем одно число первой матрицы, размножаем четыре копии.
const int32x4_t a = vdupq_n_s32(foo[r][k]);

// Загружаем строку второй матрицы.
const int32x4_t b = vld1q_s32(&bar[k][0]);

// Умножаем поэлементно.
const int32x4_t product = vmulq_s32(a, b);

// Суммируем поэлементно.
sum = vaddq_s32(sum, product);
}

// Сохраняем строку результата.
vst1q_s32(&result[r][0], sum);
}
}
#endif
Законченный исходный файл, включая тестовые данные, можно взять здесь: matmul4.c. Компилировать надо с флагом -march=native.

Такую картинку нарисовала Gemini, когда я попросил её изобразить умножение матриц 4x4.

This account has disabled anonymous posting.
If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting

If you are unable to use this captcha for any reason, please contact us by email at support@dreamwidth.org