00 Neden assembly?
Modern derleyiciler çoğu durumda mükemmel makine kodu üretir. Ancak belirli hotpath senaryolarında, SIMD kullanımında ve donanıma özgü optimizasyonlarda elle yazılmış assembly ya da intrinsics %2–10 arasında değil, kimi zaman %200–500 düzeyinde kazanım sağlayabilir.
Derleyicinin yetersiz kaldığı durumlar
Profil önce, optimize sonra
# Önce gerçek bottleneck'i bul
perf stat -e cycles,instructions,cache-misses ./program
# IPC (instructions per cycle) < 1.0 ise memory bound
# cache-miss yüksekse bellek erişim düzeni optimize et
# Sadece sonra SIMD/assembly yaz
Kodu önce doğru yaz, sonra profilini al, sonra sadece gerçek bottleneck olan %5'lik kısmı optimize et. Assembly yazmadan önce -O3 -march=native -ffast-math derleyici flaglerini dene; çoğu zaman bu yeterlidir.
01 AArch64 ISA primer
AArch64, ARMv8 mimarisinin 64-bit execution state'idir. A64 instruction set'i yalnızca AArch64'te çalışır ve önceki ARM instruction encoding'lerinden tamamen farklıdır.
Execution states
| State | Bit Genişliği | Instruction Set | Not |
|---|---|---|---|
| AArch64 | 64-bit | A64 | ARMv8 yeni mod — gömülü Linux hedefi |
| AArch32 | 32-bit | A32 (ARM) + T32 (Thumb) | Geriye dönük uyumluluk |
Register seti
Genel Amaçlı Registerlar (64-bit)
x0 – x7 : Fonksiyon argümanları / dönüş değerleri
x8 : Dolaylı sonuç konumu / syscall numarası
x9 – x15 : Caller-saved geçici registerlar
x16 – x17 : Intra-procedure-call scratch (IP0, IP1)
x18 : Platform register (genelde dokunulmamalı)
x19 – x28 : Callee-saved registerlar
x29 (fp) : Frame pointer
x30 (lr) : Link register (dönüş adresi)
sp : Stack pointer (16-byte hizalı olmalı)
pc : Program counter (doğrudan yazılamaz)
xzr/wzr : Sıfır register (her zaman 0 okur, yazmalar yoksayılır)
32-bit alt kayıtlara w prefix ile erişilir:
x0 → w0, x1 → w1, ... (üst 32-bit sıfırlanır)
PSTATE (işlemci durum registerı):
N (Negative), Z (Zero), C (Carry), V (Overflow)
SIMD / Floating-point Registerlar:
v0 – v31 : 128-bit vektör registerları
b0 (8-bit), h0 (16-bit), s0 (32-bit), d0 (64-bit), q0 (128-bit)
Exception Levels (EL)
02 A64 instruction set
A64 tüm komutlar 32-bit genişliğindedir (sabit uzunluk). Temel komut gruplarını bilmek assembly okuma ve yazma için yeterlidir.
Load / Store
// Tek register yükle / depola
ldr x0, [x1] // x0 = *(x1)
ldr x0, [x1, #8] // x0 = *(x1 + 8)
ldr x0, [x1, x2] // x0 = *(x1 + x2)
ldr x0, [x1, x2, lsl #3] // x0 = *(x1 + x2*8)
ldr x0, [x1], #8 // x0 = *x1; x1 += 8 (post-index)
ldr x0, [x1, #8]! // x1 += 8; x0 = *x1 (pre-index)
str x0, [x1] // *(x1) = x0
str w0, [x1, #4] // 32-bit store
// Çift register yükle / depola (verimli — 2 register = 1 komut)
ldp x0, x1, [x2] // x0 = *(x2), x1 = *(x2+8)
stp x0, x1, [sp, #-16]! // stack'e push (pre-decrement)
Aritmetik ve mantık
// Toplama / Çıkarma
add x0, x1, x2 // x0 = x1 + x2
add x0, x1, #100 // x0 = x1 + 100 (immediate)
adds x0, x1, x2 // flag'leri güncelle
adc x0, x1, x2 // x0 = x1 + x2 + C (carry)
sub x0, x1, x2 // x0 = x1 - x2
subs x0, x1, x2 // subtraction + flags
mul x0, x1, x2 // x0 = x1 * x2 (düşük 64-bit)
madd x0, x1, x2, x3 // x0 = x1*x2 + x3 (fused multiply-add)
sdiv x0, x1, x2 // x0 = x1 / x2 (signed)
// Bit kaydırma
lsl x0, x1, #3 // x0 = x1 << 3
lsr x0, x1, #3 // x0 = x1 >> 3 (logical, zero-fill)
asr x0, x1, #3 // x0 = x1 >> 3 (arithmetic, sign-fill)
// Mantık
and x0, x1, x2
orr x0, x1, x2
eor x0, x1, x2 // XOR
bic x0, x1, x2 // x0 = x1 & ~x2
Dal (Branch) komutları
// Koşulsuz dal
b label // PC-relative dal (±128MB)
br x0 // register'daki adrese dal
bl func // subroutine call (lr = PC+4)
blr x0 // indirect call
ret // return (br lr)
ret x19 // başka register'dan return
// Koşullu dal
cmp x0, x1 // x0 - x1 (flags güncelle, sonuç atılır)
beq label // Z=1 ise dal
bne label // Z=0 ise dal
blt label // N≠V ise dal (signed less than)
bge label // N=V ise dal (signed greater or equal)
blo label // C=0 ise dal (unsigned lower)
bhs label // C=1 ise dal (unsigned higher or same)
// Sıfır/sıfır-değil testi (cmp yerine daha verimli)
cbz x0, label // x0==0 ise dal
cbnz x0, label // x0!=0 ise dal
// Bit testi
tbz x0, #5, label // bit 5 sıfır ise dal
tbnz x0, #5, label // bit 5 bir ise dal
03 Calling convention (AAPCS64)
AAPCS64 (Procedure Call Standard for AArch64), fonksiyon argümanlarının, dönüş değerlerinin ve register tasarruf sorumluluklarının nasıl dağıtıldığını tanımlar.
Argüman ve dönüş register kuralları
| Register | Rol | Kimin sorumluluğunda |
|---|---|---|
| x0 – x7 | İlk 8 integer/pointer argüman + dönüş değeri | Caller-saved (çağıran kurtarır) |
| x8 | Dolaylı sonuç adresi (büyük struct dönüşü) | Caller-saved |
| x9 – x15 | Geçici (scratch) registerlar | Caller-saved |
| x16, x17 | PLT / linker scratch | Caller-saved |
| x19 – x28 | Callee-saved registerlar | Callee-saved (çağrılan kurtarır) |
| x29 (fp) | Frame pointer | Callee-saved |
| x30 (lr) | Link register | Caller-saved (ama fonksiyon içinde kaydedilmeli) |
| v0 – v7 | SIMD/FP argümanlar ve dönüş | Caller-saved |
| v8 – v15 | Callee-saved SIMD (sadece alt 64-bit) | Callee-saved |
Fonksiyon prologue / epilogue
// Standart AArch64 fonksiyon yapısı:
.globl my_function
my_function:
// Prologue: frame pointer + link register kaydet
stp x29, x30, [sp, #-32]! // sp -= 32; *(sp) = x29; *(sp+8) = x30
mov x29, sp // frame pointer kur
// Callee-saved register kullanılacaksa kaydet
stp x19, x20, [sp, #16] // *(sp+16) = x19; *(sp+24) = x20
// ... fonksiyon gövdesi ...
// x0 = dönüş değeri
// Epilogue: kayıtlı registerleri geri yükle
ldp x19, x20, [sp, #16]
ldp x29, x30, [sp], #32 // x29,x30 geri yükle; sp += 32
ret
Stack alignment
AArch64'te stack pointer her fonksiyon çağrısında 16-byte hizalı olmalıdır. AAPCS64 bu kuralı zorunlu kılar; hizasız erişimde bazı sistemlerde alignment fault atılır.
// Yerel değişkenler için alan aç (16-byte katları)
sub sp, sp, #32 // 32 byte (2 × 16) — OK
// sub sp, sp, #12 // 12 byte — YANLIŞ! 16'nın katı değil
// sp her zaman 16-byte hizalı:
and x0, sp, #0xf // 0 olmalı
04 Inline assembly
C kodu içinde asm volatile ile assembly komutları gömülür. GCC/Clang constraint sistemi, C değişkenlerini assembly registerlarına bağlar.
Temel sözdizimi
// asm("komutlar" : çıkış : giriş : clobber);
// Basit örnek: x = x + y
int x = 10, y = 5, result;
asm volatile(
"add %0, %1, %2" // komut: %0=çıkış, %1/%2=giriş
: "=r"(result) // çıkış constraint: "r"=register
: "r"(x), "r"(y) // giriş constraint
: // clobber listesi (boş)
);
// result = 15
Constraint türleri
| Constraint | Açıklama |
|---|---|
| "r" | Genel amaçlı register (x0–x30) |
| "m" | Bellek operandı |
| "i" | Immediate (sabit) değer |
| "=r" | Yazılacak register (çıkış) |
| "+r" | Hem okunacak hem yazılacak register |
| "w" | SIMD/FP register (v0–v31) |
| "0" | 0. constraint ile aynı register |
Gerçek kullanım örnekleri
// 1. Sistem sayaç oku (PMU cycle counter)
static inline uint64_t read_cycles(void) {
uint64_t val;
asm volatile("mrs %0, cntvct_el0" : "=r"(val));
return val;
}
// 2. Cache satırını prefetch
static inline void prefetch(const void *addr) {
asm volatile("prfm pldl1keep, [%0]" : : "r"(addr));
}
// 3. Memory barrier
static inline void dmb_sy(void) {
asm volatile("dmb sy" ::: "memory");
// "memory" clobber: derleyiciye bellek operasyonlarını
// bu barrier etrafında yeniden sıralama
}
// 4. CRC32 donanım hızlandırması
static inline uint32_t crc32_hw(uint32_t crc, uint64_t data) {
asm volatile("crc32x %w0, %w0, %1"
: "+r"(crc)
: "r"(data));
return crc;
}
// 5. Atomic compare-and-swap (LDXR/STXR)
static inline int cas64(uint64_t *ptr, uint64_t old, uint64_t new_val) {
uint64_t tmp;
int res;
asm volatile(
"1: ldxr %0, [%2] \n"
" cmp %0, %3 \n"
" bne 2f \n"
" stxr %w1, %4, [%2] \n"
" cbnz %w1, 1b \n"
"2: \n"
: "=&r"(tmp), "=&r"(res)
: "r"(ptr), "r"(old), "r"(new_val)
: "cc", "memory"
);
return (res == 0);
}
05 NEON basics
NEON, AArch64'ün Advanced SIMD (Single Instruction Multiple Data) uzantısıdır. 128-bit vektör registerlarında tek seferde birden fazla veri elemanını paralel işler.
Vektör register veri tipleri
v0 (128-bit, q0 olarak da yazılır)
┌────────────────────────────────────────────────────────────────┐
│ 127 0 │
└────────────────────────────────────────────────────────────────┘
Aynı register farklı veri tipleri olarak görülebilir:
v0.16b → 16 × 8-bit unsigned byte
v0.8b → 8 × 8-bit (64-bit alt yarı)
v0.8h → 8 × 16-bit unsigned halfword
v0.4h → 4 × 16-bit (64-bit alt yarı)
v0.4s → 4 × 32-bit unsigned word
v0.2s → 2 × 32-bit (64-bit alt yarı)
v0.2d → 2 × 64-bit doubleword
v0.1d → 1 × 64-bit (64-bit alt yarı)
Temel vektör yükleme / depolama
// Belirli sayıda eleman yükle
ld1 {v0.4s}, [x0] // 4 × 32-bit, tek register
ld1 {v0.4s, v1.4s}, [x0] // 8 × 32-bit, iki register, interleaved
ld2 {v0.4s, v1.4s}, [x0] // de-interleaved çift (LRLRLRLR → LL RR)
ld4 {v0.8b-v3.8b}, [x0] // 4-way de-interleave (RGBA → R,G,B,A)
// Store
st1 {v0.16b}, [x0]
st1 {v0.4s, v1.4s}, [x0], #32 // post-increment
// Tek eleman yükle (broadcast için)
ld1r {v0.4s}, [x0] // [x0]'daki 32-bit'i 4 lane'e çoğalt
Temel NEON aritmetik
// Vektör toplama
add v0.4s, v1.4s, v2.4s // v0 = v1 + v2 (4 × 32-bit paralel)
add v0.8h, v1.8h, v2.8h // 8 × 16-bit paralel
// Çarpma-toplama (FMA)
fmla v0.4s, v1.4s, v2.4s // v0 += v1 * v2 (float)
mla v0.4s, v1.4s, v2.4s // v0 += v1 * v2 (integer)
// Scalar ile çarp (lane broadcast)
fmul v0.4s, v1.4s, v2.s[0] // v1'i v2'nin lane-0 ile çarp
// Karşılaştırma
cmeq v0.4s, v1.4s, v2.4s // v0 = (v1 == v2) ? 0xFFFFFFFF : 0
cmgt v0.4s, v1.4s, v2.4s // v0 = (v1 > v2) ? 0xFFFFFFFF : 0
// Yatay toplama (horizontal)
addv s0, v1.4s // s0 = v1[0]+v1[1]+v1[2]+v1[3]
faddp v0.4s, v1.4s, v2.4s // pairwise add
06 NEON intrinsics
NEON intrinsics, assembly yazmadan SIMD işlemlerini C/C++ kodunda kullanmayı sağlayan arm_neon.h başlık dosyasının fonksiyonlarıdır.
Temel intrinsic tipleri ve fonksiyonlar
#include <arm_neon.h>
// Veri tipleri:
// int8x8_t, int8x16_t, uint8x8_t, uint8x16_t
// int16x4_t, int16x8_t, uint16x4_t, uint16x8_t
// int32x2_t, int32x4_t, float32x2_t, float32x4_t
// int64x1_t, int64x2_t, float64x1_t, float64x2_t
// Vektör doldurma
float32x4_t a = vdupq_n_f32(1.0f); // {1,1,1,1}
int32x4_t b = vdupq_n_s32(0); // {0,0,0,0}
// Yükleme
float32x4_t v = vld1q_f32(ptr); // 4 × float yükle
int16x8_t w = vld1q_s16(ptr); // 8 × int16 yükle
// Depolama
vst1q_f32(ptr, v); // 4 × float yaz
// Toplama
float32x4_t c = vaddq_f32(a, b); // a + b (lane-wise)
int32x4_t d = vaddq_s32(x, y);
// Çarpma
float32x4_t e = vmulq_f32(a, b);
// Scalar çarpma
float32x4_t f = vmulq_n_f32(a, 2.5f);
// FMA: a = a + b*c
float32x4_t result = vfmaq_f32(a, b, c); // a += b*c
4x4 matrix-vector product (NEON intrinsics)
// 4x4 float matrix ile 4-element vector çarpımı
void mat4_vec4_mul(const float *mat, const float *vec, float *out) {
// Vektörü 4 lane'e yükle
float32x4_t v = vld1q_f32(vec);
// Matrix sütunlarını yükle
float32x4_t m0 = vld1q_f32(mat + 0);
float32x4_t m1 = vld1q_f32(mat + 4);
float32x4_t m2 = vld1q_f32(mat + 8);
float32x4_t m3 = vld1q_f32(mat + 12);
// Her sütunu scalar ile çarp ve topla
float32x4_t res = vmulq_laneq_f32(m0, v, 0); // m0 * v[0]
res = vfmaq_laneq_f32(res, m1, v, 1); // += m1 * v[1]
res = vfmaq_laneq_f32(res, m2, v, 2); // += m2 * v[2]
res = vfmaq_laneq_f32(res, m3, v, 3); // += m3 * v[3]
vst1q_f32(out, res);
}
// 4 FMA = 4 × 4 = 16 işlem paralel
RGB → Grayscale dönüşümü (NEON)
// Y = 0.299R + 0.587G + 0.114B
void rgb_to_gray_neon(const uint8_t *rgb, uint8_t *gray, int pixels) {
// Katsayılar Q8 fixed-point: 0.299*256≈77, 0.587*256≈150, 0.114*256≈29
const uint8x8_t kr = vdup_n_u8(77);
const uint8x8_t kg = vdup_n_u8(150);
const uint8x8_t kb = vdup_n_u8(29);
for (int i = 0; i < pixels; i += 8) {
// 8 pixel RGB'yi de-interleave ederek yükle
uint8x8x3_t pixel = vld3_u8(rgb + i * 3);
uint16x8_t sum;
sum = vmull_u8(pixel.val[0], kr); // R * 77
sum = vmlal_u8(sum, pixel.val[1], kg); // + G * 150
sum = vmlal_u8(sum, pixel.val[2], kb); // + B * 29
// 16-bit → 8-bit (sağa kaydırarak normalize)
uint8x8_t result = vshrn_n_u16(sum, 8);
vst1_u8(gray + i, result);
}
}
07 SVE — Scalable Vector Extension
SVE, ARMv8.2-A ile tanıtılan ölçeklenebilir vektör uzantısıdır. Sabit 128-bit NEON'un aksine SVE vektör uzunluğu donanıma göre 128-bit'ten 2048-bit'e kadar değişir.
SVE vs NEON temel farklar
| Özellik | NEON | SVE |
|---|---|---|
| Vektör genişliği | 128-bit sabit | 128–2048-bit, donanıma göre |
| Predicate register | Yok | p0–p15 (per-lane maskeleme) |
| Gather/Scatter | Sınırlı | Doğrudan ld1 gather desteği |
| Loop yönetimi | Manuel unroll | whilelt ile otomatik |
| Kayıt sayısı | v0–v31 (32) | z0–z31 (32 geniş) |
Temel SVE döngüsü
// SVE: SIMD uzunluğundan bağımsız vektör toplama döngüsü
// void add_vectors(float *a, float *b, float *c, int n)
.globl add_vectors_sve
add_vectors_sve:
// x0=a, x1=b, x2=c, w3=n
mov x4, #0 // i = 0
.loop:
// Kalan eleman sayısına göre predicate hesapla
// p0 = (i + lane < n) olan lane'ler aktif
whilelt p0.s, w4, w3 // p0 = {i
SVE intrinsics (C)
#include <arm_sve.h>
void axpy_sve(float *y, const float *x, float a, int n) {
// y[i] += a * x[i]
int64_t i = 0;
svbool_t pg;
while (i < n) {
pg = svwhilelt_b32(i, n); // predicate mask
svfloat32_t vx = svld1_f32(pg, x + i); // x[i..]
svfloat32_t vy = svld1_f32(pg, y + i); // y[i..]
vy = svmla_n_f32_m(pg, vy, vx, a); // vy += vx * a
svst1_f32(pg, y + i, vy); // store
i += svcntw(); // VL/4 ilerle (float sayısı)
}
}
08 Profiling ile doğrulama
Optimizasyonun işe yarayıp yaramadığını nesnel olarak görmek için donanım performans sayaçlarını kullanmak gerekir. perf stat ve PMU sayaçları bu iş için biçilmiş kaftandır.
perf stat temel kullanım
# Temel sayaçlarla çalıştır
perf stat ./my_program
# Çıktı örneği:
# Performance counter stats for './my_program':
# 1,234,567,890 cycles # 1.23 GHz
# 2,456,789,123 instructions # 1.99 insn/cycle (IPC)
# 12,345,678 cache-misses # 1.2% of all cache refs
# 1,023,456 branch-misses # 0.5% of all branches
# 0.987654 seconds elapsed
# Özel sayaçlar
perf stat -e cycles,instructions,L1-dcache-misses,L1-icache-misses \
./my_program
# SIMD verimliliği için
perf stat -e cycles,instructions,fp_scale_ops_spec,fp_fixed_ops_spec \
./my_program
PMU sayaçlarını C'den okuma
#include <sys/ioctl.h>
#include <linux/perf_event.h>
// Basit cycle counter wrapper
static int perf_fd = -1;
void perf_init(void) {
struct perf_event_attr pe = {
.type = PERF_TYPE_HARDWARE,
.config = PERF_COUNT_HW_CPU_CYCLES,
.disabled = 1,
.exclude_kernel = 1,
.exclude_hv = 1,
};
perf_fd = syscall(298, &pe, 0, -1, -1, 0); // SYS_perf_event_open
}
void perf_start(void) {
ioctl(perf_fd, PERF_EVENT_IOC_RESET, 0);
ioctl(perf_fd, PERF_EVENT_IOC_ENABLE, 0);
}
uint64_t perf_stop(void) {
uint64_t count;
ioctl(perf_fd, PERF_EVENT_IOC_DISABLE, 0);
read(perf_fd, &count, sizeof(count));
return count;
}
// Kullanım:
perf_init();
perf_start();
my_function(data, N);
uint64_t cycles = perf_stop();
printf("Cycles: %lu, Per-element: %.2f\n", cycles, (double)cycles/N);
AArch64 donanım sayaçları doğrudan okuma
// User-space cycle counter (PMU erişimi açıksa)
static inline uint64_t armv8_pmccntr_read(void) {
uint64_t val;
asm volatile("mrs %0, pmccntr_el0" : "=r"(val));
return val;
}
// pmccntr_el0 user-space erişimi için:
// echo 1 > /sys/bus/event_source/devices/armv8_pmuv3/userspace_access
// PMCR_EL0.E = 1, PMCNTENSET_EL0.C = 1 (kernel driver yapar)
09 Pratik: memcpy ve dot product karşılaştırma
Scalar ve NEON implementasyonlarını yan yana yazıp perf ile throughput ölçümü yapalım.
Dot product: scalar vs NEON
// dotprod.c — scalar ve NEON dot product karşılaştırması
#include <arm_neon.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#define N (1 << 20) // 1M float
// Scalar implementasyon
float dot_scalar(const float *a, const float *b, int n) {
float sum = 0.0f;
for (int i = 0; i < n; i++)
sum += a[i] * b[i];
return sum;
}
// NEON implementasyon
float dot_neon(const float *a, const float *b, int n) {
float32x4_t acc = vdupq_n_f32(0.0f);
int i;
for (i = 0; i <= n - 4; i += 4) {
float32x4_t va = vld1q_f32(a + i);
float32x4_t vb = vld1q_f32(b + i);
acc = vfmaq_f32(acc, va, vb); // acc += va * vb
}
// Yatay toplama: 4 lane'i topla
float32x2_t sum2 = vadd_f32(vget_high_f32(acc), vget_low_f32(acc));
float32x2_t sum1 = vpadd_f32(sum2, sum2);
float result = vget_lane_f32(sum1, 0);
// Kalan elemanlar (n % 4)
for (; i < n; i++)
result += a[i] * b[i];
return result;
}
static uint64_t get_ns(void) {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1e9 + ts.tv_nsec;
}
int main(void) {
float *a = aligned_alloc(64, N * sizeof(float));
float *b = aligned_alloc(64, N * sizeof(float));
for (int i = 0; i < N; i++) { a[i] = (float)i; b[i] = 1.0f / N; }
const int ITERS = 100;
uint64_t t0, t1;
// Scalar
t0 = get_ns();
float r1 = 0;
for (int k = 0; k < ITERS; k++) r1 = dot_scalar(a, b, N);
t1 = get_ns();
double scalar_ms = (t1 - t0) / 1e6 / ITERS;
// NEON
t0 = get_ns();
float r2 = 0;
for (int k = 0; k < ITERS; k++) r2 = dot_neon(a, b, N);
t1 = get_ns();
double neon_ms = (t1 - t0) / 1e6 / ITERS;
printf("Scalar: %.3f ms (result=%.4f)\n", scalar_ms, r1);
printf("NEON: %.3f ms (result=%.4f)\n", neon_ms, r2);
printf("Speedup: %.2fx\n", scalar_ms / neon_ms);
// Throughput: float/sn
double neon_gflops = (double)N * 2 / (neon_ms * 1e-3) / 1e9;
printf("NEON GFLOP/s: %.2f\n", neon_gflops);
free(a); free(b);
return 0;
}
Derleme ve çalıştırma
# AArch64 için derle
aarch64-linux-gnu-gcc -O3 -march=armv8-a+simd \
-ftree-vectorize \
dotprod.c -o dotprod
# Cross-compile sonrası QEMU veya cihazda çalıştır
./dotprod
# Örnek çıktı (Cortex-A55):
# Scalar: 2.847 ms (result=0.5000)
# NEON: 0.731 ms (result=0.5000)
# Speedup: 3.89x
# NEON GFLOP/s: 2.87
# perf ile doğrula
perf stat -e cycles,instructions,L1-dcache-misses ./dotprod
memcpy throughput karşılaştırması
// NEON memcpy (128-bit lane kullanımı)
void memcpy_neon(void *dst, const void *src, size_t n) {
uint8_t *d = (uint8_t *)dst;
const uint8_t *s = (const uint8_t *)src;
// 64-byte (4 × 128-bit) blok kopyalama
size_t blocks = n / 64;
for (size_t i = 0; i < blocks; i++) {
uint8x16x4_t data = vld1q_u8_x4(s); // 64 byte yükle
vst1q_u8_x4(d, data); // 64 byte yaz
s += 64; d += 64;
}
// Kalan byte'lar
size_t rem = n % 64;
if (rem) memcpy(d, s, rem);
}
// Karşılaştırma:
// libc memcpy (glibc aarch64): ~8–12 GB/s (NEON + prefetch + unroll)
// Basit NEON: ~4–6 GB/s
// Scalar: ~1–2 GB/s
ARMv8 NEON, 4× float'ı paralel işleyerek döngü başına 4 FMA üretir. Gerçek dünya kazanımı 2–4× arasında değişir; bellek bant genişliği, cache missi ve branch prediction çoğunlukla SIMD verimliliğini sınırlayan faktörlerdir. SVE ise donanım bağımsız vektör genişliğiyle kod yeniden derlenmeden daha geniş SIMD'den otomatik faydalanmayı sağlar.