2016-04-22 27 views
5

好的,最近我正在做一系列測試。我有一個MC模擬,其中有幾個變量(20),這些變量有意義將它們全部放入一維數組中,因爲它使得幾件事情更易於閱讀。數組求和比在Julia中求和個體變量要慢

,但我有一個問題,我需要總結在每次迭代變量和模擬花費迭代的很多,所以我碰到這個問題(減少到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) 

這是預期的嗎?或者我做錯了什麼?我真的希望我的變量索引,因爲其他原因太長,現在不能解釋,但是這種性能損失是我不能忍受的。

回答

11

讓我們來看看這就是BEING爲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 
} 

大致回到其轉化爲朱莉婭爲:

sumtest1_int(N) = N < 1 ? 0 : ifelse(N > 0, N*28, 0) 

這一點多餘的,因爲主體可以簡化爲ifelse(N > 1, N*28, 0)(由於我們不關心N的負值,因此可以將其簡化爲ifelse(N > 1, N*28, 0)),但它仍然比執行循環要快得多。

功能sumtest2不能被分析或優化幾乎如此容易。這將需要證明數組A永遠不能改變,這是相當困難的。所以編譯器別無選擇,只能做所有的工作,當然,這比做不到它慢得多。在你的仿真中,使用局部變量的速度可能比在數組中存儲值要快,但它可能不會。你必須測量那些難以完全優化的代碼。

+0

謝謝你的詳細答案,這是非常豐富的,我學到很多東西。我意識到,通過簡化功能將其張貼在這裏,我像你解釋的那樣簡化了它。真實情況是,每次迭代中至少有1個(通常更多)變量會改變,但沒有一個是恆定的。這種情況會不一樣嗎?我會對它進行測試並在稍後發佈結果。 – Esteban

+2

沒問題。如果在循環中有任何遠程不重要的變化,編譯器不可能做這種優化。局部變量仍然比數組中的插槽更有優勢:數組需要存儲在內存中;局部變量可以存放在CPU寄存器中。 – StefanKarpinski

+0

是的你是對的。我通過在循環內簡單地生成一個隨機數「r」來測試它,並將它與第一個函數中的每個局部變量相加,時間從1秒變爲6秒......這仍然比原始的第二函數甚至沒有生成隨機數......它有點打擊,因爲我有一些想法排序變量來提高一些其他計算的速度,如果變量是本地的,我不知道該怎麼做。但是,哦,繼續學習。再次感謝您的答覆。 – Esteban