问题标签 [loop-unrolling]

For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.

0 投票
1 回答
1310 浏览

c++ - Unroll and Jam 对大循环的影响

当我编译我的应用程序时,性能比我预期的要差,我发现编译器正在报告如下所示的警告。

备注 #25461: 不完美的循环展开 - 被 2 卡住(前向量)

这是我的短代码。

索引变量 num 始终为 1024。为什么循环展开卡住不完美?另外,当我像下面这样修改代码时,不会发生 unroll-jamming!发生展开卡纸的条件是什么?

实际上,代码是大文件的一部分,所以我不能在这里写完整的代码。但下面是包含 func() 的代码。

0 投票
1 回答
691 浏览

fortran - fortran循环的simd向量长度和展开因子

我想用 SIMD 指令对下面的 fortran 进行矢量化

我使用了指令avx2。该程序由

然后我想VECTORLENGTH(n)SIMD. 如果没有这样的子句或 n = 2、4,则该信息不会提供有关展开因子的信息

如果 n = 8, 16, vectorization support: unroll factor set to 2.

我读过英特尔关于矢量化支持的文章:unroll factor set to xxxx所以我猜循环被展开为:

然后 2 X 进入向量寄存器, 2 W 进入另一个,做加法。但是 VECTORLENGTH 的值是如何起作用的呢?或者也许我真的不明白向量长度是什么意思。

而且由于我使用 avx2 指令,对于DOUBLE PRECISIONtype X,可以达到的最大长度是多少?

这是使用 SSE2、VL=8 的循环程序集的一部分,编译器告诉我展开因子是 2。但是它使用 4 个寄存器而不是 2 个。

0 投票
2 回答
112 浏览

c++ - 这种优化甚至重要吗?

页面推荐“循环展开”作为优化:

可以通过减少迭代次数和复制循环体来减少循环开销。

例子:

在下面的代码片段中,循环体可以复制一次,迭代次数可以从 100 次减少到 50 次。

下面是循环展开后的代码片段。

使用 GCC 5.2,除非您使用,否则不会启用循环展开-funroll-loops(在-O2或中均未启用-O3)。我已经检查了组件,看看是否有显着差异。

版本 1:

版本 2:

版本 2 产生更多的迭代。我错过了什么?

0 投票
1 回答
687 浏览

c - 展开循环并使用矢量化进行独立求和

对于以下循环,如果我告诉它使用关联数学,例如 with ,GCC 只会对循环进行矢量化-Ofast

这是与-Ofast -mavx

这清楚地表明循环已被矢量化。

但是这个循环也有一个依赖链。为了克服加法的延迟,我需要在 x86_64 上展开并至少进行 3 次部分求和(不包括需要展开 8 次的 Skylake 以及需要在 Haswell 和 Broadwell 上展开 10 次的 FMA 指令进行加法) . 据我了解,我可以使用-funroll-loops.

这是与-Ofast -mavx -funroll-loops.

GCC 确实展开了八次循环。但是,它不进行独立求和。它做了八个相关的和。这是没有意义的,也没有比不展开更好。

如何让 GCC 展开循环并进行独立的部分求和?


编辑:

即使没有 SSE,Clang 也会展开到四个独立的部分总和-funroll-loops,但我不确定它的 AVX 代码是否同样有效。无论如何编译器都不应该需要-funroll-loops-Ofast所以很高兴看到 Clang 至少对 SSE 这样做是正确的。

Clang 3.5.1 与-Ofast.

ICC 13.0.1-O3展开到两个独立的部分总和。ICC 显然只假设关联数学与-O3.

0 投票
1 回答
765 浏览

c++ - 如何在运行时实现 base 2 循环展开以进行优化

考虑一些需要重复执行 1-1,000,000 次的代码,并且在编译时不知道重复次数。据我了解,考虑到大量循环,循环展开将是一个可以忽略不计的优化,并且只会优化到编译时指定的 max_unrolls。我想出的想法是实现一个二进制(或基数 2)部分循环展开器,它基本上重复执行 some_function 在运行时指定的次数。我想出了一些代码来演示这个想法,下面显示了一个浓缩版本。从可用性的角度来看,以下代码中使用的方法存在许多问题。

  1. 它需要编码人员手动复制出 base 2 unroll 基本上复制出 unroll 2^n-1 次。
  2. 对于需要使用此方法的每个新功能,也需要重新执行此操作。

我的问题是三方面的。首先,我是否遗漏了一些东西,编译器是否已经足够智能,可以自己做这件事?for其次,什么是实现这一点的有效方法,以便在解决上述问题的同时将其与标准循环进行基准测试变得可行。第三,据您所知,有一个库已经实现了这一点。

请注意:我这样做纯粹是为了好玩,但没有专业知识知道这是否有效。我已经对代码进行了测试,但只发现了非常小的改进,但是我相信我无法手动展开足够远的距离来进行公平的比较。我也知道这种方法有可能创建大量的二进制大小,但是我相信这将是一个值得的时间内存权衡。此外,如果您发布任何程序集,我可能需要一年左右的时间才能理解它。

0 投票
2 回答
1205 浏览

cuda - 确定 CUDA 中#pragma unroll N 的最佳值

我了解如何#pragma unroll工作,但如果我有以下示例:

我想确定LIMIT上面内核中的最佳值,它将以x线程y数和块数启动。LIMIT可以是从2到的任何地方1<<20。由于 100 万对于变量来说似乎是一个非常大的数字(展开的 100 万个循环会导致寄存器压力,我不确定编译器是否会这样做),如果有的话,什么是“公平”数字?我如何确定这个限制?

0 投票
1 回答
137 浏览

c++ - 编译器(gcc)可以保证 c++ 循环吗?

我必须进行以下 AVX 操作:

这可以这样重写:

这在 gcc 4.9.1 中编译,尽管_mm256_shuffle_ps只接受立即整数值作为第三个参数。这意味着,thati被接受为立即数,因此意味着循环已展开。

所以我很好奇:这是编译器保证的,还是当优化标志被修改或 gcc 版本改变时会导致编译错误?使用其他编译器(msvc、icc、clang...)怎么样?

0 投票
2 回答
2399 浏览

c++ - SSE Intrinsics 和循环展开

我正在尝试优化一些循环并且我已经做到了,但我想知道我是否只是部分正确地完成了它。例如说我有这个循环:

将其展开 3 倍,得到:

现在是 SSE 翻译等价物:

或者是:

我对代码部分有点困惑:

这是做什么的?是否只是做额外的部分,例如,如果循环不能被您选择展开它的因素整除?谢谢你。

0 投票
2 回答
4573 浏览

c - 如何要求 GCC 完全展开这个循环(即剥离这个循环)?

有没有办法指示 GCC(我使用的是 4.8.4)完全展开底部函数中的循环while,即剥离这个循环?循环的迭代次数在编译时已知:58。

让我先解释一下我尝试过的方法。

通过检查 GAS 输出:

使用了 12 个寄存器 XMM0 - XMM11。如果我将标志传递-funroll-loops给 gcc:

循环仅展开两次。我检查了 GCC 优化选项。GCC 表示它-funroll-loops也会打开-frename-registers,因此当 GCC 展开循环时,它对寄存器分配的优先选择是使用“剩余”寄存器。但是 XMM12 - XMM15 只剩下 4 个,所以 GCC 最多只能展开 2 次。如果有 48 个而不是 16 个 XMM 寄存器可用,GCC 将毫无问题地展开 while 循环 4 次。

然而我又做了一个实验。我首先手动展开了两次 while 循环,获得了一个函数GEPDOT_2。那么两者之间完全没有区别

由于GEPDOT_2已用完所有寄存器,因此不执行展开。

GCC 确实注册了重命名以避免引入潜在的错误依赖。但我确信我的 ; 不会有这样的潜力GEPDOT;即使有,也不重要。我自己尝试展开循环,展开 4 次比展开 2 次快,比不展开快。当然,我可以手动展开更多次,但这很乏味。GCC 可以为我做这件事吗?谢谢。


更新 1

感谢@user3386109 的评论,我想稍微扩展一下这个问题。@user3386109 提出了一个非常好的问题。实际上,当有这么多并行指令要调度时,我确实对编译器优化寄存器分配的能力有些怀疑。

我个人认为一种可靠的方法是首先在asm内联汇编中对循环体(HPC 的关键)进行编码,然后根据需要多次复制它。今年早些时候我有一个不受欢迎的帖子:内联汇编。代码有点不同,因为循环迭代的次数 j 是一个函数参数,因此在编译时是未知的。在那种情况下,我无法完全展开循环,所以我只复制了两次汇编代码,并将循环转换为标签并跳转。事实证明,我编写的程序集的结果性能比编译器生成的程序集高约 5%,这可能表明编译器未能以我们预期的最​​佳方式分配寄存器。

我曾经(现在仍然)是汇编编码方面的婴儿,因此这是一个很好的案例研究,可以让我学习一点 x86 汇编。但从长远来看,我并不倾向于编写GEPDOT大量用于汇编的代码。主要有以下三个原因:

  1. asm内联汇编因不可移植而受到批评。虽然我不明白为什么。也许是因为不同的机器有不同的寄存器被破坏?
  2. 编译器也越来越好。所以我还是更喜欢算法优化和更好的 C 编码习惯来帮助编译器生成好的输出;
  3. 最后一个原因更重要。迭代次数可能并不总是 58。我正在开发一个高性能的矩阵分解子程序。对于缓存阻塞因子nb,迭代次数为nb-2。我不会nb像在之前的帖子中那样将其作为函数参数。这是一个机器特定的参数,将被定义为一个宏。因此迭代次数在编译时是已知的,但可能因机器而异。猜猜我在为各种nb. 因此,如果有一种方法可以简单地指示编译器剥离循环,那就太好了。

如果您还可以分享一些生产高性能但可移植的库的经验,我将不胜感激。

0 投票
1 回答
1092 浏览

assembly - 使用循环展开计算正数、负数和零数的最有效方法

假设我有以下指令,只需检查一个数字是否为正数(负数或零),如果它是正数,则将 1 添加到我们的计数器(我们不在乎数字是负数还是零)。我可以通过一个简单的循环展开来做到这一点:

我的问题是,如果我还想检查是否为零或负数,如何获得相同的有效结构。在这种情况下,我将有三个计数器(一个用于 pos,一个用于 neg,一个用于 0) 一种低效的方法是这样的。我正在尝试制作与前一个示例相同的结构(我们将正数存储在 中%rax,负数存储在 中%rbx,零存储在 中%rcx):