【问题标题】:What's the most efficient way to compare two blocks of memory in the D language?在 D 语言中比较两个内存块的最有效方法是什么?
【发布时间】:2009-11-06 01:49:13
【问题描述】:

我需要一个内存块比较函数,以便在 D 编程语言中对字节数组进行二进制搜索。它不需要任何有用的语义。它只需要快速并且是有效的比较函数(产生总排序的函数)。已知要比较的内存块长度相同。

C 的memcmp 实际上很慢,因为它试图保留有用的字符串比较语义,而我不需要。以下是迄今为止我想出的最好的。有没有人知道更好的东西,最好是不使用非便携式 CPU 特定指令?

// Faster than C's memcmp because it doesn't preserve any meaningful
// semantics.  It's just a completely arbitrary, but really fast,
// comparison function.
int memoryCompare(const(void)* lhs, const(void)* rhs, size_t n) {
    for(; n >= uint.sizeof; n -= uint.sizeof) {
        if( *(cast(uint*) lhs) < *(cast(uint*) rhs)) {
            return -1;
        } else if( *(cast(uint*) lhs) > *(cast(uint*) rhs)) {
            return 1;
        }
        lhs += uint.sizeof;
        rhs += uint.sizeof;
    }

    for(; n >= ubyte.sizeof; n -= ubyte.sizeof) {
        if( *(cast(ubyte*) lhs) < *(cast(ubyte*) rhs)) {
            return -1;
        } else if( *(cast(ubyte*) lhs) > *(cast(ubyte*) rhs)) {
            return 1;
        }
        lhs += ubyte.sizeof;
        rhs += ubyte.sizeof;
    }

    return 0;
}

编辑:我已经阅读了 SSE,但出于 3 个原因不想使用它:

  1. 它不是便携式的。
  2. 需要在 ASM 中编程。
  3. 比较指令假定您的数据是浮点数,如果某些数据恰好与 NaN 的模式匹配,这可能会出现问题。

【问题讨论】:

  • 如果你想要真正的低级,你可以尝试使用 rep cmpsb、rep cmpsd 甚至 SSE 比较的内联汇编。
  • Re “C 的 memcmp 实际上很慢,因为它试图保留有用的字符串比较语义”。您当然是在讨论特定的实现,但总的来说,在现代 CPU 上 memcmp 受限于您访问内存的速度。没有比这更好的算法了,可证明
  • 什么意思,“C 的 memcmp 实际上很慢,因为它试图保留有用的字符串比较语义”?这听起来更像strncmp() 而不是memcmp()。请显示基准测试表明您的版本(因为它甚至不是 C 语言而无法编译)比现代 C 标准库的内置 memcmp() 更快。
  • 我将其重新标记为“d”。代码都是d,所以我不确定为什么它被标记为“c”。当然,如果不是拼写错误,请随意将其改回,但这可能会让读者感到困惑,并认为您的代码有误。

标签: performance algorithm d binary-search low-level


【解决方案1】:

你可以试试:

  • 检查 uint 是否是适合您的目标 CPU 的最大类型(ulong 可能更适合本机寄存器)
  • 使用 2 个该类型的指针
  • 使用 *p++ 使用 2 个局部变量(不要为 1 个值取消引用 2 次指针)
  • 预先划分第一个循环的计数器(使用 while (counter--))
  • 通过将第二个循环替换为一个开关来展开第二个循环(如果 sizeof(适合寄存器的类型)已知并且将始终相同。)

编辑:如果第一个循环是瓶颈,那么展开可能就是答案。结合在相等值的情况下将条件数量减半,展开 4 次我得到类似:

uint* lp = (uint*)lhs;
uint* rp = (uint*)rhs;
uint  l;
uint  r;
int count = (n / uint.sizeof) / 4;

while (count--) {
    if( (l = *lp++) != (r = *rp++) {
        return (l < r) ? -1 : 1;
    }
    if( (l = *lp++) != (r = *rp++) {
        return (l < r) ? -1 : 1;
    }
    if( (l = *lp++) != (r = *rp++) {
        return (l < r) ? -1 : 1;
    }
    if( (l = *lp++) != (r = *rp++) {
        return (l < r) ? -1 : 1;
    }
}

当然,剩下 (n / uint.sizeof) % 4 迭代要做,您可以通过交错开关将其混合到这个循环中,我把它作为练习留给读者邪恶的笑容

【讨论】:

  • 1.我尝试使用 ulongs,速度较慢。 2. 我尝试取出第二个取消引用。它并没有更快,可能是 b/c 编译器无论如何都会优化到相同的东西,所以我把第二个 deref 放回去,因为它使代码更具可读性。 3.我觉得大部分瓶颈是第一个循环。
  • 对所有答案投 -1 票的人能否添加注释说明原因?
  • rsp 我认为它只是一个巨魔。我认为这些答案中的任何一个都不值得一票否决。
【解决方案2】:

我对此了解不多,但是有一些向量指令可以一次将指令应用于多个字节。你可以使用这些结果来做一个非常快速的 memcmp。我不知道您需要哪些说明,但如果您查看 Larrabee 新说明或查看这篇文章,您可能会找到您正在寻找的内容http://www.ddj.com/architect/216402188

注意:这个 CPU 不是 ATM AFAIK

-Edit- 现在我肯定有一个指令集(尝试查看 SSE 或 SSE2),如果它们对齐,可以一次比较 16 个字节。

你可以试试这个纯c++代码。

template<class T>
int memoryCompare(const T* lhs, const T * rhs, size_t n) {
    const T* endLHS = lhs + n/sizeof(T);
    while(lhs<endLHS) {
        int i = *lhs - *rhs;
        if(i != 0) return i > 0 ? 1 : -1;
        lhs++;
        rhs++;
    }
    //more code for the remaining bytes or call memoryCompare<char>(lhs, rhs, n%sizeof(T));
    return 0;
}

这里的好处是你增加了指针,这样你就可以取消引用它而不应用索引(它的 ptr_offset[index] vs ptr_offset)。以上使用模板,因此您可以在 64 位机器上使用 64 位。组装中的 CMP 实际上只是减去检查 N 和 Z 标志。而不是比较 N 和减少 N 我只是在我的版本中比较。

【讨论】:

  • 如果您取消引用 lhs 和 rhs 可能会有所帮助 :-)
【解决方案3】:

很大程度上取决于您的系统和数据。我们只能做出这么多的假设。你用的是什么处理器?它必须是直接的C代码吗? CPU寄存器的宽度是多少? CPU的缓存结构是怎样的?等等等等

这还取决于您的数据有多么不同。如果每个缓冲区的第一个字节不太可能相同,那么函数的速度就毫无意义,因为从逻辑上讲,它不会到达函数的其余部分。如果前 n-1 个字节可能通常是 sme,那么它就变得更重要了。

无论您如何进行测试,您都不太可能看到太大的变化。

无论如何,这是我自己的一个小实现,它可能会或可能不会比您自己的更快(或者,鉴于我只是在进行过程中完成了它,它可能会或可能不会起作用;)) :

int memoryCompare(const void* lhs, const void* rhs, size_t n) 
{
    uint_64 diff    = 0

    // Test the first few bytes until we are 32-bit aligned.
    while( (n & 0x3) != 0 && diff != 0 )
    {
        diff = (uint_8*)lhs - (uint_8*)rhs;
        n--;
        ((uint_8*)lhs)++;
        ((uint_8*)rhs)++;              
        }

    // Test the next set of 32-bit integers using comparisons with
    // aligned data.
    while( n > sizeof( uint_32 ) && diff != 0 )
    {
        diff = (uint_32*)lhs - (uint_32*)rhs;
        n   -= sizeof( uint_32 );
        ((uint_32*)lhs)++;
        ((uint_32*)rhs)++;
    }

    // now do final bytes.
    while( n > 0 && diff != 0 ) 
        {
        diff = (uint_8*)lhs - (uint_8*)rhs;
        n--;
        ((uint_8*)lhs)++;
        ((uint_8*)rhs)++;  
        }

    return (int)*diff / abs( diff ));
}

【讨论】:

    【解决方案4】:

    this question 的答案对您有帮助吗?

    如果编译器支持将 memcmp() 实现为内部/内置函数,那么您似乎很难超越它。

    我不得不承认我对 D 几乎一无所知,所以我不知道 D 编译器是否支持内在函数。

    【讨论】:

    • OP 已经确定他的替代方案比 memcmp 更快,并要求进一步改进。如果他的编译器会使用内在函数,那么情况可能就不是这样了。
    【解决方案5】:

    我认为 memcmp 被指定为进行逐字节比较,无论数据类型如何。您确定您的编译器的实现保留了字符串语义吗?不应该。

    【讨论】:

      【解决方案6】:

      如果您相信您的编译器的优化,您可以尝试对 acidzombie24s 的建议进行一些修改:

      template<class T> int memoryCompare(const T* lhs, const T * rhs, size_t n) {
          const T* endLHS = &lhs[n];
          while(lhs<endLHS) {
              //A good optimiser will keep these values in register
              //and may even be clever enough to just retest the flags
              //before incrementing the pointers iff we loop again.
              //gcc -O3 did the optimisation very well.
              if (*lhs > *rhs) return 1;
              if (*lhs++ < *rhs++) return -1;
          }
          //more code for the remaining bytes or call memoryCompare<char>(lhs, rhs, n%sizeof(T));
          return 0;
      }
      

      这是 x86 汇编代码中的整个 gcc -O3 优化内循环 对于上述的 C 版本,仅传递 char 数组指针:

      Loop:
              incl    %eax         ; %eax is lhs
              incl    %edx         ; %edx is rhs
              cmpl    %eax, %ebx   ; %ebx is endLHS
              jbe     ReturnEq
              movb    (%edx), %cl
              cmpb    %cl, (%eax)
              jg      ReturnGT
              jge     Loop
      ReturnLT:
              ...
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-04-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多