这两个循环中的哪一个更快?

时间:2012-05-15 19:35:00

标签: c windows performance x86 64-bit

我需要迭代一组字节,搜索一个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,如果有帮助的话。

4 个答案:

答案 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;
}

这种方法应该(希望)具有以下优点。

  1. 当窗口中没有0xAA时,每8个字节进行1次比较,而不是8次。
  2. 更少错误对齐的内存访问。
  3. 缺点包括......

    1. 它更复杂
    2. 如果数组包含大量0xAA字节(但不包含MARKER),则主搜索中的误报会影响性能。
    3. 另外一件事 - 既然你提到这只会在windows下的x86-64上运行,你有没有考虑过在汇编中写这个?如果是这样,PCMPEQB指令可能会有用。

      希望这有帮助。