arrays - 对数组求和比在 Julia 中对单个变量求和要慢

标签 arrays performance sum julia

好的,我最近在做一系列测试。我有一个 MC 模拟,其中我有几个变量 (20),将它们全部放在一个一维数组中是有意义的,因为它使一些事情更容易阅读。

但是我遇到了一个问题,我需要在每次迭代中对变量求和,并且模拟需要很多次迭代,所以我遇到了这个问题(减少到 7 个变量):

function sumtest1(N)
    s=0.0
    a=1.0
    b=2.0
    c=3.0
    d=4.0
    e=5.0
    f=6.0
    g=7.0
    for i = 1:N
        s += (a+b+c+d+e+f+g)
    end
    return s
end

function sumtest2(N)
    s=0.0
    A=[1.0,2.0,3.0,4.0,5.0,6.0,7.0]
    for i = 1:N
        s += sum(A)
    end
    return s
end

@time sumtest1(1_000_000_000)
elapsed time: 0.998272756 seconds (96 bytes allocated)

@time sumtest1(1_000_000_000)
elapsed time: 7.522198967 seconds (208 bytes allocated)

这是预期的吗?还是我做错了什么?由于其他原因现在无法解释,我真的很想对我的变量进行索引,但这种性能损失是我无法接受的。

最佳答案

让我们看看正在为 sumtest1 执行的 LLVM 代码:

julia> @code_llvm sumtest1(10^9)

define double @julia_sumtest1_21391(i64) {
top:
  %1 = icmp sgt i64 %0, 0
  %2 = select i1 %1, i64 %0, i64 0
  %3 = icmp eq i64 %2, 0
  br i1 %3, label %L3, label %L.preheader

L.preheader:                                      ; preds = %top
  %4 = icmp sgt i64 %0, 0
  %smax = select i1 %4, i64 %0, i64 0
  br label %L

L:                                                ; preds = %L, %L.preheader
  %lsr.iv = phi i64 [ %smax, %L.preheader ], [ %lsr.iv.next, %L ]
  %s.0 = phi double [ %5, %L ], [ 0.000000e+00, %L.preheader ]
  %5 = fadd double %s.0, 2.800000e+01
  %lsr.iv.next = add i64 %lsr.iv, -1
  %6 = icmp eq i64 %lsr.iv.next, 0
  br i1 %6, label %L3, label %L

L3:                                               ; preds = %L, %top
  %s.1 = phi double [ 0.000000e+00, %top ], [ %5, %L ]
  ret double %s.1
}

这很难理解,但在循环体中有一件事很突出,L :
  %5 = fadd double %s.0, 2.800000e+01

对于每次迭代,预先计算的常数 28.0正在添加到累加器中,s .编译器可以告诉您永远不会更改任何局部变量,因此它知道每次添加相同的总和。循环必须执行的唯一原因是重复的浮点加法并不完全等同于乘法。如果所有局部变量都改为整数,其中重复加法与乘法完全等效,则完全消除循环:
julia> @time sumtest1_int(10^9)
  0.000005 seconds (6 allocations: 192 bytes)
28000000000

julia> @code_llvm sumtest1_int(10^9)

define i64 @julia_sumtest1_int_21472(i64) {
top:
  %1 = icmp slt i64 %0, 1
  br i1 %1, label %L3, label %L.preheader

L.preheader:                                      ; preds = %top
  %2 = icmp sgt i64 %0, 0
  %.op = mul i64 %0, 28
  %3 = select i1 %2, i64 %.op, i64 0
  br label %L3

L3:                                               ; preds = %L.preheader, %top
  %s.1 = phi i64 [ 0, %top ], [ %3, %L.preheader ]
  ret i64 %s.1
}

大致翻译回 Julia 为:
sumtest1_int(N) = N < 1 ? 0 : ifelse(N > 0, N*28, 0)

这有点多余,因为主体可以简化为 ifelse(N > 1, N*28, 0) (反过来也可以改为 28N 因为我们不关心 N 的负值),但它仍然比执行循环快得多。

函数sumtest2几乎不能那么容易地分析或优化。这需要证明数组 A永远无法改变,这是相当困难的。所以编译器别无选择,只能做所有的工作,这当然比不做要慢得多。在您的模拟中,使用局部变量可能仍然比将值存储在数组中更快,但可能不会。您必须测量那些更难完全优化的代码才能确定。

关于arrays - 对数组求和比在 Julia 中对单个变量求和要慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36801197/

相关文章:

java - 如何在方法中修改给定数组的值而不修改Java中的实际数组?

javascript - 对包含带空格的文本的数组进行排序

sql-server - 添加 ORDER BY 语句时 SQL 查询很慢

R:如何得到两个分布的总和?

c# - 如何检查二维数组中的 key 对是否存在?

java - 数组中的二进制搜索不起作用

linux - 测量内核空间开销的准确方法

java - 类(class)阅读领域费用

mysql - 当值之间的差距大于 x 时如何计算出现次数

c - 将 int 与 char 数组相加