🧺
C言語でSIMD命令によりプログラムの高速化を可視化する
サマリ
単純な加算をする際のSIMD(Single Instruction/Multiple Data)命令との処理時間の比較を行いました。
コードはLLMに書いていただきました。
マシンスペック
MacBook Air M2 arm64
通常のforループにより加算
共通ヘッダ
/* bench_common.h : どちらのソースからもインクルード */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <mach/mach_time.h>
#define N 100000000 // 1 億要素
#define FLUSH_MB 32 // 32 MB で L1/L2 を追い出し
static inline double ns_since(uint64_t t0)
{
mach_timebase_info_data_t info;
mach_timebase_info(&info);
uint64_t dt = mach_absolute_time() - t0;
return (double)dt * info.numer / info.denom; // ns
}
static void flush_dcache(void)
{
static uint8_t *dummy;
const size_t SZ = FLUSH_MB * 1024 * 1024;
if (!dummy) dummy = aligned_alloc(64, SZ);
for (size_t i = 0; i < SZ; i += 64) dummy[i]++;
}
#include "bench_common.h"
__attribute__((noinline))
void scalar_add(const float *a, const float *b, float *c, size_t n)
{
#pragma clang loop vectorize(disable) // ★ 自動 SIMD を無効化
for (size_t i = 0; i < n; ++i)
c[i] = a[i] + b[i]; // 素直な a+b
}
int main(void)
{
float *a = aligned_alloc(16, N * sizeof(float));
float *b = aligned_alloc(16, N * sizeof(float));
float *c = aligned_alloc(16, N * sizeof(float));
for (size_t i = 0; i < N; ++i) { a[i] = (float)i; b[i] = (float)(N - i); }
flush_dcache();
uint64_t t0 = mach_absolute_time();
scalar_add(a, b, c, N);
printf("scalar time = %.2f ms\n", ns_since(t0)/1e6);
free(a); free(b); free(c);
return 0;
}
コンパイルして実行します。
clang -O3 -arch arm64 norm.c -o norm
./norm
結果は
scalar time = 114.01 ms
SIMD命令による加算
#include "bench_common.h"
#include <arm_neon.h>
__attribute__((noinline))
void neon_add(const float *a, const float *b, float *c, size_t n)
{
size_t i = 0;
for (; i + 16 <= n; i += 16) { // 16 要素一括
float32x4_t a0 = vld1q_f32(a + i );
float32x4_t a1 = vld1q_f32(a + i + 4);
float32x4_t a2 = vld1q_f32(a + i + 8);
float32x4_t a3 = vld1q_f32(a + i + 12);
float32x4_t b0 = vld1q_f32(b + i );
float32x4_t b1 = vld1q_f32(b + i + 4);
float32x4_t b2 = vld1q_f32(b + i + 8);
float32x4_t b3 = vld1q_f32(b + i + 12);
vst1q_f32(c + i , vaddq_f32(a0, b0));
vst1q_f32(c + i + 4, vaddq_f32(a1, b1));
vst1q_f32(c + i + 8, vaddq_f32(a2, b2));
vst1q_f32(c + i + 12, vaddq_f32(a3, b3));
}
for (; i < n; ++i) // 端数
c[i] = a[i] + b[i];
}
int main(void)
{
float *a = aligned_alloc(16, N * sizeof(float));
float *b = aligned_alloc(16, N * sizeof(float));
float *c = aligned_alloc(16, N * sizeof(float));
for (size_t i = 0; i < N; ++i) { a[i] = (float)i; b[i] = (float)(N - i); }
flush_dcache();
uint64_t t0 = mach_absolute_time();
neon_add(a, b, c, N);
printf("neon time = %.2f ms\n", ns_since(t0)/1e6);
free(a); free(b); free(c);
return 0;
}
同様にコンパイルして実行します。
clang -O3 -arch arm64 simd.c -o simd
結果は
neon time = 48.31 ms
Discussion