我有2个16个元素(字符)数组,我需要“比较”,看看两者之间有多少元素相同。
这个例程将被使用数百万次(通常运行大约60或7000万次),所以我需要它尽可能快。我正在研究C ++(C ++ Builder 2007,用于记录)
现在,我有一个简单的说法:
matches += array1[0] == array2[0];
重复16次(因为分析它看起来比使用for循环快30%)
还有其他方法可以更快地运作吗?
有关环境和数据本身的一些数据:
答案 0 :(得分:16)
更新:此答案已被修改,以使我的评论与下面提供的源代码相匹配。
如果您有能力使用SSE2和popcnt指令,则可以进行优化。
16个字节恰好适合SSE寄存器。使用c ++和assembly / intrinsics,将两个16字节数组加载到xmm寄存器中,然后cmp它们。这会生成一个位掩码,表示比较的真/假条件。然后使用movmsk指令将位掩码的位表示加载到x86寄存器中;这就变成了一个小字段,你可以计算所有的1来确定你有多少真值。硬件popcnt指令可以快速计算寄存器中的所有1。
这需要了解装配/内在和SSE的知识。您应该能够找到两者的Web资源。
如果在不支持SSE2或popcnt的计算机上运行此代码,则必须遍历数组并使用展开的循环方法计算差异。
祝你好运编辑: 既然你表示你不知道汇编,这里有一些示例代码来说明我的答案:
#include "stdafx.h"
#include <iostream>
#include "intrin.h"
inline unsigned cmpArray16( char (&arr1)[16], char (&arr2)[16] )
{
__m128i first = _mm_loadu_si128( reinterpret_cast<__m128i*>( &arr1 ) );
__m128i second = _mm_loadu_si128( reinterpret_cast<__m128i*>( &arr2 ) );
return _mm_movemask_epi8( _mm_cmpeq_epi8( first, second ) );
}
int _tmain( int argc, _TCHAR* argv[] )
{
unsigned count = 0;
char arr1[16] = { 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0 };
char arr2[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0 };
count = __popcnt( cmpArray16( arr1, arr2 ) );
std::cout << "The number of equivalent bytes = " << count << std::endl;
return 0;
}
一些注意事项:此函数使用SSE2指令和Phenom处理器中引入的popcnt指令(这是我使用的机器)。我相信最新的带有SSE4的英特尔处理器也有其优势。该函数不检查CPUID的指令支持;如果在没有SSE2或popcnt的处理器上使用该函数,则该函数是未定义的(您可能会得到无效的操作码指令)。该检测代码是一个单独的线程。
我没有计时这段代码;我认为它更快的原因是因为它一次比较16个字节,无分支。您应该修改它以适应您的环境,并自己计时以查看它是否适合您。我在VS2008 SP1上编写并测试了这个。
SSE更喜欢在自然的16字节边界上对齐的数据;如果你可以保证那么你应该获得额外的速度改进,你可以将_mm_loadu_si128指令更改为_mm_load_si128,这需要对齐。
答案 1 :(得分:7)
关键是使用CPU支持的最大寄存器进行比较,然后在必要时回退到字节。
下面的代码演示了使用4字节整数,但如果您在SIMD架构(任何现代Intel或AMD芯片)上运行,您可以在回退到基于整数的循环之前在一条指令中比较两个阵列。目前大多数编译器都支持128位类型,因此不需要ASM。
(注意,对于SIMD比较,您的阵列必须是16字节对齐的,并且某些处理器(例如MIPS)将要求阵列为4字节对齐以进行基于int的比较。
E.g。
int* array1 = (int*)byteArray[0];
int* array2 = (int*)byteArray[1];
int same = 0;
for (int i = 0; i < 4; i++)
{
// test as an int
if (array1[i] == array2[i])
{
same += 4;
}
else
{
// test individual bytes
char* bytes1 = (char*)(array1+i);
char* bytes2 = (char*)(array2+i);
for (int j = 0; j < 4; j++)
{
same += (bytes1[j] == bytes2[j];
}
}
}
我不记得MSVC编译器究竟支持什么样的SIMD,但你可以做类似的事情;
// depending on compiler you may have to insert the words via an intrinsic
__m128 qw1 = *(__m128*)byteArray[0];
__m128 qw2 = *(__m128*)byteArray[1];
// again, depending on the compiler the comparision may have to be done via an intrinsic
if (qw1 == qw2)
{
same = 16;
}
else
{
// do int/byte testing
}
答案 2 :(得分:2)
如果您能够控制数组的位置,例如在内存中将其中一个放在内存中,则可能会导致它们在第一次访问时被加载到CPU的缓存中。
这取决于CPU及其缓存结构,因机器而异。
您可以在Henessy & Patterson's Computer Architecture: A Quantitative Approach
中了解内存层次结构和缓存答案 3 :(得分:2)
如果你需要绝对最低的占地面积,我会选择汇编代码。我有一段时间没有这样做,但我敢打赌MMX(或更可能是SSE2 / 3)的指令可以让你在很少的指令中做到这一点。
答案 4 :(得分:2)
如果匹配是常见的情况,那么尝试将值加载为32位整数而不是16位,这样您就可以一次性比较2(并将其计为2次匹配)。
如果两个32位值不相同,则必须单独测试它们(并输出顶部和底部16位值)。
代码会更复杂,但应该更快。
如果您的目标是64位系统,您可以使用64位整数执行相同的技巧,如果您真的想要超出限制,那么请查看删除汇编程序并使用各种基于向量的指令,这样可以让您工作一次128位。
答案 5 :(得分:1)
神奇的编译器选项会大大改变时间。特别是让它产生SSE矢量化可能会让你获得巨大的加速。
答案 6 :(得分:1)
这是否必须与平台无关,或者此代码是否始终在相同类型的CPU上运行?如果您将自己局限于现代x86 CPU,则可以使用MMX指令,这样可以允许您在一个时钟周期内操作8个字节的数组。 AFAIK,gcc允许您在C代码中嵌入汇编,而英特尔的编译器(icc)支持内部函数,这些封装允许您直接调用特定的汇编指令。其他SIMD指令集(例如SSE)也可能对此有用。
答案 7 :(得分:1)
数组中的值之间是否有任何关联?有些字节更可能与其他字节相同吗?价值观中可能存在一些内在的顺序吗?然后你可以针对最可能的情况进行优化。
答案 8 :(得分:1)
如果你解释数据实际代表什么,那么可能有一种完全不同的方式来表示内存中的数据,这将使这种类型的暴力比较变得不必要。注意详细说明数据实际代表什么?
答案 9 :(得分:0)
一个声明是否更快?
matches += (array1[0] == array2[0]) + (array1[1] == array2[1]) + ...;
答案 10 :(得分:0)
如果写16次比简单循环快,那么你的编译器很糟糕,或者你没有打开优化。
简短回答:没有更快的方法,除非你在并行硬件上进行矢量操作。
答案 11 :(得分:0)
尝试使用指针而不是数组:
p1 = &array1[0];
p2 = &array2[0];
match += (*p1++ == *p2++);
// copy 15 times.
当然,您必须根据其他方法来衡量这一点,以确定哪种方法最快。
您确定此例程是您处理的瓶颈吗?您是否真的通过优化它来加快整个应用程序的性能?同样,只有测量才能证明。
答案 12 :(得分:0)
有什么方法可以修改数组的存储方式吗?考虑到您可能正在使用32位编译器,一次比较1个字节非常慢。相反,如果将16个字节存储为4个整数(32位)或2个长度(64位),则只需要分别执行4或2次比较。
要问自己的问题是将数据存储为4整数或2长数组的成本是多少。您需要多久访问一次数据等。
答案 13 :(得分:0)
总有很好的旧x86 REPNE CMPS指令。
答案 14 :(得分:0)
一个额外的可能优化:如果你期望大多数时候数组是相同的,那么作为第一步做一个memcmp()可能会稍快一点,如果测试返回true则设置'16'作为答案。如果您不希望阵列经常相同,那么只会减慢速度。