数组比较(逐个元素)

时间:2015-05-05 13:51:25

标签: c algorithm compare comparison vectorization

我正在使用的算法花费了大部分时间来比较一个数组和一行矩阵。如果任何 ith 元素相同,则算法调用过程A,如果没有元素相等,则调用过程B.例如:

[1, 4, 10, 3, 5][5, 3, 0, 3, 0]调用A(),因为对于第4个位置,两个数组中的值都是3。

[1, 4, 10, 3, 5][5, 3, 0, 1, 0]调用B()因为对于相同的位置,值永远不会相同。

请注意:(1)数组和矩阵行的大小N始终相同,(2)当至少一个值匹配时,算法会调用A()

在C中执行此操作的最简单但非常天真的方法是:

for(int i=0; i<N; i++)
   if( A[i] == B[i] ){
      flag = 1;
      break;
   }

这仍然非常低效。在最坏的情况下,我将进行N次比较。这里真正的问题是该算法可以进行数万亿次比较。

N(矩阵中数组/行的大小)从100到1000不等。我想加快这个程序。我看了矢量化,发现我可以使用cmpeq_pd。但是,矢量化仍然有限,因为我的所有参赛作品 longs 。有没有人有想法?我可以申请面具等吗?

更多信息/背景:

  • 这是一种迭代算法。在每次迭代中,我将矩阵增加到一行并多次检查整个矩阵。我也可以更新几行。
  • 匹配的可能性并不取决于位置。
  • 我愿意有误报和否定,以便大大加快这个程序。
  • 如果匹配,则验证匹配的位置相关(我只需知道是否存在匹配位置)。
  • 最大数量(约70%)的比较不会导致匹配。
  • 并行化在不同的级别完成,即此内核无法并行化。

6 个答案:

答案 0 :(得分:3)

我不知道这是否适用于您正在开发的应用程序,但是大型阵列上的操作通常在GPU上加速。您可以期望比CPU高10-20倍的吞吐量。如果您的应用可能会在CUDA上运行会产生巨大差异的关键部分。

答案 1 :(得分:2)

虽然您的Sandy Bridge CPU只有AVX用于256位SIMD(而不是AVX2),因此缺乏对4路SIMD 64位整数操作的支持,我认为您仍然可以使用AVX浮点指令实现4路SIMD,如如下:比较64位整数值的2 x 256位向量,v1v2

__m256d vcmp = _mm256_xor_pd(v1, v2); // use XOR rather than compare, so we are not 
                                      // affected by values which map to NaNs
vcmp = _mm256_cmp_pd(vcmp, _mm256_setzero_pd(), _CMP_EQ_OQ);
                                      // now we can do a valid comparison as if the
                                      // data really is double precision float
int mask = _mm256_movemask_pd(vcmp);  // extract the sign bits
bool any_eq = (mask != 0);            // if any elements matched then mask
                                      // will be non-zero

以下是用于测试和说明目的的示例程序:

#include <stdio.h>
#include <stdint.h>
#include <immintrin.h>

int test(__m256d v1, __m256d v2)
{
    __m256d vcmp = _mm256_xor_pd(v1, v2);
    vcmp = _mm256_cmp_pd(vcmp, _mm256_setzero_pd(), _CMP_EQ_OQ);
    return _mm256_movemask_pd(vcmp);
}

int main()
{
    int64_t a1[4] = { 3098, 3860, 405, 3308 };
    int64_t a2[4] = { 1930, 1274, 2195, 2939 };
    int64_t a3[4] = { 1930, 1274, 405, 2939 };

    __m256i v1 = _mm256_loadu_pd((double *)a1);
    __m256i v2 = _mm256_loadu_pd((double *)a2);
    __m256i v3 = _mm256_loadu_pd((double *)a3);

    printf("mask = %d (should be == 0)\n", test(v1, v2));

    printf("mask = %d (should be != 0)\n", test(v1, v3));

    return 0;
}

测试:

$ gcc -Wall -mavx a3mlord2.c && ./a.out 
mask = 0 (should be == 0)
mask = 4 (should be != 0)

答案 2 :(得分:1)

每当你寻找优化时,你面前都有不同的路径:

  • 算法优化:通常是一种排序算法,在您的情况下使用行中或行之间的某些依赖关系来仅测试某些情况而不是N值。你没有说出我们可以用的任何东西,但也许知道这样的规则 - 这种优化可以有数量级的增益
  • 中级优化:一旦你选择了你的算法,检查你如何组织你的循环和测试 - 再次,我不知道可以做什么 - 通常增加10%左右,除了糟糕的实现
  • 低级优化:试图比优化编译器更聪明一般会让你松散,但在某些情况下,基准测试不同的实现可能会带来一些百分之一的收益
  • 并行化:如果算法支持它,则可以对一个或多个核心或处理器上的总处理进行划分。预期收益通常略低于同时线程数。

根据您所说的,唯一可能的优化是并行处理n个核心,每个核心(减1)执行行的一部分,另一个处理第一次比较的结果。但如前所述,如果数据中存在规则,则收益可能会高得多。

答案 3 :(得分:1)

在C中执行此操作的最简单但非常天真的方法是
正如您所暗示的那样,从可读性的角度来看,您在此声明下提供的代码示例可能很简单,但它是否会转换为编译后数据比较最简单,最有效的方法?

建议尝试块比较:
呈现数据以进行比较的方式可以有助于速度和效率的比较。将值加载到单独的变量中(分配给使用单独的寄存器),然后比较寄存器。

long a1 = A[0];
long a2 = A[1];
long a3 = A[2];
long a4 = A[3];
...
long an = A[n];

long b1 = B[0];
long b2 = B[1];
long b3 = B[2];
long b4 = B[3];
...
long bn = B[n];

if ((a1 == b1) || (a2 == b2) || (a3 == b3) || (a4 == b4) ... || (an == bn))
{
   //do something
}
else
{
   //do something else
}

要真正了解方法是否最快,请对其进行编码,查看它生成的程序集,或对其进行基准测试。正如您在帖子中建议的那样,循环数组元素可能不是最有效的方法。

编辑 :一个偏见:Matlab以包含一些最快的数组比较例程而着称,它也有一个Matlab来C转换能力。如果您或同事拥有Matlab副本,您可以尝试对使用Matlab创建的算法进行一些速度测试,然后转换为C以观察它创建的内容。我之前使用过这个功能,它产生的C构造并不是很好看,但通常非常有效(就速度而言)。

答案 4 :(得分:0)

SIMD处理根本不可能有所帮助:你有一个相当小的循环接触大量数据(每次迭代16个字节)。即使在没有SIMD的情况下运行,这也可能会使内存总线饱和。

在我看来,你有两个基本选择:

  1. 您使用更多/更宽的内存总线 这可以通过使用多个内核或GPU来实现。

  2. 您尝试减少比较次数 从您的问题中可以看出是否可能,但如果您的算法多次进行相同的比较,则可以通过缓存比较结果来重构算法。根据算法,可以显着提高速度。

答案 5 :(得分:-3)

如果您正在使用gcc,并且如果您使用的是x86平台,那么您的代码可能会受益于使用memcmp()代替&#34;自行开发的&#34; for循环。 memcmp()(分别是它的内置对手)做了一些非常聪明的优化。