簡體   English   中英

計算兩個 _m128i SIMD 向量之間的匹配字節數

[英]Count number of matching bytes between two _m128i SIMD vectors

我正在開發一種生物信息學工具,並且正在嘗試使用 SIMD 來提高其速度。

給定兩個長度為 16 的字符 arrays,我需要快速計算字符串匹配的索引數 例如,以下兩個字符串“TTTTTTTTTTTTTTTT”和“AAAAGGGGTTTTCCCC”從第 9 位到第 12 位(“TTTT”)匹配,因此 output 應為 4。

如下面的 function foo 所示(工作正常但速度慢),我將 seq1 和 seq2 中的每個字符打包到 __m128i 變量 s1 和 s2 中,並使用 _mm_cmpeq_epi8 同時比較每個 position。 然后,使用 popcnt128(來自 Marat Dukhan 的快速計算 __m128i 寄存器中設置的位數)將匹配位數相加。

float foo(char* seq1, char* seq2) {
    __m128i s1, s2, ceq;
    int match;
    s1 =  _mm_load_si128((__m128i*)(seq1));
    s2 =  _mm_load_si128((__m128i*)(seq2));
    ceq = _mm_cmpeq_epi8(s1, s2);
    match = (popcnt128(ceq)/8);
    return match;
}

盡管 Marat Dukhan 的 popcnt128 比天真的添加 __m128i 中的每一位要快得多,但 __popcnt128() 是 function 中最慢的瓶頸,約占計算速度的 80%。 所以,我想提出一個 popcnt128 的替代方案。


我試圖將__m128i ceq解釋為字符串,並將其用作預先計算的查找表的鍵,該查找表將字符串映射到總位數。 如果 char 數組是可散列的,我可以做類似的事情

union{__m128i ceq; char c_arr[16];}
match = table[c_arr] // table = unordered map

如果我嘗試對字符串執行類似的操作(即union{__m128i ceq; string s;}; ),我會收到以下錯誤消息“::()' 被隱式刪除,因為默認定義格式不正確”。 當我嘗試其他事情時,我遇到了分段錯誤。

有什么方法可以告訴編譯器將 __m128i 讀取為字符串,以便我可以直接使用 __m128i 作為 unordered_map 的鍵? 我不明白為什么它不應該工作,因為字符串是一個連續的字符數組,可以自然地用 __m128i 表示。 但我無法讓它工作,也無法在線找到任何解決方案。

您可能正在為更長的序列、多個 SIMD 數據向量執行此操作。 在這種情況下,您可以在一個向量中累積計數,您只在最后求和。 單獨計算每個向量的效率要低得多。

請參閱如何使用 SIMD 計算字符出現次數- 而不是_mm256_set1_epi8(c); 要搜索特定字符,請從另一個字符串加載。 做其他所有事情,包括
counts = _mm_sub_epi8(counts, _mm_cmpeq_epi8(s1, s2));
在內部循環中,循環展開。 (比較結果是 integer 0 / -1,因此減去它會將 0 或 1 添加到另一個向量。)在 256 次迭代后有溢出的風險,因此最多 255 次。該鏈接問題使用 AVX2,但__m128i版本這些內在函數中的僅需要 SSE2。 (當然,AVX2 可以讓每個向量指令完成兩倍的工作量。)

使用_mm_sad_epu8(v, _mm_setzero_si128()); 然后累積到另一個計數向量中。 同樣,這一切都在鏈接的問答中的代碼中,所以只需復制/粘貼它並將另一個字符串的負載添加到內部循環中,而不是使用廣播常量。


對於單個向量:

您不需要計算__m128i中的所有位; 通過將每個元素的 1 位提取到標量 integer 來利用每個字節中的所有 8 位都相同的事實。 (與其他一些 SIMD ISA 不同,x86 SIMD 可以有效地做到這一點)

    count = __builtin_popcnt(_mm_movemask_epi8(cmp_result));

另一個可能的選項是針對 0 的psadbw (比較結果中字節的 hsum),但這需要一個 qword 一半的最后 hsum 步驟,因此這將比 HW popcnt 更糟。 但是,如果您不能使用-mpopcnt進行編譯,那么值得考慮是否需要僅使用 SSE2 的基線 x86-64。 (此外,您需要在 psadbw 之前取反,或將總和縮小 1/255...)

(請注意, psadbw 策略基本上是我在答案的第一部分中描述的,但是僅針對單個向量,沒有利用將多個計數廉價地添加到一個向量累加器中的能力。)

如果您確實需要將結果作為float ,那么這會使psadbw策略不那么糟糕:您可以始終將值保留在 SIMD 向量中,使用_mm_cvtepi32_ps對水平和結果進行打包轉換(甚至比cvtsi2ss int->浮點標量轉換)。 _mm_cvtps_f32是免費的; 標量浮點數只是 XMM 寄存器的低元素。

但說真的,你現在真的需要一個 integer 算作float 你不能至少等到你得到所有向量的總和,或者保留它 integer 嗎?

-mpopcntgcc -msse4.2-march=native隱含在小於 10 年的任何東西上。 Core 2 缺少硬件 popcnt,但 Nehalem 為 Intel 提供了它。

暫無
暫無

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

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