C++ 找到一种有效的方法在2个巨大的缓冲区上执行最大值(字节/字节)

C++ 找到一种有效的方法在2个巨大的缓冲区上执行最大值(字节/字节),c++,c,performance,numpy,max,C++,C,Performance,Numpy,Max,我需要非常快地比较900万字节,以保持每个字节的最大值。以下是我的工作: int bufSize=9000000; 字节t*buf=/*…*/; 字节_t*maxBuf=/*…*/; 对于int i=0;imaxBuf[i]{ maxBuf[i]=buf[i]; } } 它的工作,但我需要减少3处理时间 特别是,是否有一种方法可以使用64位CPU 你知道numpy阵列是否有帮助吗 编辑:处理器为四核ARM Cortex-A57,操作系统为Linux for Tegra。很抱歉,我以前应该写这篇文

我需要非常快地比较900万字节,以保持每个字节的最大值。以下是我的工作:

int bufSize=9000000; 字节t*buf=/*…*/; 字节_t*maxBuf=/*…*/; 对于int i=0;imaxBuf[i]{ maxBuf[i]=buf[i]; } } 它的工作,但我需要减少3处理时间

特别是,是否有一种方法可以使用64位CPU

你知道numpy阵列是否有帮助吗


编辑:处理器为四核ARM Cortex-A57,操作系统为Linux for Tegra。很抱歉,我以前应该写这篇文章。

就你所拥有的而言,没有比这更快的方法了。使用python的numpy实际上只会改进python,从而提供类似C的行为

我认为你最好的选择是使用OpenMP。这是一个简单的教程。由于每个迭代都是相互独立的,我认为您的代码应该如下所示:

#pragma omp parallel for
for (int i = 0; i < bufSize; ++i) {
    #pragma omp simd
    if (buf[i] > maxBuf[i]) {
        maxBuf[i] = buf[i];
    }
}
然后使用-fopenmp编译。不过,我不确定pragma-omp-simd系列是否会有多大帮助

您还可以添加编译器优化。这是一份清单。另请参阅。但这些并不总能提高速度,这取决于很多因素。只要试一下,它就可以大大优化你的代码


例如,我有一个需要几个小时的算法。在进行了编译器优化和OpenMP之后,我能够将它缩短到30秒左右。但是,这个领域的编程会变得非常困难,有很多因素需要考虑。

就你所拥有的来说,没有更快的方法来完成它。使用python的numpy实际上只会改进python,从而提供类似C的行为

我认为你最好的选择是使用OpenMP。这是一个简单的教程。由于每个迭代都是相互独立的,我认为您的代码应该如下所示:

#pragma omp parallel for
for (int i = 0; i < bufSize; ++i) {
    #pragma omp simd
    if (buf[i] > maxBuf[i]) {
        maxBuf[i] = buf[i];
    }
}
然后使用-fopenmp编译。不过,我不确定pragma-omp-simd系列是否会有多大帮助

您还可以添加编译器优化。这是一份清单。另请参阅。但这些并不总能提高速度,这取决于很多因素。只要试一下,它就可以大大优化你的代码


例如,我有一个需要几个小时的算法。在进行了编译器优化和OpenMP之后,我能够将它缩短到30秒左右。但是,这个编程领域会变得非常困难,有很多因素需要考虑。

< P>你可以在我的系统上找到一个高效的解决方案[英特尔I5-8250U] ~45毫秒vs 1ms,如果你有一个AVX2能力的CPU,同时使用因特尔SIMD内含子

处理32个字节。 因为9000000可以被32整除,所以您甚至不需要额外的循环来完成

// #include <immintrin.h>, also for g++ add `-mavx2`-flag

int bufSize = 9000000;
byte *buf = static_cast<byte *>(_mm_malloc(sizeof(*buf) * bufSize, 32));
byte *maxBuf = static_cast<byte *>(_mm_malloc(sizeof(*maxBuf) * bufSize, 32));

for (int i = 0; i < bufSize; ++i) 
{
    buf[i] = (byte) rand();
    maxBuf[i] = (byte) rand();
}

for (int i = 0; i < bufSize; i += 32) 
{
    __m256i *buf_simd = (__m256i *) &buf[i];
    __m256i *maxBuf_simd = (__m256i *) &maxBuf[i];

    *maxBuf_simd = _mm256_max_epu8(*maxBuf_simd, *buf_simd);
}

_mm_free(buf);
_mm_free(maxBuf);
因为我没有你的数据,我用随机数据创建了两个数组。这里非常重要的一点是,它们是32字节对齐的

之后,在for循环的每次迭代中,我将32字节加载到向量寄存器中,并执行_mm256_max_epu8,它基本上将256bit划分为32字节的数据包,即所谓的压缩向量,并选取每个字节的最大值。可以通过上面的链接找到更详细的解释


如果您只有支持SSE2的cpu,则可以使用128位向量的_mm_max_epu8。

如果您有支持AVX2的cpu,并使用Intels SIMD Intrinsic一次性处理32个字节,则可以在我的系统[Intel i5-8250U]上获得高效的解决方案

因为9000000可以被32整除,所以您甚至不需要额外的循环来完成

// #include <immintrin.h>, also for g++ add `-mavx2`-flag

int bufSize = 9000000;
byte *buf = static_cast<byte *>(_mm_malloc(sizeof(*buf) * bufSize, 32));
byte *maxBuf = static_cast<byte *>(_mm_malloc(sizeof(*maxBuf) * bufSize, 32));

for (int i = 0; i < bufSize; ++i) 
{
    buf[i] = (byte) rand();
    maxBuf[i] = (byte) rand();
}

for (int i = 0; i < bufSize; i += 32) 
{
    __m256i *buf_simd = (__m256i *) &buf[i];
    __m256i *maxBuf_simd = (__m256i *) &maxBuf[i];

    *maxBuf_simd = _mm256_max_epu8(*maxBuf_simd, *buf_simd);
}

_mm_free(buf);
_mm_free(maxBuf);
因为我没有你的数据,我用随机数据创建了两个数组。这里非常重要的一点是,它们是32字节对齐的

之后,在for循环的每次迭代中,我将32字节加载到向量寄存器中,并执行_mm256_max_epu8,它基本上将256bit划分为32字节的数据包,即所谓的压缩向量,并选取每个字节的最大值。可以通过上面的链接找到更详细的解释


如果您只有支持SSE2的cpu,则可以使用128位向量的_mm_max_epu8。

暂时指出显而易见的问题。您的代码有选择地修改maxBuf中的数据,这导致矢量器失败。只需将代码改为使用std::max

对于int i=0;i 证明:

内部循环已展开,现在使用AVX2:

.LBB0_12:                               # =>This Inner Loop Header: Depth=1
        vmovdqu ymm0, ymmword ptr [rsi + rax]
        vmovdqu ymm1, ymmword ptr [rsi + rax + 32]
        vmovdqu ymm2, ymmword ptr [rsi + rax + 64]
        vmovdqu ymm3, ymmword ptr [rsi + rax + 96]
        vpmaxub ymm0, ymm0, ymmword ptr [rdi + rax]
        vpmaxub ymm1, ymm1, ymmword ptr [rdi + rax + 32]
        vmovdqu ymmword ptr [rsi + rax], ymm0
        vmovdqu ymmword ptr [rsi + rax + 32], ymm1
        vpmaxub ymm0, ymm2, ymmword ptr [rdi + rax + 64]
        vpmaxub ymm1, ymm3, ymmword ptr [rdi + rax + 96]
        vmovdqu ymmword ptr [rsi + rax + 64], ymm0
        vmovdqu ymmword ptr [rsi + rax + 96], ymm1
        vmovdqu ymm0, ymmword ptr [rsi + rax + 128]
        vmovdqu ymm1, ymmword ptr [rsi + rax + 160]
        vpmaxub ymm0, ymm0, ymmword ptr [rdi + rax + 128]
        vpmaxub ymm1, ymm1, ymmword ptr [rdi + rax + 160]
        vmovdqu ymmword ptr [rsi + rax + 128], ymm0
        vmovdqu ymmword ptr [rsi + rax + 160], ymm1
        vmovdqu ymm0, ymmword ptr [rsi + rax + 192]
        vmovdqu ymm1, ymmword ptr [rsi + rax + 224]
        vpmaxub ymm0, ymm0, ymmword ptr [rdi + rax + 192]
        vpmaxub ymm1, ymm1, ymmword ptr [rdi + rax + 224]
        vmovdqu ymmword ptr [rsi + rax + 192], ymm0
        vmovdqu ymmword ptr [rsi + rax + 224], ymm1
        add     rax, 256
        add     rdx, 4
        jne     .LBB0_12

暂时指出显而易见的问题。您的代码有选择地修改maxBuf中的数据,这导致矢量器失败。只需将代码改为使用std::max

对于int i=0;i 证明:

内部循环已展开,现在使用AVX2:

.LBB0_12:                               # =>This Inner Loop Header: Depth=1
        vmovdqu ymm0, ymmword ptr [rsi + rax]
        vmovdqu ymm1, ymmword ptr [rsi + rax + 32]
        vmovdqu ymm2, ymmword ptr [rsi + rax + 64]
        vmovdqu ymm3, ymmword ptr [rsi + rax + 96]
        vpmaxub ymm0, ymm0, ymmword ptr [rdi + rax]
        vpmaxub ymm1, ymm1, ymmword ptr [rdi + rax + 32]
        vmovdqu ymmword ptr [rsi + rax], ymm0
        vmovdqu ymmword ptr [rsi + rax + 32], ymm1
        vpmaxub ymm0, ymm2, ymmword ptr [rdi + rax + 64]
        vpmaxub ymm1, ymm3, ymmword ptr [rdi + rax + 96]
        vmovdqu ymmword ptr [rsi + rax + 64], ymm0
        vmovdqu ymmword ptr [rsi + rax + 96], ymm1
        vmovdqu ymm0, ymmword ptr [rsi + rax + 128]
        vmovdqu ymm1, ymmword ptr [rsi + rax + 160]
        vpmaxub ymm0, ymm0, ymmword ptr [rdi + rax + 128]
        vpmaxub ymm1, ymm1, ymmword ptr [rdi + rax + 160]
        vmovdqu ymmword ptr [rsi + rax + 128], ymm0
        vmovdqu ymmword ptr [rsi + rax + 160], ymm1
        vmovdqu ymm0, ymmword ptr [rsi + rax + 192]
        vmovdqu ymm1, ymmword ptr [rsi + rax + 224]
        vpmaxub ymm0, ymm0, ymmword ptr [rdi + rax + 192]
        vpmaxub ymm1, ymm1, ymmword ptr [rdi + rax + 224]
        vmovdqu ymmword ptr [rsi + rax + 192], ymm0
        vmovdqu ymmword ptr [rsi + rax + 224], ymm1
        add     rax, 256
        add     rdx, 4
        jne     .LBB0_12

多亏了@Frederik,我们找到了执行这些操作的方法 他在手臂上使用霓虹灯

代码如下:

包括 在vmax_u8上: 在vst1_u8上: 另见:
感谢@Frederik,我们发现了如何使用手臂上的霓虹灯执行这些操作

代码如下:

包括 在vmax_u8上: 在vst1_u8上: 另见:
我不知道64位能有什么帮助。您可以拆分缓冲区并在多线程中处理它。要尝试的东西是maxBuf[i]=buf[i]>maxBuf[i]?buf[i]:maxBuf[i],如果它说服编译器实现它的分支更少,这可能会有所帮助。64位解决方案将更为复杂。最具性能的解决方案将使用特定于平台的向量指令,如SSE。您应该显示所使用的编译器选项,并指出所使用的CPU和操作系统。稍微展开循环,以便在处理循环上花费更少的时间,在比较上花费更多的时间。例如,对于在循环中检查的四个元素(而不是一个),首先要确定有多少个4的集合,其余的要在另一个循环中处理。我不确定64位有什么帮助。您可以拆分缓冲区并在多线程中处理它。要尝试的东西是maxBuf[i]=buf[i]>maxBuf[i]?buf[i]:maxBuf[i],如果它说服编译器实现它的分支更少,这可能会有所帮助。64位解决方案将更为复杂。最具性能的解决方案将使用特定于平台的向量指令,如SSE。您应该显示所使用的编译器选项,并指出所使用的CPU和操作系统。稍微展开循环,以便在处理循环上花费更少的时间,在比较上花费更多的时间。例如,对于在循环中检查的四个元素而不是一个元素,首先找出有多少组4个元素,其余的元素将在另一个循环中处理。您希望simd带有For循环,而不是在内部。这些不是有效地做同样的事情吗?您希望simd带有For循环,不是在里面。这些不是有效地做同样的事情吗?谢谢!你认为你的解决方案对ARM有效吗?此代码在四核ARM Cortex-A57上运行,操作系统为Linux for Tegra。@不,抱歉,此特定示例在ARM上不起作用。然而,ARM处理器有自己的向量扩展,称为NEON。您可以尝试robthebloke的解决方案,为您的CPU提供特定的调优参数,以尝试使用NEON矢量。摆弄一下门闩可能会有帮助。谢谢!你认为你的解决方案对ARM有效吗?此代码在四核ARM Cortex-A57上运行,操作系统为Linux for Tegra。@不,抱歉,此特定示例在ARM上不起作用。然而,ARM处理器有自己的向量扩展,称为NEON。您可以尝试robthebloke的解决方案,为您的CPU提供特定的调优参数,以尝试使用NEON矢量。摆弄一下门闩可能会有帮助。非常感谢。我以前应该写过,但我的代码运行在四核ARM Cortex-A57上,操作系统是Linux for Tegra。使用std::max比使用17ms更有效。我需要在6毫秒内完成。你认为我错过了编译器的参数吗?非常感谢。我以前应该写过,但我的代码运行在四核ARM Cortex-A57上,操作系统是Linux for Tegra。使用std::max比使用17ms更有效。我需要在6毫秒内完成。你认为我错过了编译器的参数吗?