- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
在这个简单的例子中使用有符号和无符号循环计数器之间的差异让我感到非常惊讶:
double const* a;
__assume_aligned(a, 64);
double s = 0.0;
//for ( unsigned int i = 0; i < 1024*1024; i++ )
for ( int i = 0; i < 1024*1024; i++ )
{
s += a[i];
}
在签名的情况下,生成了 icc 19.0.0(我显示了循环的展开部分):
..B1.2:
vaddpd zmm7, zmm7, ZMMWORD PTR [rdi+rax*8]
vaddpd zmm6, zmm6, ZMMWORD PTR [64+rdi+rax*8]
vaddpd zmm5, zmm5, ZMMWORD PTR [128+rdi+rax*8]
vaddpd zmm4, zmm4, ZMMWORD PTR [192+rdi+rax*8]
vaddpd zmm3, zmm3, ZMMWORD PTR [256+rdi+rax*8]
vaddpd zmm2, zmm2, ZMMWORD PTR [320+rdi+rax*8]
vaddpd zmm1, zmm1, ZMMWORD PTR [384+rdi+rax*8]
vaddpd zmm0, zmm0, ZMMWORD PTR [448+rdi+rax*8]
add rax, 64
cmp rax, 1048576
jb ..B1.2 # Prob 99%
在无符号情况下,icc 使用额外的寄存器来寻址内存,以及相应的LEA
:
..B1.2:
lea edx, DWORD PTR [8+rax]
vaddpd zmm6, zmm6, ZMMWORD PTR [rdi+rdx*8]
lea ecx, DWORD PTR [16+rax]
vaddpd zmm5, zmm5, ZMMWORD PTR [rdi+rcx*8]
vaddpd zmm7, zmm7, ZMMWORD PTR [rdi+rax*8]
lea esi, DWORD PTR [24+rax]
vaddpd zmm4, zmm4, ZMMWORD PTR [rdi+rsi*8]
lea r8d, DWORD PTR [32+rax]
vaddpd zmm3, zmm3, ZMMWORD PTR [rdi+r8*8]
lea r9d, DWORD PTR [40+rax]
vaddpd zmm2, zmm2, ZMMWORD PTR [rdi+r9*8]
lea r10d, DWORD PTR [48+rax]
vaddpd zmm1, zmm1, ZMMWORD PTR [rdi+r10*8]
lea r11d, DWORD PTR [56+rax]
add eax, 64
vaddpd zmm0, zmm0, ZMMWORD PTR [rdi+r11*8]
cmp eax, 1048576
jb ..B1.2 # Prob 99%
对我来说,令人惊讶的是它没有生成相同的代码(给定编译时循环计数)。是编译器优化问题吗?
编译选项:-O3 -march=skylake-avx512 -mtune=skylake-avx512 -qopt-zmm-usage=high
最佳答案
这是 ICC 愚蠢的错过优化。它不是 AVX512 特有的;使用默认/通用架构设置仍然会发生这种情况。
lea ecx, DWORD PTR [16+rax]
正在计算i+16
作为展开的一部分,截断为 32 位(32 位操作数大小)并将零扩展为 64 位(写入 32 位寄存器时在 x86-64 中隐式)。这显式地实现了类型宽度上无符号环绕的语义。
gcc 和 clang 可以毫无问题地证明 unsigned i
不会换行,因此他们可以优化从 32 位无符号到 64 位指针宽度的零扩展,以便在寻址模式中使用,因为循环上限已知1。
回想一下,无符号环绕在 C 和 C++ 中是明确定义的,但有符号溢出是未定义的行为。这意味着有符号变量可以提升为指针宽度,并且编译器不必在每次将它们用作数组索引时重新将符号扩展为指针宽度。 ( a[i]
相当于 *(a+i)
,并且将整数添加到指针的规则意味着对于寄存器的高位可能不匹配的窄值,符号扩展是必要的。)
有符号溢出 UB 是 ICC 能够正确优化有符号计数器的原因,即使它无法使用范围信息。另请参阅http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html (关于未定义的行为)。请注意,它使用 add rax, 64
和cmp
具有 64 位操作数大小(RAX 而不是 EAX)
我将您的代码放入 MCVE 中以使用其他编译器进行测试。 __assume_aligned
仅限 ICC,所以我使用了 GNU C __builtin_assume_aligned
.
#define COUNTER_TYPE unsigned
double sum(const double *a) {
a = __builtin_assume_aligned(a, 64);
double s = 0.0;
for ( COUNTER_TYPE i = 0; i < 1024*1024; i++ )
s += a[i];
return s;
}
clang 像这样编译你的函数 ( Godbolt compiler explorer ):
# clang 7.0 -O3
sum: # @sum
xorpd xmm0, xmm0
xor eax, eax
xorpd xmm1, xmm1
.LBB0_1: # =>This Inner Loop Header: Depth=1
addpd xmm0, xmmword ptr [rdi + 8*rax]
addpd xmm1, xmmword ptr [rdi + 8*rax + 16]
addpd xmm0, xmmword ptr [rdi + 8*rax + 32]
addpd xmm1, xmmword ptr [rdi + 8*rax + 48]
addpd xmm0, xmmword ptr [rdi + 8*rax + 64]
addpd xmm1, xmmword ptr [rdi + 8*rax + 80]
addpd xmm0, xmmword ptr [rdi + 8*rax + 96]
addpd xmm1, xmmword ptr [rdi + 8*rax + 112]
add rax, 16 # 64-bit loop counter
cmp rax, 1048576
jne .LBB0_1
addpd xmm1, xmm0
movapd xmm0, xmm1 # horizontal sum
movhlps xmm0, xmm1 # xmm0 = xmm1[1],xmm0[1]
addpd xmm0, xmm1
ret
我没有启用 AVX,这不会改变循环结构。请注意,clang 仅使用 2 个向量累加器,因此如果 L1d 缓存中的数据很热,它将成为最新 CPU 上 FP 添加延迟的瓶颈。 Skylake 最多可容纳 8 个 addpd
立即飞行(每个时钟吞吐量 2 个,具有 4 个周期延迟)。因此,对于(某些)数据在 L2 或尤其是 L1d 缓存中很热的情况,ICC 可以做得更好。
奇怪的是,clang 没有使用指针增量,如果它无论如何都会添加/cmp。在循环之前只需要几个额外的指令,并且会简化寻址模式,即使在 Sandybridge 上也允许负载的微融合。 (但它不是 AVX,因此 Haswell 及更高版本可以保持负载微熔断。Micro fusion and addressing modes)。 GCC 会这样做,但根本不展开,这是 GCC 的默认设置,没有配置文件引导优化。
无论如何,ICC 的 AVX512 代码将取消层压到单独的加载中,并在问题/重命名阶段添加 uops(或者在添加到 IDQ 之前,我不确定)。因此,它不使用指针增量来节省前端带宽,为更大的乱序窗口消耗更少的 ROB 空间,并且对超线程更加友好,这是非常愚蠢的。
<小时/>脚注1:
(即使不是,没有像 volatile
或 atomic
访问这样的副作用的无限循环是未定义的行为,所以即使使用 i <= n
和运行时变量 n
,编译器可以假设循环不是无限的,因此 i
没有换行。Is while(1); undefined behavior in C? )
在实践中,gcc 和 clang 不会利用这一点,并创建一个实际上可能无限的循环,并且不会因为这种可能的怪异而自动矢量化。所以避免i <= n
带有运行时变量 n
,特别是对于无符号比较。使用i < n
相反。
如果展开,i += 2
可以起到类似的效果。
因此,在源代码中执行结束指针和指针增量通常是好的,因为这通常对于汇编来说是最佳的。
关于assembly - 有符号或无符号循环计数器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52862923/
我在leetcode上看到这段代码,是一道求众数的题,下面是题目描述: 给定一个大小为 n 的数组,找到多数元素。众数元素是出现次数超过 ⌊ n/2 ⌋ 次的元素。 你可以假设数组是非空的并且多数元素
每次在 JavaScript 中执行特定操作时,例如: $(function() { $('#typing').keyup(function () { switch($(this)
我一直在为网页设计一个计数器,但我一直被这个我无法解决的功能所困扰。 我有一个 4 个 div 的计数器,因为其中两个是小数字,另外两个是大数字,所以第一个运行得很快,我看不到它们的功能。 有人知道如
我已经在文档中进行了一些搜索,并在网上花了一段时间,但找不到解决方案!我希望警报告诉我单击 .thumb 时它处于each() 的哪一次迭代。 EG:有六个.thumb,我点击数字3,浏览器弹出3!
在 Handlebars 中,假设我有 names 的集合.我能怎么做 {{#each names}} {{position}} {{name}} {{/each}} 在哪里 {{position}}
这个问题在这里已经有了答案: Numbering rows within groups in a data frame (9 个回答) 4年前关闭。 我们如何在数据帧的每组中生成唯一的 ID 号?以下
我正在努力解决以下问题。我希望为给定的“一”序列创建一个计数器。例如,我有以下内容: 1 1 1 1 0 0 1 1 1 0 0 1 1 1 1 鉴于该序列,我希望为 1 的每个序列设置一个计数器直到
我正在努力解决以下问题。我希望为给定的“一”序列创建一个计数器。例如,我有以下内容: 1 1 1 1 0 0 1 1 1 0 0 1 1 1 1 鉴于该序列,我希望为 1 的每个序列设置一个计数器直到
我有一个jsfiddle here 这是一个简单的 JavaScript 函数,可以计算出设定的数字。 是否可以进行这种计数,但也保留一位小数 所以它算 1.1、1.2、1.3 等。 func
我正在构建一个计数器,当我按下鼠标时,它应该增加到 maxValue 并且减少不超过 0。我还可以选择将计数器重置为其初始值:0。另外,如果 maxValue 是偶数,它应该计数到该数字。但是,如果
所以我成功地为字母和单词构建了其他计数器,但现在我只能用这个来计算句子。我的代码如下,当我运行它时,它会返回很多错误消息: #include #include #include int main
Closed. This question is off-topic。它当前不接受答案。
我需要一个计数器,它会随着某些任务的完成而递增。我们只需要最后一小时的值,即窗口将移动而不是静态时间。 解决此问题的最佳方法是什么?我能想到的一种方法是拥有一个大小为 60 的数组,每分钟一个,并更新
我希望使用计数器来为我提供独特的引用系统。我想单击一个按钮,然后检查一个字段/文件中的最后一个数字,然后简单地向其添加 1,然后将其插入到屏幕上的字段中? 不确定执行此操作的最佳方法或具体如何执行此操
我有一个用 php 制作的表格,在该表格内我显示了数据库中的一些内容。我在每个 td 中创建了一个简单的按钮(类似于 Like),我希望每次点击它都会增加 1。这是带有按钮的行: echo "
如何将数据库中的值转换为可用于 if else 函数的 int 值? 例如:在我的数据库“armnumber = 3”中,如何在 if else 函数中使用它? 代码 string myConnect
我需要生成唯一的“ids”,问题是,它只能在 1 - 99999 之间。 “好”的是,它仅在与另一列组合时必须是唯一的。 我们有组,每个组都有自己的“group_id”,每个组都需要类似 unique
有这个简单的代码: UPDATE counter SET c= c +1 where id = 1; 并且它在开头的 c 字段中为 null 的情况下不起作用。它只有在已经输入了一些数字时才有效,也就
我正在尝试在 python 中构建一个具有闭包属性的计数器。以下工作中的代码: def generate_counter(): CNT = [0] def add_one():
我使用 CSS 来计算 HTML 文档中的部分: body {counter-reset: sect;} section:before { counter-increment: sect;
我是一名优秀的程序员,十分优秀!