如果要优化主要是完全相同的字符串,你可以使用的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()进行了多少优化,它都将变得很慢。 –