![](/img/trans.png)
[英]NEON code faster than standard C code on armeabi-v7a but slower on arm64-v8a
[英]Why ARM NEON code slower than native C code
我正在 ARM NEON(ARM8-A 架構)中實現去量化操作。 但我遇到了一個奇怪的問題,即 ARM NEON 版本(11 毫秒)比 C 版本(4.75 毫秒)慢。
這是我們的代碼,
NEON ASM 代碼:
.arch armv8-a+crc
.file "dequantize.c"
.text
.align 2
.global retinaface_dequantize_v3
.type retinaface_dequantize_v3, %function
retinaface_dequantize_v3:
.LFB3836:
.cfi_startproc
mov x6, x0 // ivtmp.39, in_cls
add x0, x0, 266240 // _214, in_cls,
add x0, x0, 2560 // _214, _214,
adrp x7, .LC0 // tmp294,
ldr q21, [x7, #:lo12:.LC0] // tmp222,
adrp x7, .LC1 // tmp295,
ldr q20, [x7, #:lo12:.LC1] // tmp227,
adrp x7, .LC2 // tmp296,
ldr q19, [x7, #:lo12:.LC2] // tmp229,
adrp x7, .LC3 // tmp297,
ldr q1, [x7, #:lo12:.LC3] // tmp246,
adrp x7, .LC4 // tmp298,
ldr q0, [x7, #:lo12:.LC4] // tmp248,
.L2:
ldr q28, [x6] // _108,* ivtmp.39
ldr q27, [x6, 16] // _107,
ldr q26, [x1] // _106,* ivtmp.41
ldr q25, [x1, 16] // _105,
ldr q24, [x1, 32] // _104,
ldr q23, [x1, 48] // _103,
ldr q22, [x2] // _102,* ivtmp.45
ldr q18, [x2, 16] // _101,
ldr q17, [x2, 32] // _100,
ldr q16, [x2, 48] // _99,
ldr q7, [x2, 64] // _98,
ldr q6, [x2, 80] // _97,
ldr q5, [x2, 96] // _96,
ldr q4, [x2, 112] // _95,
ldr q3, [x2, 128] // _94,
ldr q2, [x2, 144] // _93,
fmulx v28.2d, v28.2d, v21.2d // tmp221, _108, tmp222
fmulx v27.2d, v27.2d, v21.2d // tmp224, _107, tmp222
fmulx v26.2d, v26.2d, v19.2d // tmp228, tmp226, tmp229
fmulx v25.2d, v25.2d, v19.2d // tmp233, tmp231, tmp229
fmulx v24.2d, v24.2d, v19.2d // tmp238, tmp236, tmp229
fmulx v23.2d, v23.2d, v19.2d // tmp243, tmp241, tmp229
fmulx v22.2d, v22.2d, v0.2d // tmp247, tmp245, tmp248
fmulx v18.2d, v18.2d, v0.2d // tmp252, tmp250, tmp248
fmulx v17.2d, v17.2d, v0.2d // tmp257, tmp255, tmp248
fmulx v16.2d, v16.2d, v0.2d // tmp262, tmp260, tmp248
fmulx v7.2d, v7.2d, v0.2d // tmp267, tmp265, tmp248
fmulx v6.2d, v6.2d, v0.2d // tmp272, tmp270, tmp248
fmulx v5.2d, v5.2d, v0.2d // tmp277, tmp275, tmp248
fmulx v4.2d, v4.2d, v0.2d // tmp282, tmp280, tmp248
fmulx v3.2d, v3.2d, v0.2d // tmp287, tmp285, tmp248
fmulx v2.2d, v2.2d, v0.2d // tmp292, tmp290, tmp248
fadd v26.2d, v26.2d, v20.2d // tmp226, _106, tmp227
fadd v25.2d, v25.2d, v20.2d // tmp231, _105, tmp227
fadd v24.2d, v24.2d, v20.2d // tmp236, _104, tmp227
fadd v23.2d, v23.2d, v20.2d // tmp241, _103, tmp227
fadd v22.2d, v22.2d, v1.2d // tmp245, _102, tmp246
fadd v18.2d, v18.2d, v1.2d // tmp250, _101, tmp246
fadd v17.2d, v17.2d, v1.2d // tmp255, _100, tmp246
fadd v16.2d, v16.2d, v1.2d // tmp260, _99, tmp246
fadd v7.2d, v7.2d, v1.2d // tmp265, _98, tmp246
fadd v6.2d, v6.2d, v1.2d // tmp270, _97, tmp246
fadd v5.2d, v5.2d, v1.2d // tmp275, _96, tmp246
fadd v4.2d, v4.2d, v1.2d // tmp280, _95, tmp246
fadd v3.2d, v3.2d, v1.2d // tmp285, _94, tmp246
fadd v2.2d, v2.2d, v1.2d // tmp290, _93, tmp246
str q28, [x3] // tmp221,* ivtmp.55
str q27, [x3, 16] // tmp224,
str q26, [x4] // tmp228,* ivtmp.57
str q25, [x4, 16] // tmp233,
str q24, [x4, 32] // tmp238,
str q23, [x4, 48] // tmp243,
str q22, [x5] // tmp247,* ivtmp.61
str q18, [x5, 16] // tmp252,
str q17, [x5, 32] // tmp257,
str q16, [x5, 48] // tmp262,
str q7, [x5, 64] // tmp267,
str q6, [x5, 80] // tmp272,
str q5, [x5, 96] // tmp277,
str q4, [x5, 112] // tmp282,
str q3, [x5, 128] // tmp287,
str q2, [x5, 144] // tmp292,
add x6, x6, 32 // ivtmp.39, ivtmp.39,
add x1, x1, 64 // ivtmp.41, ivtmp.41,
add x2, x2, 160 // ivtmp.45, ivtmp.45,
add x3, x3, 32 // ivtmp.55, ivtmp.55,
add x4, x4, 64 // ivtmp.57, ivtmp.57,
add x5, x5, 160 // ivtmp.61, ivtmp.61,
cmp x6, x0 // ivtmp.39, _214
bne .L2 //,
// dequantize.c:475: }
ret
.cfi_endproc
.LFE3836:
.size retinaface_dequantize_v3, .-retinaface_dequantize_v3
.section .rodata.cst16,"aM",@progbits,16
.align 4
.LC0:
.word 0
.word 1064304640
.word 0
.word 1064304640
.LC1:
.word 0
.word -1067417600
.word 0
.word -1067417600
.LC2:
.word 536870912
.word 1068027667
.word 536870912
.word 1068027667
.LC3:
.word 0
.word -1067515904
.word 0
.word -1067515904
.LC4:
.word 3758096384
.word 1069039660
.word 3758096384
.word 1069039660
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
C 代碼:
void retinaface_dequantize_v0(uint8_t *in_cls, uint8_t *in_bbox, uint8_t *in_ldm, double *out_cls, double *out_bbox, double *out_ldm, uint64_t length)
{
double const dequan_cls = 0.00390625;
double const dequan_bbox = 0.048454854637384415;
double const dequan_ldm = 0.0947292372584343;
const uint8_t bbox_minus = 132;
const uint8_t ldm_minus = 124;
for (int64_t i = 16799;i>=0;i--)
{
//cls
out_cls[i*2] = dequan_cls * (uint8_t)in_cls[i*2];
out_cls[i*2+1] = dequan_cls * (uint8_t)in_cls[i*2+1];
//bbox
out_bbox[i*4] = dequan_bbox * ((uint8_t)in_bbox[i*4] - bbox_minus);
out_bbox[i*4+1] = dequan_bbox * ((uint8_t)in_bbox[i*4+1] - bbox_minus);
out_bbox[i*4+2] = dequan_bbox * ((uint8_t)in_bbox[i*4+2] - bbox_minus);
out_bbox[i*4+3] = dequan_bbox * ((uint8_t)in_bbox[i*4+3] - bbox_minus);
//ldm
out_ldm[i*10] = dequan_ldm * ((uint8_t)in_ldm[i*10] - ldm_minus);
out_ldm[i*10+1] = dequan_ldm * ((uint8_t)in_ldm[i*10+1] - ldm_minus);
out_ldm[i*10+2] = dequan_ldm * ((uint8_t)in_ldm[i*10+2] - ldm_minus);
out_ldm[i*10+3] = dequan_ldm * ((uint8_t)in_ldm[i*10+3] - ldm_minus);
out_ldm[i*10+4] = dequan_ldm * ((uint8_t)in_ldm[i*10+4] - ldm_minus);
out_ldm[i*10+5] = dequan_ldm * ((uint8_t)in_ldm[i*10+5] - ldm_minus);
out_ldm[i*10+6] = dequan_ldm * ((uint8_t)in_ldm[i*10+6] - ldm_minus);
out_ldm[i*10+7] = dequan_ldm * ((uint8_t)in_ldm[i*10+7] - ldm_minus);
out_ldm[i*10+8] = dequan_ldm * ((uint8_t)in_ldm[i*10+8] - ldm_minus);
out_ldm[i*10+9] = dequan_ldm * ((uint8_t)in_ldm[i*10+9] - ldm_minus)
}
}
該代碼看起來像一個好的編譯器將完全矢量化的代碼。 反匯編代碼以查看編譯器生成了什么。
如果沒有,請使用 NEON 編譯器內在函數來生成完全矢量化的代碼,而不是匯編程序。 使用編譯器內在函數的優點是編譯器可以重新排序 ARM 和 ARM NEON 指令,以防止流水線停頓,並最大化並發執行。 它做得很好。
您的代碼最明顯的問題:您需要將算術運算和 ARM 匯編器與 vld1/vst1 加載/存儲指令交錯,以防止管道在等待連續加載/存儲完成時停止。 循環頭部的 ARM 指令需要在初始加載指令之間盡可能下推,並行執行,NEON 數學指令需要分散在初始加載指令之間,以便它們同時執行停止 memory 讀取(和寫入)。
編譯器喜歡做這種指令調度,並且比最專業的匯編編碼器手工做的還要好。 專家將從編譯器生成的代碼開始,並使用(非常昂貴且難以使用)ARM 分析工具來查找編譯器錯誤調度操作的位置。 並且很高興能比編寫良好的 C/C++ 代碼提高 5% 或 10%。
使用內在函數時的另一個好處是,您可以告訴編譯器您正在使用哪個特定的 ARM 處理器,並且編譯器將為該特定處理器優化調度。 因此,當您從 a52 遷移到 a76 或 Arm9 處理器時,您最終不會從頭開始重寫代碼。
fwiw,所有 vld1 數據讀取都將是緩存未命中。 合理優化的代碼應該花費 99% 的時間等待 memory 讀取,幾乎所有代碼的 rest 在等待讀取完成時同時執行。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.