我需要迭代一组字节,搜索一个4字节的值(所有4个字节是相同的)。数据的长度是可变的,这些字节可以在数据内的任何位置;我正在寻找第一个例子。我正在尝试找到最快的实现,因为这个逻辑运行在我的代码的关键部分。
这只能在x86& x64,在Windows下。
typedef unsigned char Byte;
typedef Byte* BytePtr;
typedef unsigned int UInt32;
typedef UInt32* UInt32Ptr;
const Byte MARKER_BYTE = 0xAA;
const UInt32 MARKER = 0xAAAAAAAA;
UInt32 nDataLength = ...;
BytePtr pData = ...;
BytePtr pEnd = pData + nDataLength - sizeof ( UInt32 );
// Option 1 -------------------------------------------
while ( pData < pEnd )
{
if ( *( (UInt32Ptr) pData ) == MARKER )
{
... // Do something here
break;
}
pData++;
}
// Option 2 -------------------------------------------
while ( pData < pEnd )
{
if ( ( *pData == MARKER_BYTE ) && ( *( (UInt32Ptr) pData ) == MARKER ) )
{
... // Do something here
break;
}
pData++;
}
我认为Option 2
更快,但我不确定我的推理是否正确。
Option 1
首先从内存中读取4个字节,然后根据4字节常量进行检查,如果没有找到,则进入下一个字节并重新开始。来自内存的下一个4字节就绪将重叠已经读取的3个字节,因此需要再次获取相同的字节。我的4字节标记之前的大多数字节将被读取两次。
Option 2
一次只读取1个字节,如果该单个字节匹配,则从该地址读取完整的4字节值。这样,所有字节只读取一次,只读取4个匹配字节。
我的推理是正确的还是我忽略了什么?
在有人提出之前,是的,我确实需要进行这种优化。 :)
编辑:请注意,此代码只能在基于Intel / AMD的计算机上运行。我不关心其他架构是否会无法运行,只要普通的x86 / x64计算机(台式机/服务器)运行时没有问题或性能损失。
编辑2 :编译器是VC ++ 2008,如果有帮助的话。
答案 0 :(得分:6)
您也可以尝试Boyer-Moore方法。
pData = start + 3;
int i;
while(pData < pEnd) {
for(i = 0; i < 4; ++i) {
if (*(pData-i) != MARKER_BYTE) {
pData += 4-i;
break;
}
}
if (i == 4) {
/* do something here with (pData-3) */
break;
}
}
如果你很幸运,那么在你找到匹配项之前,它只会测试每四个字节。
这是否比测试每个字节更快或更慢是任何人对短模式的猜测。
答案 1 :(得分:3)
选项1将执行大量未对齐的内存访问。我不确定硬件是否可能。至少在某些硬件上,Windows将拦截产生的异常,并且非常缓慢地模拟内存访问。性能灾难。
无论如何,你已经有了代码。为什么不测量它并且100%确定?
答案 2 :(得分:1)
选项2。 没有理由获取4个字节,如果256个中的255个第一个不是你想要的那个。
为了Pete的缘故,展开循环。
编辑:展开。长度为nDataLength
。你可以这样说:
pEnd1 = pData + (nDataLength & -8);
while (pData < pEnd1){
if (pData[0] == theByteIWant){ ... }
if (pData[1] == theByteIWant){ ... }
...
if (pData[7] == theByteIWant){ ... }
pData += 8;
}
while(pData < pEnd){
if (pData[0] == theByteIWant){ ... }
pData++;
}
看看那是做什么的?你不会花一半时间问一个问题(pData < pEnd)
,答案几乎总是一样的。
答案 3 :(得分:1)
这种方法并不完整,但基本思路是一次搜索八(8)个字节用于0xAA模式。如果找到,则可以对MARKER模式执行二次搜索。
阶段1:逐字节测试,直到您的阵列对齐8字节。
阶段2: #define HAS_NUL_BYTE(x)((x) - 0x0101010101010101ull)&amp; ~x&amp; 0x8080808080808080ull)
uint64_t value;
for (...) {
value = *(uint64_t *) array[i] ^ 0xAAAAAAAAAAAAAAAAull;
if (HAS_NUL_BYTE (value) != 0) {
perform secondary search for the MARKER pattern
}
i += 8;
}
这种方法应该(希望)具有以下优点。
缺点包括......
另外一件事 - 既然你提到这只会在windows下的x86-64上运行,你有没有考虑过在汇编中写这个?如果是这样,PCMPEQB指令可能会有用。
希望这有帮助。