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

Geo*_*air 6 julia

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

Sve*_*nov 5

我做了一些窥探,这是我喜欢的(我在下面的所有实验中都使用了 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
}
Run Code Online (Sandbox Code Playgroud)

在这个片段中%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)
Run Code Online (Sandbox Code Playgroud)

这些架构的好消息是它们支持基于任意数字的索引。该movsd -8(%rax,%rsi,8), %xmm0leaq -1(%rsi), %rax是受在朱莉娅的基于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.


Rob*_*vey 3

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

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

  • 或者甚至更好,如果它是与数组一起存储数字值(数组长度,类的 vtbl 等),甚至不减 1 并按原样使用数字,将隐藏数字放在索引 0 处。 (2认同)