在 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 语言中比较两个内存块的最有效方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章