基于单一的数组索引是否存在性能损失?

时间:2015-01-19 20:06:50

标签: julia

对于Julia使用基于单一的数组索引是否存在性能损失(无论多小),因为机器代码通常更直接支持从零开始的索引?

2 个答案:

答案 0 :(得分:4)

我做了一些窥探,这是我喜欢的(我在下面的所有实验中使用了Julia 0.6):

> arr = zeros(5);
> @code_llvm arr[1]

define double @jlsys_getindex_51990(i8** dereferenceable(40), i64) #0 !dbg !5 {
top:
  %2 = add i64 %1, -1
  %3 = bitcast i8** %0 to double**
  %4 = load double*, double** %3, align 8
  %5 = getelementptr double, double* %4, i64 %2
  %6 = load double, double* %5, align 8
  ret double %6
}

在此代码段%1中保存实际索引。请注意%2 = add i64 %1, -1。 Julia确实在引擎盖下使用了基于0的数组,并从索引中减去了1。这导致生成额外的llvm指令,因此llvm代码看起来效率稍差。但是,这种额外的算术运算如何渗透到本机代码是另一个问题。

在ax86和amd64

> @code_native arr[1]
        .text
Filename: array.jl
Source line: 520
    leaq    -1(%rsi), %rax
    cmpq    24(%rdi), %rax
    jae L20
    movq    (%rdi), %rax
    movsd   -8(%rax,%rsi,8), %xmm0  # xmm0 = mem[0],zero
    retq
L20:
    pushq   %rbp
    movq    %rsp, %rbp
    movq    %rsp, %rcx
    leaq    -16(%rcx), %rax
    movq    %rax, %rsp
    movq    %rsi, -16(%rcx)
    movl    $1, %edx
    movq    %rax, %rsi
    callq   0xffffffffffcbf392
    nopw    %cs:(%rax,%rax)

这些架构的好消息是它们支持基于任意数字的索引。 movsd -8(%rax,%rsi,8), %xmm0leaq -1(%rsi), %rax是受Julia中基于1的索引影响的两条指令。查看movsd指令,在这一条指令中,我们同时执行实际索引和减法。 -8部分是减法。如果使用基于0的索引而不是指令movsd (%rax,%rsi,8), %xmm0

另一个受影响的指令是leaq -1(%rsi), %rax。但是由于cmp指令使用in-out参数这一事实,%rsi的值必须复制到另一个寄存器,因此在基于0的索引下仍会生成相同的指令,但它会可能看起来像leaq (%rsi), %rax

因此,在x86和amd64机器上,基于1的索引只会使用相同指令的稍微复杂的版本,但不会生成其他指令。代码最有可能与基于0的索引一样快。如果存在任何减速,则可能是由于特定的微架构并且将存在于一个CPU模型中而不存在于另一个CPU模型中。这种差异归结为芯片,我不担心。

不幸的是,我对arm和其他架构知之甚少,但情况可能类似。

与其他语言的接口

当与C或Python等其他语言交互时,总是必须记住在传递索引时减去或加1。编译器无法帮助您,因为其他代码超出了它的范围。因此,在这种情况下,存在1次提取算术运算的性能损失。但除非这是一个非常紧密的循环,否则这种差异可以忽略不计。

房间里的大象

嗯,房间里的大象是受约束的检查。回到上一个程序集片段,大多数生成的代码都关注 - 前3个指令和L20标签下的所有内容。实际索引只是movqmovsd指令。因此,如果您关心真正快速的代码,那么绑定检查会比基于1的索引获得更多的性能损失。幸运的是,Julia提供了通过@inbound--check-bounds=no来缓解这些问题的方法。

答案 1 :(得分:3)

最有可能的是,Julia只是从你提供的索引中减去1,并在引擎盖下使用从零开始的数组。因此,性能损失将是减法的成本(几乎肯定是无关紧要的)。

编写两小段代码来测试每个代码的性能会很容易。