[英]memcpy beats SIMD intrinsics
當 ARM 設備上可以使用 NEON 向量指令時,我一直在尋找復制各種數據量的快速方法。
我做了一些基准測試,並得到了一些有趣的結果。 我試圖理解我在看什么。
我有四個版本來復制數據:
逐個復制元素:
for (int i = 0; i < size; ++i)
{
copy[i] = orig[i];
}
此代碼將四個值加載到一個臨時寄存器中,然后將該寄存器復制到輸出中。
因此,負載的數量減少了一半。 可能有一種方法可以跳過臨時寄存器並將負載減少四分之一,但我還沒有找到方法。
int32x4_t tmp;
for (int i = 0; i < size; i += 4)
{
tmp = vld1q_s32(orig + i); // load 4 elements to tmp SIMD register
vst1q_s32(©2[i], tmp); // copy 4 elements from tmp SIMD register
}
memcpy
, 使用memcpy
,但一次復制 4 個元素。 這是為了與 NEON 版本進行比較。
for (int i = 0; i < size; i+=4)
{
memcpy(orig+i, copy3+i, 4);
}
memcpy
使用具有完整數據量的memcpy
。
memcpy(orig, copy4, size);
我使用2^16
值的基准測試給出了一些令人驚訝的結果:
1. Baseline time = 3443[µs]
2. NEON time = 1682[µs]
3. memcpy (stepped) time = 1445[µs]
4. memcpy time = 81[µs]
NEON 時間的加速是可以預期的,但是更快的memcpy
時間讓我感到驚訝。 而4
的時間更是如此。
為什么memcpy
做得這么好? 它是否在引擎蓋下使用 NEON? 或者是否有我不知道的高效內存復制指令?
這個問題討論了 NEON 與memcpy()
。 但是我覺得答案沒有充分探討為什么 ARM memcpy
實現運行得如此好
完整的代碼清單如下:
#include <arm_neon.h>
#include <vector>
#include <cinttypes>
#include <iostream>
#include <cstdlib>
#include <chrono>
#include <cstring>
int main(int argc, char *argv[]) {
int arr_size;
if (argc==1)
{
std::cout << "Please enter an array size" << std::endl;
exit(1);
}
int size = atoi(argv[1]); // not very C++, sorry
std::int32_t* orig = new std::int32_t[size];
std::int32_t* copy = new std::int32_t[size];
std::int32_t* copy2 = new std::int32_t[size];
std::int32_t* copy3 = new std::int32_t[size];
std::int32_t* copy4 = new std::int32_t[size];
// Non-neon version
std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now();
for (int i = 0; i < size; ++i)
{
copy[i] = orig[i];
}
std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now();
std::cout << "Baseline time = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[µs]" << std::endl;
// NEON version
begin = std::chrono::steady_clock::now();
int32x4_t tmp;
for (int i = 0; i < size; i += 4)
{
tmp = vld1q_s32(orig + i); // load 4 elements to tmp SIMD register
vst1q_s32(©2[i], tmp); // copy 4 elements from tmp SIMD register
}
end = std::chrono::steady_clock::now();
std::cout << "NEON time = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[µs]" << std::endl;
// Memcpy example
begin = std::chrono::steady_clock::now();
for (int i = 0; i < size; i+=4)
{
memcpy(orig+i, copy3+i, 4);
}
end = std::chrono::steady_clock::now();
std::cout << "memcpy time = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[µs]" << std::endl;
// Memcpy example
begin = std::chrono::steady_clock::now();
memcpy(orig, copy4, size);
end = std::chrono::steady_clock::now();
std::cout << "memcpy time = " << std::chrono::duration_cast<std::chrono::microseconds>(end - begin).count() << "[µs]" << std::endl;
return 0;
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.