草庐IT

c++ - 通过索引集对非连续元素进行矢量化

coder 2024-02-23 原文

矢量化的标准模板似乎是这样的:

#define N 100
double arr[N];
double func(int i);

for(int i = 0; i <N; i++)
    arr[i] = func(i);

连续访问所有索引的位置。

但是,我遇到的情况不是 arr 的所有 N 元素都需要更新。我的模板如下:

#define N 100
double arr[N];
double func(int i);

int indexset[N];//this indexset has the indices of arr[] that get updated
int number_in_index_set;
//E.g., if I only need to update arr[4] and arr[10], number_in_index_set = 2
//indexset[0] = 4 and indexset[1] = 10

for(int i = 0; i <number_in_index_set; i++)
    arr[indexset[i]] = func(indexset[i]);

在这种情况下,Intel Advisor 报告此循环未矢量化,因为无法在执行循环之前计算循环迭代次数。在我的应用程序中,这个循环是针对不同的索引子集执行的,对于每个这样的子集,number_in_index_setindexset[] 都会相应地改变。

我有两个问题:

(1)有问题的循环甚至被矢量化意味着什么?数组索引不是连续的,那么编译器将如何对循环进行矢量化?

(2) 假设矢量化是可能的,正如 Intel Advisor 似乎暗示的那样,如何才能安全地将所讨论的循环矢量化?因此,Intel Advisor 的建议是:

For Example 1, where the loop iteration count is not available before the loop 

executes: If the loop iteration count and iterations lower bound can be calculated 

for the whole loop:
    Move the calculation outside the loop using an additional variable.
    Rewrite the loop to avoid goto statements or other early exits from the loop.
    Identify the loop iterations lower bound using a constant.
For example, introduce the new limit variable:
void foo(float *A) {
  int i;
  int OuterCount = 90;
  int limit = bar(int(A[0]));
  while (OuterCount > 0) {
    for (i = 1; i < limit; i++) {
      A[i] = i + 4;
    }
    OuterCount--;
  }
}
For Example 2, where the compiler cannot determine if there is aliasing between all 

the pointers used inside the loop and loop boundaries: Assign the loop boundary value 

to a local variable. In most cases, this is enough for the compiler to determine 

aliasing may not occur.
You can use a directive to accomplish the same thing automatically.
Target  ICL/ICC/ICPC Directive
Source loop     #pragma simd or #pragma omp simd
Do not use global variables or indirect accesses as loop boundaries unless you also 

use one of the following:
    Directive to ignore vector dependencies
    Target  ICL/ICC/ICPC Directive
    Source loop     #pragma ivdep
    restrict
    keyword.

具体来说,上述哪些建议可以保证安全矢量化?

最佳答案

根据补充的意见,假设你要优化的功能如下:

void euclidean(double x0, double y0, const double *x, const double* y,
               const size_t *index, size_t index_size, double *result)
{
    for (size_t i = 0; i < index_size; ++i) {
        double dx = x0 - x[index[i]];
        double dy = y0 - y[index[i]];
        result[index[i]] = sqrt(dx*dx + dy * dy);
    }
}

由于您的目标是奔腾,因此只有 SSE2 SIMD 指令可用。您可以尝试以下优化功能(也可用 here ):

void euclidean_sse2(double x0, double y0, const double *x, const double* y,
               const size_t *index, size_t index_size, double *result)
{
    __m128d vx0 = _mm_set1_pd(x0);
    __m128d vy0 = _mm_set1_pd(y0);
    for (size_t i = 0; i + 1 < index_size; i += 2) { // process 2 at a time
        size_t i0 = index[i];
        size_t i1 = index[i+1];
        __m128d vx = _mm_set_pd(x[i1], x[i0]);  // load 2 scattered elements
        __m128d vy = _mm_set_pd(y[i1], y[i0]);  // load 2 scattered elements
        __m128d vdx = _mm_sub_pd(vx, vx0);
        __m128d vdy = _mm_sub_pd(vy, vy0);
        __m128d vr = _mm_sqrt_pd(_mm_add_pd(_mm_mul_pd(vdx, vdx), _mm_mul_pd(vdy, vdy)));
        _mm_store_sd(result + i0, vr);
        _mm_storeh_pd(result + i1, vr);
    }
    if (index_size & 1) {  // if index_size is odd, there is one more element to process
        size_t i0 = index[index_size-1];
        double dx = x0 - x[i0];
        double dy = y0 - y[i0];
        result[i0] = sqrt(dx*dx + dy * dy);
    }
}

这里的加载和存储操作不是向量化的,即我们正在一个一个地加载 xy 并且我们正在存储到 result逐个。所有其他操作都是向量化的。

对于 gcc,主循环体的汇编器输出如下。

    // scalar load operations
    mov     r10, QWORD PTR [rax]
    mov     r9, QWORD PTR [rax+8]
    add     rax, 16
    movsd   xmm3, QWORD PTR [rdi+r10*8]
    movsd   xmm2, QWORD PTR [rsi+r10*8]
    movhpd  xmm3, QWORD PTR [rdi+r9*8]
    movhpd  xmm2, QWORD PTR [rsi+r9*8]
    // vectorial operations
    subpd   xmm3, xmm4
    subpd   xmm2, xmm5
    mulpd   xmm3, xmm3
    mulpd   xmm2, xmm2
    addpd   xmm2, xmm3
    sqrtpd  xmm2, xmm2
    // scalar store operations
    movlpd  QWORD PTR [r8+r10*8], xmm2
    movhpd  QWORD PTR [r8+r9*8], xmm2

您还可以进行一些循环展开(这可能是编译器向量化器会做的)。但在这种情况下,循环体相当大,并且可能受内存 I/O 限制,所以我认为它不会有太大帮助。

如果对这些函数的含义不清楚,可以阅读this .

关于矢量化的全面讨论是 here .简而言之,现代 cpu 除了常规寄存器外还提供 vector 寄存器。根据机器架构的不同,我们有 128 位寄存器(SSE 指令)、256 位寄存器(AVX 指令)、512 位寄存器(AVX512 指令)。矢量化意味着充分利用这些寄存器。例如,如果我们有一个 vector double {x0, x1, x2, …. xn},我们想把它加到常量k上,得到{x0+k, x1+k, x2+k, …. xn+k},在标量模式下,操作分解为读取x(i),添加k,存储结果。在 vector 模式下,使用 SSE,看起来像是同时读取 x[i]x[i+1],同时添加 k,同时存储 2 个结果。 如果元素在内存中不连续,我们不能同时加载x[i]和x[i+1],也不能同时存储结果,但至少我们可以同时进行这两个加法。

关于c++ - 通过索引集对非连续元素进行矢量化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53246116/

有关c++ - 通过索引集对非连续元素进行矢量化的更多相关文章

  1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  3. ruby - 如何以所有可能的方式将字符串拆分为长度最多为 3 的连续子字符串? - 2

    我试图获取一个长度在1到10之间的字符串,并输出将字符串分解为大小为1、2或3的连续子字符串的所有可能方式。例如:输入:123456将整数分割成单个字符,然后继续查找组合。该代码将返回以下所有数组。[1,2,3,4,5,6][12,3,4,5,6][1,23,4,5,6][1,2,34,5,6][1,2,3,45,6][1,2,3,4,56][12,34,5,6][12,3,45,6][12,3,4,56][1,23,45,6][1,2,34,56][1,23,4,56][12,34,56][123,4,5,6][1,234,5,6][1,2,345,6][1,2,3,456][123

  4. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

    尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

  5. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  6. ruby-on-rails - 如何优雅地重启 thin + nginx? - 2

    我的瘦服务器配置了nginx,我的ROR应用程序正在它们上运行。在我发布代码更新时运行thinrestart会给我的应用程序带来一些停机时间。我试图弄清楚如何优雅地重启正在运行的Thin实例,但找不到好的解决方案。有没有人能做到这一点? 最佳答案 #Restartjustthethinserverdescribedbythatconfigsudothin-C/etc/thin/mysite.ymlrestartNginx将继续运行并代理请求。如果您将Nginx设置为使用多个上游服务器,例如server{listen80;server

  7. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

    我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

  8. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  9. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

    我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

  10. ruby-on-rails - Enumerator.new 如何处理已通过的 block ? - 2

    我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m

随机推荐