簡體   English   中英

memcpy 勝過 SIMD 內在函數

[英]memcpy beats SIMD intrinsics

當 ARM 設備上可以使用 NEON 向量指令時,我一直在尋找復制各種數據量的快速方法。

我做了一些基准測試,並得到了一些有趣的結果。 我試圖理解我在看什么。

我有四個版本來復制數據:

1. 基線

逐個復制元素:

for (int i = 0; i < size; ++i)
{
    copy[i] = orig[i];
}

2. 霓虹燈

此代碼將四個值加載到一個臨時寄存器中,然后將該寄存器復制到輸出中。

因此,負載的數量減少了一半。 可能有一種方法可以跳過臨時寄存器並將負載減少四分之一,但我還沒有找到方法。

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(&copy2[i], tmp); // copy 4 elements from tmp SIMD register
}

3. 步進memcpy ,

使用memcpy ,但一次復制 4 個元素。 這是為了與 NEON 版本進行比較。

for (int i = 0; i < size; i+=4)
{
    memcpy(orig+i, copy3+i, 4);
}

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(&copy2[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;
}

注意:此代碼在錯誤的方向上使用了 memcpy。 它應該是memcpy(dest, src, num_bytes)

因為“正常的 memcpy”測試最后發生,與其他測試相比,大量數量級的加速將通過死代碼消除來解釋。 優化器看到在最后一次 memcpy 之后沒有使用orig ,所以它消除了 memcpy。

編寫可靠基准測試的一個好方法是使用Benchmark框架,並使用他們的benchmark::DoNotOptimize(x)函數防止死代碼消除。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM