簡體   English   中英

C/C++ fast 兩個系列的絕對區別

[英]C/C++ fast absolute difference between two series

我有興趣生成高效的 c/c++ 代碼來獲取兩個時間序列之間的差異。 更精確:時間序列值存儲為 uint16_t arrays,長度固定且等長 == 128。

我很擅長純 c 以及純 c++ 實現。 我的代碼示例在 c++

我的意圖是:

Let A,B and C be discrete time series of length l with a value-type of uint16_t.
Vn[n<l]: Cn = |An - Bn|;

我能想到的偽代碼:

for index i:
 if a[i] > b[i]:
    c[i] = a[i] - b[i]
 else:
    c[i] = b[i] - a[i]

或者在 c/c++ 中

for(uint8_t idx = 0; idx < 128; idx++){
    c[i] = a[i] > b[i] ? a[i] - b[i] : b[i] - a[i];
}

但我真的不喜歡循環中的 if/else 語句。 我可以接受循環——這可以由編譯器展開。 有點像:

void getBufDiff(const uint16_t (&a)[], const uint16_t (&b)[], uint16_t (&c)[]) {
#pragma unroll 16
    for (uint8_t i = 0; i < 128; i++) {
        c[i] = a[i] > b[i] ? a[i] - b[i] : b[i] - a[i];
    }
#end pragma
}

我正在尋找的是一個“魔術代碼”,它可以加速 if/else 並讓我得到兩個無符號值之間的絕對差值。

我可以接受 +/- 1 的精度(以防發生一些位魔術)。 我也同意更改數據類型以獲得更快的結果。 而且我也可以放棄其他東西的循環。

所以像:

void getBufDiff(const uint16_t (&a)[], const uint16_t (&b)[], uint16_t (&c)[]) {
#pragma unroll 16
    for (uint8_t i = 0; i < 128; i++) {
        c[i] = magic_code_for_abs_diff(a[i],b[i]);
    }
#end pragma
}

嘗試對這兩個值進行異或運算。 僅針對其中一種情況給出正確的結果。

編輯 2:

在我的筆記本電腦上對不同的方法進行了快速測試。

對於 250000000 個條目,這是性能(256 輪):

c[i] = a[i] > b[i] ? a[i] - b[i] : b[i] - a[i];  ~500ms
c[i] = std::abs(a[i] - b[i]);                    ~800ms
c[i] = ((a[i] - b[i]) + ((a[i] - b[i]) >> 15)) ^ (i >> 15) ~425ms
uint16_t tmp = (a[i] - b[i]); c[i] = tmp * ((tmp > 0) - (tmp < 0)); ~600ms
uint16_t ret[2] = { a[i] - b[i], b[i] - a[i] };c[i] = ret[a[i] < b[i]] ~900ms
c[i] = ((a[i] - b[i]) >> 31 | 1) * (a[i] - b[i]); ~375ms
c[i] = ((a[i] - b[i])) ^ ((a[i] - b[i]) >> 15) ~425ms

您的問題很適合 SIMD。 GCC 可以自動完成,這里是一個簡化的例子: https://godbolt.org/z/36nM8bYYv

void absDiff(const uint16_t* a, const uint16_t* b, uint16_t* __restrict__ c)
{
    for (uint8_t i = 0; i < 16; i++)
        c[i] = a[i] - b[i];
}

請注意,我添加了__restrict__以啟用自動矢量化,否則編譯器必須假設您的 arrays 可能重疊並且使用 SIMD 是不安全的(因為某些寫入可能會改變循環中的未來讀取)。

我一次將其簡化為 16 個,並為了便於說明刪除了絕對值。 生成的程序集是:

    vld1.16 {q9}, [r0]!
    vld1.16 {q11}, [r1]!
    vld1.16 {q8}, [r0]
    vld1.16 {q10}, [r1]
    vsub.i16        q9, q9, q11
    vsub.i16        q8, q8, q10
    vst1.16 {q9}, [r2]!
    vst1.16 {q8}, [r2]
    bx      lr

這意味着它一次從a加載 8 個整數,然后從b加載,重復一次,然后一次執行 8 個減法,然后再次將 8 個值存儲兩次到c中。 比沒有 SIMD 的指令少很多。

當然,它需要進行基准測試以查看這在您的系統上是否真的更快(在您加回絕對值部分后,我建議使用您的?:方法,它不會破壞自動矢量化),但我希望它會快得多。

快速abs (在兩個補整數下)可以實現為(x + (x >> N)) ^ (x >> N)其中 N 是 int - 1 的大小,即在您的情況下為 15。 這是std::abs的可能實現。 你還是可以試試

怪異的回答

由於您寫的是“我可以使用 +/- 1 精度”,因此您可以使用 XOR 解決方案:代替abs(x) ,執行x ^ (x >> 15) 對於負值,這將給出 off-by-1 結果。

如果您想計算負值的正確結果,請使用其他答案( x >> 15校正)。

無論如何,這種 XOR 技巧只有在不可能發生溢出時才有效。 因此,編譯器無法用使用 XOR 的代碼替換abs

嘗試讓編譯器看到 SIMD 指令的條件通道選擇模式,如下所示(偽代碼):

// store a,b to SIMD registers
for(0 to 32)
   a[...] = input[...]
   b[...] = input2[...]

// single type operation, easily parallelizable
for(0 to 32)
   vector1[...] = a[...] - b[...]

// single type operation, easily parallelizable
// maybe better to compute b-a to decrease dependency to first step
// since a and b are already in SIMD registers
for(0 to 32)
   vector2[...] = -vector1[...]

// single type operation, easily parallelizable
// re-use a,b registers, again
for(0 to 32)
   vector3[...] = a[...] < b[...]

// x84 architecture has SIMD instructions for this
// operation is simple, no other calculations inside, just 3 inputs, 1 out
// all operands are registers (at least should be, if compiler works fine)
for(0 to 32)
   vector4[...] = vector3[...] ? vector2[...]:vetor1[...]

如果您編寫基准代碼,我可以將其與其他解決方案進行比較。 但是,對於有問題的第一個基准代碼自動執行相同操作的好的編譯器(或好的編譯器標志)並不重要。

暫無
暫無

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

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