如果要優化主要是完全相同的字符串,你可以使用的x86 SSE/AVX向量指令。您的基本想法看起來很好:只要您檢測到第二個區別,就會中斷。
要查找和計算字符差異,像PCMPEQB
/PMOVMSKB
/test-and-branch這樣的序列可能是好的。 (使用C/C++內部函數來獲取這些向量指令)。當您的向量循環檢測到當前塊中的非零差異時,可以使用位掩碼來查看是否剛剛找到第一個差異,或者如果在同一個塊中發現了兩個差異。
我扔了一個未經測試的,沒有完全充實的AVX2版本的我所描述的。 此代碼假定字符串長度爲32的多個倍數。儘早停止並用清理結尾處理最後一個塊是留給讀者的一個練習。
#include <immintrin.h>
#include <string>
// not tested, and doesn't avoid reading past the end of the string.
// TODO: epilogue to handle the last up-to-31 left-over bytes separately.
bool canChange_avx2_bmi(std::string const& strA, std::string const& strB, char& letter) {
size_t size = strA.size();
if (size != strB.size())
return false;
int diffs = 0;
size_t diffpos = 0;
size_t pos = 0;
do {
uint32_t diffmask = 0;
while(pos < size) {
__m256i vecA = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(& strA[pos]));
__m256i vecB = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(& strB[pos]));
__m256i vdiff = _mm256_cmpeq_epi8(vecA, vecB);
diffmask = _mm256_movemask_epi8(vdiff);
pos += 32;
if (diffmask) break; // gcc makes worse code if you include && !diffmask in the while condition, instead of this break
}
if (diffmask) {
diffpos = pos + _tzcnt_u32(diffmask); // position of the lowest set bit. Could safely use BSF rather than TZCNT here, since we only run when diffmask is non-zero.
diffs += _mm_popcnt_u32(diffmask);
}
} while(pos < size && diffs <= 1);
if (diffs == 1) {
letter = strA[diffpos];
return true;
}
return false;
}
醜陋break
,而不是包括在while
條件顯然有助於gcc generate better code。 do{}while()
也符合我希望asm出來的方式。我沒有嘗試使用for
或while
循環來查看gcc會做什麼。
內環真的是緊是這樣的:
.L14:
cmp rcx, r8
jnb .L10 # the while(pos<size) condition
.L6: # entry point for first iteration, because gcc duplicates the pos<size test ahead of the loop
vmovdqu ymm0, YMMWORD PTR [r9+rcx] # tmp118,* pos
vpcmpeqb ymm0, ymm0, YMMWORD PTR [r10+rcx] # tmp123, tmp118,* pos
add rcx, 32 # pos,
vpmovmskb eax, ymm0 # tmp121, tmp123
test eax, eax # tmp121
je .L14 #,
從理論上講,這應該每2個時鐘(英特爾Haswell的)一個迭代運行。循環中有7個融合域uop。 (將會是6,但是2-reg addressing modes apparently can't micro-fuse on SnB-family CPUs。)由於兩個uops是負載,而不是ALU,所以在SnB/IvB上這個吞吐量也是可以實現的。
這對於在兩條弦相同的區域飛行非常有用。正確處理任意字符串長度的開銷會使得這可能比簡單的標量函數慢,如果字符串很短,和/或早期有多個差異。
你想要什麼樣的優化?時間?記憶?順便說一下,你沒有處理已改變的字符的索引,你只是返回字符本身 – Pooya
我可以看到'canChange()'(有趣的名字,在那)檢查字符對 - 你能詳細說明'每一個單詞對? – greybeard
我認爲這是「通過每一對單詞迭代」,這是慢的來源;發佈的代碼看起來非常有效。如果將文本1中的每個單詞與文本2中的每個單詞進行比較,那麼這是一個O(N^2)操作,無論您對canChange()進行了多少優化,它都將變得很慢。 –