在评估另一个问题的过程中,我对两个不同的julia程序生成相同代码但需要不同时间执行的情况进行了讨论。
using BenchmarkTools
test(n) = [g() for i = 1:n]
案例1:
g() = 0;
@btime test(1000);
1.020μs(1次分配:7.94 KiB)
代码1:
code_native(g,())
.text
Filename: In[2]
pushq %rbp
movq %rsp, %rbp
Source line: 1
xorl %eax, %eax
popq %rbp
retq
nopl (%rax,%rax)
@code_native test(1000)
.text
Filename: In[1]
pushq %rbp
movq %rsp, %rbp
Source line: 2
subq $16, %rsp
xorl %eax, %eax
testq %rdi, %rdi
cmovnsq %rdi, %rax
movq $1, -16(%rbp)
movq %rax, -8(%rbp)
movabsq $collect, %rax
leaq -16(%rbp), %rdi
callq *%rax
addq $16, %rsp
popq %rbp
retq
nopw %cs:(%rax,%rax)
案例2:
g() = UInt8(0);
@btime test(1000);
142.603 ns(1次分配:1.06 KiB)
代码2:
code_native(g,())
.text
Filename: In[8]
pushq %rbp
movq %rsp, %rbp
Source line: 1
xorl %eax, %eax
popq %rbp
retq
nopl (%rax,%rax)
@code_native test(1000)
.text
Filename: In[11]
pushq %rbp
movq %rsp, %rbp
Source line: 2
subq $16, %rsp
xorl %eax, %eax
testq %rdi, %rdi
cmovnsq %rdi, %rax
movq $1, -16(%rbp)
movq %rax, -8(%rbp)
movabsq $collect, %rax
leaq -16(%rbp), %rdi
callq *%rax
addq $16, %rsp
popq %rbp
retq
nopw %cs:(%rax,%rax)
不同的时间但相同的代码对我来说听起来很奇怪。有人可以解释一下这里发生了什么吗?
答案 0 :(得分:3)
时差与每次使用的不同函数g()
无关,而与结果归零的内存量无关。
在情况1中,需要分配8个字节* 1000 = 8000个字节并归零。
在情况2中,需要分配1个字节* 1000 = 1000个字节并归零。
这可以从@btime
的结果中看出。在一个更清晰的例子中,我们有:
julia> @btime zeros(1000);
767.300 ns (1 allocation: 7.94 KiB)
julia> @btime zeros(125);
128.849 ns (1 allocation: 1.06 KiB)
其中zeros(n)
只返回n
Int零的数组。请注意,分配的金额与问题中的金额相匹配。
<强>更新强>
Stefan指出,奇怪的是,@code_native
和g()
的{{1}}输出在两次运行中都是相同的。这引出了一个问题,计算机如何知道它是分配UInt8还是Ints?
由于test(Int)
被重新定义且g()
依赖于它,因此0.5 / 0.6引入的用于处理重新定义的世界时代机制在重新定义后调用时触发test(Int)
的重新编译。新的test(Int)
具有类似的test(Int)
(在x86目标计算机上),但是对于@code_native
值的引用在两个编译中是不同的。要清除这一点,$collect
输出显示版本之间的后缀差异:
@code_llvm
VS。
define %jl_value_t addrspace(10)* @julia_test_62122(i64) #0 !dbg !5 {
top:
:
:
%5 = call %jl_value_t addrspace(10)* @julia_collect_62123(%Generator addrspace(11)* nocapture readonly %4)
ret %jl_value_t addrspace(10)* %5
}
更接近金属方法会挖出两个版本的机器代码:
0x55,0x48,0x89,0xe5,0x48,0x8b,0x06,0x48,0x8b,0x38,0x48, 0xb8, 0xa0,0x52, 0xa1,0x21,0x7e,0x7e,0x00,0x00,0xff,0xd0, 0x5d,0xc3
VS
0x55,0x48,0x89,0xe5,0x48,0x8b,0x06,0x48,0x8b,0x38,0x48, 0xb8, 0x10,0x58, 0xa1,0x21,0x7e,0x7e,0x00,0x00,0xff,0xd0, 0x5d,0xc3
注意0xc3是define %jl_value_t addrspace(10)* @julia_test_62151(i64) #0 !dbg !5 {
top:
:
:
%5 = call %jl_value_t addrspace(10)* @julia_collect_62152(%Generator addrspace(11)* nocapture readonly %4)
ret %jl_value_t addrspace(10)* %5
}
指令的x86操作码。要获得机器代码,您需要浏览ret
嵌套对象/数组的兔子洞。