- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我最近一直在学习 SIMD in assembly (x86_64),并且有一些意想不到的结果。归结为以下内容。
我有两个循环运行多次的程序。第一个程序包含一个执行 4 条 SIMD 指令的循环,第二个程序包含这个完全相同的循环和一个额外的指令。代码如下所示:
第一个程序:
section .bss
doublestorage: resb 8
section .text
global _start
_start:
mov rax, 0x0000000100000001
mov [doublestorage], rax
cvtpi2pd xmm1, [doublestorage]
cvtpi2pd xmm2, [doublestorage]
cvtpi2pd xmm3, [doublestorage]
cvtpi2pd xmm4, [doublestorage]
cvtpi2pd xmm5, [doublestorage]
cvtpi2pd xmm6, [doublestorage]
cvtpi2pd xmm7, [doublestorage]
mov rax, (1 << 31)
loop:
movupd xmm1, xmm3
movupd xmm2, xmm5
divpd xmm1, xmm2
addpd xmm4, xmm1
dec rax
jnz loop
mov rax, 60
mov rdi, 0
syscall
第二个程序:
section .bss
doublestorage: resb 8
section .text
global _start
_start:
mov rax, 0x0000000100000001
mov [doublestorage], rax
cvtpi2pd xmm1, [doublestorage]
cvtpi2pd xmm2, [doublestorage]
cvtpi2pd xmm3, [doublestorage]
cvtpi2pd xmm4, [doublestorage]
cvtpi2pd xmm5, [doublestorage]
cvtpi2pd xmm6, [doublestorage]
cvtpi2pd xmm7, [doublestorage]
mov rax, (1 << 31)
loop:
movupd xmm1, xmm3
movupd xmm2, xmm5
divpd xmm1, xmm2
addpd xmm4, xmm1
movupd xmm6, xmm7
dec rax
jnz loop
mov rax, 60
mov rdi, 0
syscall
现在,我的想法是:第二个程序有更多的指令要执行,因此执行时间会长得多。但是,如果我对两个程序都计时,那么第二个程序比第一个程序花费的时间更少。我总共运行了这两个程序 100 次,结果是:
Runtime first program: mean: 5.6129 s, standard deviation: 0.0156 s
Runtime second program: mean: 5.5056 s, standard deviation: 0.0147 s
我得出结论,第二个程序运行得相当快。这些结果对我来说似乎违反直觉,所以我想知道造成这种行为的原因是什么。
为了完整起见,我正在运行 Ubuntu 15.10 和 NASM 编译器 (-elf64),并使用 Intel Core i7-5600。另外,我检查了反汇编,编译器没有进行任何优化:
第一个程序的Objdump:
exec/instr4: file format elf64-x86-64
Disassembly of section .text:
00000000004000b0 <.text>:
4000b0: 48 b8 01 00 00 00 01 movabs $0x100000001,%rax
4000b7: 00 00 00
4000ba: 48 89 04 25 28 01 60 mov %rax,0x600128
4000c1: 00
4000c2: 66 0f 2a 0c 25 28 01 cvtpi2pd 0x600128,%xmm1
4000c9: 60 00
4000cb: 66 0f 2a 14 25 28 01 cvtpi2pd 0x600128,%xmm2
4000d2: 60 00
4000d4: 66 0f 2a 1c 25 28 01 cvtpi2pd 0x600128,%xmm3
4000db: 60 00
4000dd: 66 0f 2a 24 25 28 01 cvtpi2pd 0x600128,%xmm4
4000e4: 60 00
4000e6: 66 0f 2a 2c 25 28 01 cvtpi2pd 0x600128,%xmm5
4000ed: 60 00
4000ef: 66 0f 2a 34 25 28 01 cvtpi2pd 0x600128,%xmm6
4000f6: 60 00
4000f8: 66 0f 2a 3c 25 28 01 cvtpi2pd 0x600128,%xmm7
4000ff: 60 00
400101: b8 00 00 00 80 mov $0x80000000,%eax
400106: 66 0f 10 cb movupd %xmm3,%xmm1
40010a: 66 0f 10 d5 movupd %xmm5,%xmm2
40010e: 66 0f 5e ca divpd %xmm2,%xmm1
400112: 66 0f 58 e1 addpd %xmm1,%xmm4
400116: 48 ff c8 dec %rax
400119: 75 eb jne 0x400106
40011b: b8 3c 00 00 00 mov $0x3c,%eax
400120: bf 00 00 00 00 mov $0x0,%edi
400125: 0f 05 syscall
第二个程序的Objdump:
exec/instr5: file format elf64-x86-64
Disassembly of section .text:
00000000004000b0 <.text>:
4000b0: 48 b8 01 00 00 00 01 movabs $0x100000001,%rax
4000b7: 00 00 00
4000ba: 48 89 04 25 2c 01 60 mov %rax,0x60012c
4000c1: 00
4000c2: 66 0f 2a 0c 25 2c 01 cvtpi2pd 0x60012c,%xmm1
4000c9: 60 00
4000cb: 66 0f 2a 14 25 2c 01 cvtpi2pd 0x60012c,%xmm2
4000d2: 60 00
4000d4: 66 0f 2a 1c 25 2c 01 cvtpi2pd 0x60012c,%xmm3
4000db: 60 00
4000dd: 66 0f 2a 24 25 2c 01 cvtpi2pd 0x60012c,%xmm4
4000e4: 60 00
4000e6: 66 0f 2a 2c 25 2c 01 cvtpi2pd 0x60012c,%xmm5
4000ed: 60 00
4000ef: 66 0f 2a 34 25 2c 01 cvtpi2pd 0x60012c,%xmm6
4000f6: 60 00
4000f8: 66 0f 2a 3c 25 2c 01 cvtpi2pd 0x60012c,%xmm7
4000ff: 60 00
400101: b8 00 00 00 80 mov $0x80000000,%eax
400106: 66 0f 10 cb movupd %xmm3,%xmm1
40010a: 66 0f 10 d5 movupd %xmm5,%xmm2
40010e: 66 0f 5e ca divpd %xmm2,%xmm1
400112: 66 0f 58 e1 addpd %xmm1,%xmm4
400116: 66 0f 10 f7 movupd %xmm7,%xmm6
40011a: 48 ff c8 dec %rax
40011d: 75 e7 jne 0x400106
40011f: b8 3c 00 00 00 mov $0x3c,%eax
400124: bf 00 00 00 00 mov $0x0,%edi
400129: 0f 05 syscall
最佳答案
有 no such thing as an "i7 5600" .我假设你的意思是 i7 5600U ,这是一款低功耗 (15W TDO) CPU,具有 2.6GHz 基础频率/3.2GHz 涡轮频率。
你能仔细检查一下这是否可重现吗?确保 CPU 时钟速度在两次测试中保持恒定,因为您的低功耗 CPU 可能无法在除法单元一直忙碌的情况下全速运行。
也许对于使用性能计数器(例如 perf stat ./a.out
)进行测试也很有用,测量核心时钟周期。 (不是“引用”周期。您想计算时钟实际运行的实际周期。)
IACA最多只支持 Haswell。除了两个循环的每次迭代 14c 之外,它没有说明任何其他内容,这会成为除法器吞吐量的瓶颈。 (Agner Fog 对 divpd
的测量值为 one per 8-14c throughput on Haswell, one per 8c on Broadwell。)
有a recent question about broadwell throughput ,但这是关于使前端饱和。
这个循环应该纯粹是 divpd
吞吐量 (one per 8c on Broadwell) 的瓶颈。如果效果是真实的,我想出的唯一解释是 movupd
insns 之一并不总是被消除,而是从 divpd
中窃取 p0 用于有时一个循环。
循环中的三个未融合域微指令都在不同的端口上运行,因此它们不可能相互延迟。 (p0 上的 divpd
,p1 上的 addpd
,p6 上的 predicted-taken fused cmp/jcc
)。
实际上,即使是该理论也站不住脚。未消除的 movaps xmm,xmm
使用 Broadwell 上的端口 5。我假设 movupd xmm,xmm
的奇怪选择也解码为 port5 uop。 (Agner Fog 甚至没有列出 movups
/movupd
的 reg-reg 形式的条目,因为每个人总是使用 movaps
。或者 movapd
如果他们喜欢将 insn 类型与数据匹配,即使它长一个字节并且现有的 uarch 不关心 s
与 d
的旁路延迟,只有 movaps
用于 float/double 和 movdqa
用于整数。)
有趣的是,我的 2.4GHz E6600(Conroe/merom 微架构)在 4.5 秒内运行您的循环。 Agner Fog 的表格将 Merom 上的 divpd
列为 5-31c 中的一个。 1.0/1.0
大概发生在 5c 中。 Sandybridge 的 best-case divide 比 Nehalem 慢得多。只有使用 Skylake 时,最佳情况下的吞吐量才能像 Merom 一样快速下降。 (对于 128b divpd
,吞吐量固定为每 4c 一个)。
顺便说一句,这是您的代码版本,它使用一些更常规的方法在 regs 中设置 FP 数据:
default REL ; use RIP-relative for access to globals by default, so you don't have to write `[rel my_global]`
section .rodata
; ALIGN 16 ; only desirable if we're doing a 128b load instead of a 64b broadcast-load
one_dp: dq 1.0
section .text
global _start
_start:
mov rax, 0x0000000100000001
mov [rsp-16], rax ; if you're going to store/reload, use the stack for scratch space, not a static location that will probably cache-miss.
cvtpi2pd xmm1, [rsp-16] ; this is the form with xmm, mm/m64 args. Interestingly, for the memory operand form, this should perform the same but saves a byte in the encoding.
cvtdq2pd xmm8, [rsp-16] ; this is the "normal" insn, with xmm, xmm/m64 args.
movq xmm9, rax
cvtdq2pd xmm9, xmm9
; Fun fact: 64bit int <-> FP is only available for scalar until AVX-512 introduces packed conversions for qword integer vectors.
;mov eax, 1 ; still 1 from earlier
cvtsi2sd xmm2, eax
unpcklpd xmm2, xmm2 ; broadcast
pcmpeqw xmm3,xmm3 ; generate the constant on the fly, from Agner Fog's asm guide
psllq xmm3, 54
psrlq xmm3, 2 ; (double)1.0 in both halves.
movaps xmm4, xmm3 ; duplicate the data instead of re-doing the conversion. Shorter and cheaper.
movaps xmm5, xmm3
movaps xmm6, xmm3
;movaps xmm7, [ones] ; load 128b constant
movddup xmm7, [one_dp] ; broadcast-load
mov eax, (1 << 31) ; 1<<31 fits in a 32bit reg just fine.
;IACA_start
.loop:
;movupd xmm1, xmm3
;movupd xmm2, xmm5
movaps xmm1, xmm3 ; prob. no perf diff, but weird to see movupd used for reg-reg moves
movaps xmm2, xmm5
divpd xmm1, xmm2
addpd xmm4, xmm1
;movaps xmm6, xmm7
dec eax
jnz .loop
;IACA_end
mov eax, 60
xor edi,edi
syscall ; exit(0)
对于 IACA,我使用了:
%macro IACA_start 0
mov ebx, 111
db 0x64, 0x67, 0x90
%endmacro
%macro IACA_end 0
mov ebx, 222
db 0x64, 0x67, 0x90
%endmacro
.loop:
movapd xmm1, xmm3 ; replace the only operand that div writes
divpd xmm1, xmm2
addpd xmm4, xmm1
dec eax
jnz .loop
然后循环只有 4 个融合域微指令。应该使差异为零,因为 divpd
吞吐量应该仍然是唯一的瓶颈。
或使用 AVX:
vdivpd xmm0, xmm1, xmm2
vaddpd xmm4, xmm4, xmm0
cmp/jcc
关于loops - 时间性能 SIMD 汇编程序 : longer loop executes faster,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36652462/
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 想改进这个问题?将问题更新为 on-topic对于堆栈溢出。 7年前关闭。 Improve this qu
我有一个代码库,我可以在我的 mac 上编译和运行,但不能在我的远程 linux 机器上编译和运行,我不确定为什么。 编译时出现错误 fatal error: simd/simd.h: No such
我需要了解如何编写一些可并行化问题的 C++ 跨平台实现,以便在可用的情况下利用 SIMD(SSE、SPU 等)。以及我希望能够在运行时在 SIMD 和非 SIMD 之间切换。 您建议我如何解决这个问
我正在使用 AVX 内在 _mm256_extract_epi32() . 不过,我不完全确定我是否正确使用它,因为 gcc 不喜欢我的代码,而 clang 编译它并运行它没有问题。 我根据整数变量的
当我可以使用 SSE3 或 AVX 时,SSE2 或 MMX 等较旧的 SSE 版本是否可用 - 还是我还需要单独检查它们? 最佳答案 一般来说,这些都是附加的,但请记住,多年来英特尔和 AMD 对这
在 godbolt.org 使用 gcc 7.2 我可以看到以下内容 code在汇编程序中翻译得非常好。我看到 1 次加载、1 次添加和 1 次存储。 #include __attribute__(
假设我们有一个函数将两个数组相乘,每个数组有 1000000 个 double 值。在 C/C++ 中,该函数如下所示: void mul_c(double* a, double* b) {
我有一个 A = a1 a2 a3 a4 b1 b2 b3 b4 c1 c2 c3 c4 d1 d2 d3 d4 我有两排, float32x2_t a = a1 a2 flo
我正在考虑编写一个 SIMD vector 数学库,因此作为一个快速基准,我编写了一个程序,该程序执行 1 亿(4 个 float ) vector 元素乘法并将它们加到累积总数中。对于我的经典非 S
我正在开发带有英特尔编译器 OpenMP 4.0 的英特尔 E5(6 核、12 线程) 为什么这段代码 SIMD 编译比并行 SIMD 编译更快? for (int suppv = 0; suppv
OpenMP 4.0 引入了 SIMD 结构以利用 CPU 的 SIMD 指令。根据规范http://www.openmp.org/mp-documents/OpenMP4.0.0.pdf ,有两种结
英特尔编译器允许我们通过以下方式对循环进行矢量化 #pragma simd for ( ... ) 但是,您也可以选择使用 OpenMP 4 的指令执行此操作: #pragma omp simd fo
关注我的 x86 question ,我想知道如何在 Arm-v8 上有效地矢量化以下代码: static inline uint64_t Compress8x7bit(uint64_t x) {
Intel 提供了几个 SIMD 命令,它们似乎都对 128 位数据执行按位异或: _mm_xor_pd(__m128d, __m128d) _mm_xor_ps(__m128, __m128) _m
可以使用“位打包”技术压缩无符号整数:在一个无符号整数 block 中,只存储有效位,当一个 block 中的所有整数都“小”时,会导致数据压缩。该方法称为 FOR (引用框架)。 有SIMD lib
SSE 寄存器是否在逻辑处理器(超线程)之间共享或复制? 对于 SSE 繁重的程序,我能否期望从并行化中获得与普通程序相同的加速(英特尔声称具有超线程的处理器为 30%)? 最佳答案 从英特尔的文档中
我正在编写一个使用 SSE 指令来乘法和相加整数值的程序。我用浮点数做了同样的程序,但我的整数版本缺少一个指令。 使用浮点数,在完成所有操作后,我将 de 值返回到常规浮点数数组,执行以下操作: _m
我正在开发基于Intel指令集(AVX,FMA等)的高性能算法。当数据按顺序存储时,我的算法(内核)运行良好。但是,现在我面临一个大问题,但没有找到解决方法或解决方案: see 2D Matrix i
大家好 :) 我正在尝试了解有关浮点、SIMD/数学内在函数和 gcc 的快速数学标志的一些概念。更具体地说,我在 x86 cpu 上使用 MinGW 和 gcc v4.5.0。 我已经搜索了一段时间
根据https://sourceware.org/glibc/wiki/libmvec GCC 具有数学函数的向量实现。它们可以被编译器用于优化,可以在这个例子中看到:https://godbolt.
我是一名优秀的程序员,十分优秀!