- 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/
我认为这样的表达式会导致 Haskell 永远评估。但是 GHCi 和编译程序中的行为让我感到惊讶。 例如,在 GHCi 中,这些表达式一直阻塞到 I Control+C ,但不消耗 CPU。看起来像
就目前情况而言,这个问题不太适合我们的问答形式。我们希望答案得到事实、引用资料或专业知识的支持,但这个问题可能会引发辩论、争论、民意调查或扩展讨论。如果您觉得这个问题可以改进并可能重新开放,visit
如果可以的话,我想减少这段代码: class Alarm { internal static void isGreaterThanOrBelowValue(int min, int max,
我有以下问题: 我想创建一个批处理文件,循环访问一定数量的 IP 地址,以停止远程 PC 上的某个服务。 因为停止过程需要一些时间,所以我需要第二个循环来查询服务的状态并等待,直到服务达到“已停止”状
我已经完整地编写了“The Rust Programming Language”在线书籍中的程序,chapter 2 .我还进一步开发了它:通过添加一个简单的问题/响应,用户可以通过输入“y”再次玩游
这个人已经困扰了我一阵子了, 我们应该如何在集合中存储值或在for循环中映射? (let [s #{}] (for [ i (range 10) j (range 1
mov ecx, 16 looptop: . . . loop looptop 这个循环会执行多少次? 如果 ecx
我似乎无法找到一种在 Xtend 中表达以下内容而不诉诸 while 循环的好方法: for(int i = 0; i range(int stop) { range(0, stop) }
好吧,长话短说,我正在学习汇编,我正在尝试循环打印出 ascii 字符“0”-“9”。因此,我完成了我在示例中看到的所有基础知识,例如使用 pushad 和 popad 保存寄存器状态,分配堆栈空间,
我正在尝试为自己编写一个扑克计算器,我有一个 5 级深的 for 循环。 为此,我将 for 循环一个接一个地嵌套。我正在寻找一种方法来简单地使用一个循环(或函数),它可以告诉我想去多少层。对于这个例
我有一本包含约 150,000 个键的字典。没有重复的键。每个 key 的长度为 127 个字符,每个 key 在 1-11 个位置上有所不同(大多数差异发生在 key 的末尾)。每个键的值是一个唯一
我正在尝试编写一个 Lisp 程序来实现与点和方 block 非常相似的棋盘游戏,这意味着我有两个玩家相互竞争但可以连续移动。我正在尝试实现最简单的 minimax 算法来实现这一点,没有 alpha
下面是我实现的代码的简要说明。 for 循环的复杂度应该是 O(n)。我只是无法弄清楚内部 while 循环的时间复杂度。 int x,n; // Inputted by the user.
我目前正在尝试使用 html 分词器 https://godoc.org/golang.org/x/net/html . 所以我想做的是:从 url 获取所有链接,如果 url 包含特定字符串 ->
我有 32 个文件(以相同的模式命名,唯一的区别是下面写的 $sample 编号)我想分成 4 个文件夹。我正在尝试使用以下脚本来完成这项工作,但该脚本无法正常工作,有人可以帮我使用以下 shell
我必须根据 where 条件在我的内部表上做一个循环,但根据我的程序模式,必须在运行时修改 where 条件的字段。 我知道在 SELECT 语句中这是可能的,但是当我在循环中执行此操作时出现错误。
我正在学习关于kdb数据库的q。我担心q中没有循环。 我需要写一个算法,用像C这样的冗长程序在几个嵌套的for循环中编写。但是在q中,我被无法循环的事实所困扰。 仅举一个具体的例子(很多),我有一个简
我不明白为什么这段代码只循环一次然后退出? 在 Ghci 中,我只能回答第一个循环,然后似乎变量 cont 设置为 false 并且我没有提示回答。 结果是: *Main> testLoop1 td1
我正在 Racket 中运行 for 循环,对于列表中的每个对象,我想执行两件事:如果该项目满足条件,(1) 将其附加到我的新列表中,(2) 然后打印列表。但我不知道如何在 Racket 中执行此操作
我正在尝试使用 matlab 并行包中的 parfor 循环。我和这个人有类似的问题:MATLAB parfor slicing issue? 。输出矩阵似乎没有被识别为切片变量。在我的具体情况下,我
我是一名优秀的程序员,十分优秀!