在 D 语言中比较两个内存块的最有效方法是啥?

Posted

技术标签:

【中文标题】在 D 语言中比较两个内存块的最有效方法是啥?【英文标题】: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 个原因不想使用它:

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

【问题讨论】:

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

【讨论】:

以上是关于在 D 语言中比较两个内存块的最有效方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

C语言中计算数组长度的方法是啥

在 Julia 中定义一个非常稀疏的网络矩阵的最有效方法是啥?

在c语言中&std是啥意思

在c语言中&std是啥意思

在 SSE2 上进行无符号 64 位比较的最有效方法是啥?

在 SQL 中找到两个集合的最紧凑和最有效的方法是啥? [复制]