Julia生成相同的代码但需要不同的时间来执行

时间:2017-08-09 11:36:51

标签: performance julia

在评估另一个问题的过程中,我对两个不同的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)

不同的时间但相同的代码对我来说听起来很奇怪。有人可以解释一下这里发生了什么吗?

1 个答案:

答案 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_nativeg()的{​​{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嵌套对象/数组的兔子洞。