短短幾年,由 MIT CSAIL 實(shí)驗(yàn)室開(kāi)發(fā)的編程語(yǔ)言 Julia 已然成為編程界的新寵,尤其在科學(xué)計(jì)算領(lǐng)域炙手可熱。很大部分是因?yàn)檫@門(mén)語(yǔ)言結(jié)合了 C 語(yǔ)言的速度、Ruby 的靈活、Python 的通用性,以及其他各種語(yǔ)言的優(yōu)勢(shì)于一身。那么你知道為什么 Julia 的速度能做到那么快嗎?這并不是因?yàn)楦玫木幾g器,而是一種更新的設(shè)計(jì)理念,Julia 在開(kāi)發(fā)之初就將這種理念納入其中,而這也是關(guān)注 “人生苦短” 的 Python 所欠缺的。
為什么要選擇 Julia?因?yàn)樗绕渌_本語(yǔ)言更快,它在具備 Python、MATLAB、R 語(yǔ)言開(kāi)發(fā)速度的同時(shí),又能生成與 C 語(yǔ)言和 Fortran 一樣快的代碼。
但 Julia 新手對(duì)這種說(shuō)法可能會(huì)有點(diǎn)懷疑。
為什么其他腳本語(yǔ)言不也提升一下速度?Julia 可以做到的,為什么其他腳本語(yǔ)言做不到?
你能提供基準(zhǔn)測(cè)試來(lái)證明它的速度嗎?
這似乎有違 “天底下沒(méi)有免費(fèi)的午餐” 的道理。它真的有那么完美嗎?
很多人認(rèn)為 Julia 運(yùn)行速度很快,因?yàn)樗羌磿r(shí)編譯(JIT)型的(也就是說(shuō),每條語(yǔ)句都使用編譯的函數(shù)來(lái)運(yùn)行,這些函數(shù)要么在使用之前進(jìn)行即時(shí)編譯,要么在之前已經(jīng)編譯過(guò)并放在緩存中)。這就引出了一個(gè)問(wèn)題:Julia 是否提供了比 Python 或 R 語(yǔ)言(MATLAB 默認(rèn)使用 JIT)更好的 JIT 實(shí)現(xiàn)?因?yàn)槿藗冊(cè)谶@些 JIT 編譯器上所做的工作比 Julia 要多得多,所以我們憑什么認(rèn)為 Julia 這么快就會(huì)超過(guò)這些編譯器?但其實(shí)這完全是對(duì) Julia 的誤解。
我想以一種非常直觀的方式說(shuō)明,Julia 的速度之所以快,是因?yàn)樗脑O(shè)計(jì)決策。Julia 的的核心設(shè)計(jì)決策是通過(guò)多重分派實(shí)現(xiàn)專(zhuān)門(mén)化的類(lèi)型穩(wěn)定性,編譯器因此可以很容易地生成高效的代碼,同時(shí)還能夠保持代碼的簡(jiǎn)潔,讓它 “看起來(lái)就像一門(mén)腳本語(yǔ)言”。
但是,在本文的示例中,我們將看到 Julia 并不總是像其他腳本語(yǔ)言那樣,我們必須接受 “午餐不全是免費(fèi)” 的事實(shí)。
要看出它們之間的區(qū)別,我們只需要看看基本的數(shù)學(xué)運(yùn)算。
Julia 中的數(shù)學(xué)運(yùn)算
一般來(lái)說(shuō),Julia 中的數(shù)學(xué)運(yùn)算與其他腳本語(yǔ)言中的數(shù)學(xué)運(yùn)算看起來(lái)是一樣的。它們的數(shù)字都是 “真正的數(shù)字”,比如 Float64 就是 64 位浮點(diǎn)數(shù)或者類(lèi)似于 C 語(yǔ)言中的 “double”。Vector {Float64} 與 C 語(yǔ)言 double 數(shù)組的內(nèi)存布局是一樣的,都可以很容易地與 C 語(yǔ)言進(jìn)行互操作(實(shí)際上,在某種意義上,“Julia 是構(gòu)建在 C 語(yǔ)言之上的一個(gè)層”),從而帶來(lái)更高的性能。
使用 Julia 進(jìn)行一些數(shù)學(xué)運(yùn)算:
a =2+2b = a/3c = a÷3#div tab completion, means integer divisiond =4*5println([a;b;c;d])[4.0, 1.33333, 1.0, 20.0]
我在這里使用了 Julia 的 unicode 制表符補(bǔ)全功能。Julia 允許使用 unicode 字符,這些字符可以通過(guò)制表符實(shí)現(xiàn) Latex 風(fēng)格的語(yǔ)句。同樣,如果一個(gè)數(shù)字后面跟著一個(gè)變量,那么不需要使用 * 運(yùn)算符就可以進(jìn)行乘法運(yùn)算。例如,下面的 Julia 的代碼是合法的:
α =0.5?f(u) = α*u; ?f(2)sin(2π)-2.4492935982947064e-16類(lèi)型穩(wěn)定性和代碼內(nèi)省
類(lèi)型穩(wěn)定性是指一個(gè)方法只能輸出一種可能的類(lèi)型。例如:*(::Float64,::Float64) 輸出的類(lèi)型是 Float64。不管你給它提供什么參數(shù),它都會(huì)返回一個(gè) Float64。這里使用了多重分派:“*” 操作符根據(jù)它看到的類(lèi)型調(diào)用不同的方法。例如,當(dāng)它看到浮點(diǎn)數(shù)時(shí),就會(huì)返回浮點(diǎn)數(shù)。Julia 提供了代碼自省宏,可以看到代碼被編譯成什么東西。因此,Julia 不只是一門(mén)普通的腳本語(yǔ)言,還是一門(mén)可以讓你處理匯編的腳本語(yǔ)言!和其他很多語(yǔ)言一樣,Julia 被編譯成 LLVM (LLVM 是一種可移植的匯編格式)。
@code_llvm2*5;Function*; Location: int.jl:54define i64 @"julia_*_33751"(i64, i64) {top:%2= mul i64 %1, %0ret i64 %2}
這段代碼的意思是:執(zhí)行一個(gè)浮點(diǎn)數(shù)乘法操作,然后返回結(jié)果。我們也可以看一下匯編代碼。
@code_native2*5.text;Function* {;Location: int.jl:54imulq %rsi, %rdimovq %rdi, %raxretqnopl (%rax,%rax);}
“*” 函數(shù)被編譯成與 C 語(yǔ)言或 Fortran 中完全相同的操作,這意味著它可以達(dá)到相同的性能(盡管它是在 Julia 中定義的)。因此,Julia 不僅可以 “接近” C 語(yǔ)言,而且實(shí)際上可以得到相同的 C 語(yǔ)言代碼。那么在什么情況下會(huì)發(fā)生這種情況?
Julia 的有趣之處在于,上面的這個(gè)問(wèn)題其實(shí)問(wèn)得不對(duì),正確的問(wèn)題應(yīng)該是:在什么情況下代碼不能被編譯成像 C 語(yǔ)言或 Fortran 那樣?這里的關(guān)鍵是類(lèi)型穩(wěn)定性。如果一個(gè)函數(shù)是類(lèi)型穩(wěn)定的,那么編譯器就會(huì)知道函數(shù)在任意時(shí)刻的類(lèi)型,就可以巧妙地將其優(yōu)化為與 C 語(yǔ)言或 Fortran 相同的匯編代碼。如果它不是類(lèi)型穩(wěn)定的,Julia 必須進(jìn)行昂貴的 “裝箱”,以確保在操作之前知道函數(shù)的類(lèi)型是什么。
這是 Julia 與其他腳本語(yǔ)言之間最關(guān)鍵的不同點(diǎn)。
好的方面是 Julia 的函數(shù)(類(lèi)型穩(wěn)定)基本上就是 C 語(yǔ)言或 Fortran 的函數(shù),因此 “^”(乘方)運(yùn)算速度很快。那么,類(lèi)型穩(wěn)定的 ^(::Int64,::Int64) 會(huì)輸出什么?
2^5322^-50.03125
這里我們會(huì)得到一個(gè)錯(cuò)誤。為了確保編譯器可以為 “^” 返回一個(gè) Int64,它必須拋出一個(gè)錯(cuò)誤。但在 MATLAB、Python 或 R 語(yǔ)言中這么做是不會(huì)拋出錯(cuò)誤的,因?yàn)檫@些語(yǔ)言沒(méi)有所謂的類(lèi)型穩(wěn)定性。
如果沒(méi)有類(lèi)型安全性會(huì)怎樣?讓我們看一下代碼:
@code_native ^(2,5).text; Function ^ {; Location: intfuncs.jl:220pushq %raxmovabsq$power_by_squaring, %raxcallq *%raxpopq %rcxretqnop;}
現(xiàn)在,我們來(lái)定義自己的整數(shù)乘方運(yùn)算。與其他腳本語(yǔ)言一樣,我們讓它變得更 “安全”:
functionexpo(x,y)ify>0returnx^yelsex= convert(Float64,x)returnx^yendendexpo (genericfunctionwith1method)
現(xiàn)在運(yùn)行一下看看行不行:
println(expo(2,5))expo(2,-5)320.03125
再來(lái)看看匯編代碼。
@code_native expo(2,5).text;Functionexpo{; Location: In[8]:2pushq %rbxmovq %rdi, %rbx;Function>; {; Location: operators.jl:286;Function<; {; Location: int.jl:49testq %rdx, %rdx;}}jle L36; Location: In[8]:3;?Function?^; {; Location: intfuncs.jl:220movabsq $power_by_squaring, %raxmovq %rsi, %rdimovq %rdx, %rsicallq *%rax;}movq %rax, (%rbx)movb $2, %dlxorl %eax, %eaxpopq %rbxretq; Location: In[8]:5;?Function?convert; {; Location: number.jl:7;?Function?Type; {; Location: float.jl:60L36:vcvtsi2sdq %rsi, %xmm0, %xmm0;}}; Location: In[8]:6;?Function?^; {; Location: math.jl:780;?Function?Type; {; Location: float.jl:60vcvtsi2sdq %rdx, %xmm1, %xmm1movabsq $__pow, %rax;}callq *%rax;}vmovsd %xmm0, (%rbx)movb $1, %dlxorl %eax, %eax; Location: In[8]:3popq %rbxretqnopw %cs:(%rax,%rax);}
這是一個(gè)非常直觀的演示,說(shuō)明了 Julia 通過(guò)使用類(lèi)型推斷獲得了比其他腳本語(yǔ)言更高的性能。
核心思想:多重分派 + 類(lèi)型穩(wěn)定性 => 速度 + 可讀性
類(lèi)型穩(wěn)定性是 Julia 區(qū)別于其他腳本語(yǔ)言的一個(gè)關(guān)鍵特性。事實(shí)上,Julia 的核心思想是這樣的:
多重分派允許一種語(yǔ)言將函數(shù)調(diào)用分派給類(lèi)型穩(wěn)定的函數(shù)。
這就是Julia 的核心思想,現(xiàn)在讓我們花點(diǎn)時(shí)間深入了解一下。如果函數(shù)內(nèi)部具有類(lèi)型穩(wěn)定性(也就是說(shuō),函數(shù)內(nèi)的任意函數(shù)調(diào)用也是類(lèi)型穩(wěn)定的),那么編譯器就會(huì)知道每一步的變量類(lèi)型,它就可以在編譯函數(shù)時(shí)進(jìn)行充分的優(yōu)化,這樣得到的代碼基本上與 C 語(yǔ)言或 Fortran 相同。多重分派在這里可以起到作用,它意味著 “*” 可以是一個(gè)類(lèi)型穩(wěn)定的函數(shù):對(duì)于不同的輸入,它有不同的含義。但是,如果編譯器在調(diào)用 “*” 之前能夠知道 a 和 b 的類(lèi)型,那么它就知道應(yīng)該使用哪個(gè) “*” 方法,這樣它就知道 c=a*b 的輸出類(lèi)型是什么。這樣它就可以將類(lèi)型信息一路傳下去,從而實(shí)現(xiàn)全面的優(yōu)化。
我們從中可以學(xué)到一些東西。首先,為了實(shí)現(xiàn)這種級(jí)別的優(yōu)化,必須具有類(lèi)型穩(wěn)定性。大多數(shù)語(yǔ)言為了讓用戶(hù)可以更輕松地編碼,都沒(méi)有在標(biāo)準(zhǔn)庫(kù)中提供這種特性。其次,需要通過(guò)多重分派來(lái)專(zhuān)門(mén)化類(lèi)型函數(shù),讓腳本語(yǔ)言語(yǔ)法 “看上去更顯式” 一些。最后,需要一個(gè)健壯的類(lèi)型系統(tǒng)。為了構(gòu)建非類(lèi)型穩(wěn)定的乘方運(yùn)算,我們需要使用轉(zhuǎn)換函數(shù)。因此,要在保持腳本語(yǔ)言的語(yǔ)法和易用性的同時(shí)實(shí)現(xiàn)這種原始性能必須將語(yǔ)言設(shè)計(jì)成具有多重分派類(lèi)型穩(wěn)定性的語(yǔ)言,并提供一個(gè)健壯的類(lèi)型系統(tǒng)。
Julia 基準(zhǔn)測(cè)試
Julia 官網(wǎng)提供的基準(zhǔn)測(cè)試只是針對(duì)編程語(yǔ)言組件的執(zhí)行速度,并沒(méi)有說(shuō)是在測(cè)試最快的實(shí)現(xiàn),所以這里存在一個(gè)很大的誤解。R 語(yǔ)言程序員一邊看著使用 R 語(yǔ)言實(shí)現(xiàn)的 Fibonacci 函數(shù),一邊說(shuō):“這是一段很糟糕的代碼,不應(yīng)該在 R 語(yǔ)言中使用遞歸,因?yàn)檫f歸很慢”。但實(shí)際上,F(xiàn)ibonacci 函數(shù)是用來(lái)測(cè)試遞歸的,而不是用來(lái)測(cè)試語(yǔ)言的執(zhí)行速度的。
Julia 使用了類(lèi)型穩(wěn)定函數(shù)的多重分派機(jī)制,因此,即使是早期版本的 Julia 也可以?xún)?yōu)化得像 C 語(yǔ)言或 Fortran 那樣。非常明顯,幾乎在所有情況下,Julia 都非常接近 C 語(yǔ)言。當(dāng)然,也有與 C 語(yǔ)言不一樣的地方,我們可以來(lái)看看這些細(xì)節(jié)。首先是在計(jì)算 Fibonacci 數(shù)列時(shí) C 語(yǔ)言比 Julia 快 2.11 倍,這是因?yàn)檫@是針對(duì)遞歸的測(cè)試,而 Julia 并沒(méi)有完全為遞歸進(jìn)行過(guò)優(yōu)化。Julia 其實(shí)也可以加入這種優(yōu)化(尾遞歸優(yōu)化),只是出于某些原因他們才沒(méi)有這么做,最主要是因?yàn)椋嚎梢允褂梦策f歸的地方也可以使用循環(huán),而循環(huán)是一種更加健壯的優(yōu)化,所以他們建議使用循環(huán)來(lái)代替脆弱的尾遞歸。
Julia 表現(xiàn)不太好的地方還有 rand_mat_stat 和 parse_int 測(cè)試。這主要是因?yàn)檫吔鐧z查導(dǎo)致的。在大多數(shù)腳本語(yǔ)言中,如果你試圖訪(fǎng)問(wèn)超出數(shù)組邊界的元素就會(huì)出錯(cuò),Julia 默認(rèn)情況下也會(huì)這么做。
function test1()a = zeros(3)for i=1:4a[i] = iendendtest1()BoundsError: attempttoaccess3-elementArray{Float64,1}atindex[4]Stacktrace:[1] setindex!at./array.jl:769[inlined][2] test1()at./In[11]:4[3] top-levelscopeatIn[11]:7
不過(guò),你可以使用 @inbounds 宏來(lái)禁用這個(gè)功能:
functiontest2()a=zeros(3)@inboundsfori=1:4a[i] =iendendtest2()
這樣你就獲得了與 C 語(yǔ)言或 Fortran 一樣的不安全行為和執(zhí)行速度。這是 Julia 的另一個(gè)有趣的特性:默認(rèn)情況下是一個(gè)安全的腳本語(yǔ)言特性,在必要的時(shí)候禁用這個(gè)功能,以便獲得性能提升。
嚴(yán)格類(lèi)型
除了類(lèi)型穩(wěn)定性,你還需要嚴(yán)格類(lèi)型。在 Python 中,你可以將任何東西放入數(shù)組中。而在 Julia 中,你只能將類(lèi)型 T 放入 Vector {T} 中。Julia 提供了各種非嚴(yán)格的類(lèi)型,例如 Any。如果有必要,可以創(chuàng)建 Vector {Any},例如:
a = Vector{Any}(undef,3)a[1] =1.0a[2] ="hi!"a[3] =:Symbolica3-element Array{Any,1}:1.0"hi!":Symbolic
Union 是另一個(gè)不那么極端的抽象類(lèi)型,例如:
a = Vector{Union{Float64,Int}}(undef,3)a[1] =1.0a[2] =3a[3] =1/4a3-elementArray{Union{Float64, Int64},1}:1.030.25
這個(gè) Union 只接受浮點(diǎn)數(shù)和整數(shù)。不過(guò),它仍然是一個(gè)抽象類(lèi)型。接受抽象類(lèi)型作為參數(shù)的函數(shù)無(wú)法知道元素的類(lèi)型(在這個(gè)例子中,元素要么是浮點(diǎn)數(shù),要么是整數(shù)),這個(gè)時(shí)候,多重分派優(yōu)化在這里起不到作用,所以 Julia 此時(shí)的性能就不如其他腳本語(yǔ)言。
所以我們可以得出一個(gè)性能原則:盡可能使用嚴(yán)格類(lèi)型。使用嚴(yán)格類(lèi)型還有其他好處:嚴(yán)格類(lèi)型的 Vector {Float64} 實(shí)際上與 C 語(yǔ)言或 Fortran 是字節(jié)兼容的,所以不經(jīng)過(guò)轉(zhuǎn)換就可以直接用在 C 語(yǔ)言或 Fortran 程序中。
不免費(fèi)的午餐
很明顯,Julia 為了在保持腳本語(yǔ)言特征的同時(shí)實(shí)現(xiàn)性能目標(biāo),做出了非常明智的設(shè)計(jì)決策。但是,它也為此付出了一些代價(jià)。接下來(lái),我將展示 Julia 的一些奇特的東西及其相應(yīng)的工具。
性能是可選的
之前已經(jīng)說(shuō)明了 Julia 提供了多種方法來(lái)提升性能(比如 @inbounds),但我們不一定要使用它們。你也可以編寫(xiě)類(lèi)型不穩(wěn)定的函數(shù),雖然與 MATLAB、R 語(yǔ)言、Python 一樣慢,但你絕對(duì)可以這么做。在對(duì)性能要求沒(méi)有那么高的地方,可以將其作為一個(gè)可選項(xiàng)。
檢查類(lèi)型穩(wěn)定性
由于類(lèi)型穩(wěn)定性非常重要,Julia 為我們提供了一些工具,用來(lái)檢查一個(gè)函數(shù)是不是類(lèi)型穩(wěn)定的,其中最重要的是 @code_warntype 宏。讓我們用它來(lái)檢查一個(gè)類(lèi)型穩(wěn)定的函數(shù):
@code_warntype2^5Body::Int64│2201─ %1= invoke Base.power_by_squaring(_2::Int64,_3::Int64)::Int64│ └──return%1
請(qǐng)注意,它將函數(shù)中所有變量都顯示為嚴(yán)格類(lèi)型。那么 expo 會(huì)是怎樣的?
@code_warntype expo(2,5)Body::Union{Float64, Int64}│?? >21─ %1= (Base.slt_int)(0, y)::Bool│ └── goto#3 if not %1│32─ %3= π (x, Int64)│? ^ │ %4= invoke Base.power_by_squaring(%3::Int64,_3::Int64)::Int64│ └──return%4│53─ %6= π (x, Int64)││? Type │ %7= (Base.sitofp)(Float64, %6)::Float64│6│ %8= π (%7, Float64)│? ^ │ %9= (Base.sitofp)(Float64, y)::Float64││ │ %10= $(Expr(:foreigncall,"llvm.pow.f64", Float64, svec(Float64, Float64),:(:llvmcall),2,:(%8),:(%9),:(%9),:(%8)))::Float64│ └──return%10
請(qǐng)注意,可能的返回值是%4 和%10,它們是不同的類(lèi)型,因此返回類(lèi)型被推斷為 Union {Float64,Int64}。為了準(zhǔn)確地追蹤這種不穩(wěn)定性發(fā)生的位置,我們可以使用 Traceur.jl:
using Traceur@trace expo(2,5)┌ Warning:xisassignedasInt64└ @ In[8]:2┌ Warning:xisassignedasFloat64└ @ In[8]:5┌ Warning:expo returns Union{Float64, Int64}└ @ In[8]:232
在第 2 行,x 被分配了一個(gè) Int,而在第 5 行又被分配了一個(gè) Float64,因此它被推斷為 Union {Float64, Int64}。第 5 行是我們放置顯式轉(zhuǎn)換調(diào)用的地方,這樣我們就確定了問(wèn)題所在的位置。
處理必要的類(lèi)型不穩(wěn)定性
首先,我已經(jīng)證明了某些在 Julia 會(huì)出錯(cuò)的函數(shù)在其他腳本語(yǔ)言中卻可以 “讀懂你的想法”。在很多情況下,你會(huì)發(fā)現(xiàn)你可以從一開(kāi)始就使用不同的類(lèi)型,以此來(lái)實(shí)現(xiàn)類(lèi)型穩(wěn)定性(為什么不直接使用 2.0^-5?)。但是,在某些情況下,你找不到合適的類(lèi)型。這個(gè)問(wèn)題可以通過(guò)轉(zhuǎn)換來(lái)解決,但這樣會(huì)失去類(lèi)型穩(wěn)定性。你必須重新考慮你的設(shè)計(jì),并巧妙地使用多重分派。
假設(shè)我們有一個(gè) Vector {Union {Float64,Int}} 類(lèi)型的 a,并且可能遇到必須使用 a 的情況,需要在 a 的每個(gè)元素上執(zhí)行大量操作。在這種情況下,知道給定元素的類(lèi)型將帶來(lái)性能的大幅提升,但由于類(lèi)型位于 Vector {Union {Float64,Int}} 中,因此無(wú)法在下面這樣的函數(shù)中識(shí)別出類(lèi)型:
functionfoo(array)fori in eachindex(array)val = array[i]#doalgorithmXonvalendendfoo (genericfunctionwith1method)
不過(guò),我們可以通過(guò)多重分派來(lái)解決這個(gè)問(wèn)題。我們可以在元素上使用分派:
functioninner_foo(val)#DoalgorithmXonvalendinner_foo(genericfunctionwith1 method)
然后將 foo 定義為:
functionfoo2(array::Array)foriineachindex(array)inner_foo(array[i])endendfoo2(genericfunctionwith1 method)
因?yàn)樾枰獮榉峙蓹z查類(lèi)型,所以 inner_foo 函數(shù)是嚴(yán)格類(lèi)型化的。因此,如果 inner_foo 是類(lèi)型穩(wěn)定的,那么就可以通過(guò)專(zhuān)門(mén)化 inner_foo 來(lái)提高性能。這就導(dǎo)致了一個(gè)通用的設(shè)計(jì)原則:在處理奇怪或非嚴(yán)格的類(lèi)型時(shí),可以使用一個(gè)外部函數(shù)來(lái)處理邏輯類(lèi)型,同時(shí)使用一個(gè)內(nèi)部函數(shù)來(lái)處理計(jì)算任務(wù),實(shí)現(xiàn)最佳的性能,同時(shí)仍然具備腳本語(yǔ)言的通用能力。
REPL 的全局作用域性能很糟糕
Julia 全局作用域的性能很糟糕。官方的性能指南建議不要使用全局作用域。然而,新手可能會(huì)意識(shí)不到 REPL 其實(shí)就是全局作用域。為什么?首先,Julia 是有嵌套作用域的。例如,如果函數(shù)內(nèi)部有函數(shù),那么內(nèi)部函數(shù)就可以訪(fǎng)問(wèn)外部函數(shù)的所有變量。
functiontest(x)y=x+2functiontest2()y+3endtest2()endtest(genericfunctionwith1 method)
在 test2 中,y 是已知的,因?yàn)樗窃?test 中定義的。如果 y 是類(lèi)型穩(wěn)定的,那么所有這些工作就可以帶來(lái)性能的提升,因?yàn)?test2 可以假設(shè) y 是一個(gè)整數(shù)?,F(xiàn)在讓我們來(lái)看一下在全局作用域里會(huì)發(fā)生什么:
a=3functionbadidea()a+2enda=3.03.0
因?yàn)闆](méi)有使用分派來(lái)專(zhuān)門(mén)化 badidea,并且可以隨時(shí)更改 a 的類(lèi)型,因此 badidea 在編譯時(shí)無(wú)法進(jìn)行優(yōu)化,因?yàn)樵诰幾g期間 a 的類(lèi)型是未知的。但是,Julia 允許我們聲明常量:
consta_cons =3functionbadidea()a_cons+ 2endbadidea(genericfunctionwith1 method)
請(qǐng)注意,函數(shù)將使用常量的值來(lái)進(jìn)行專(zhuān)門(mén)化,因此它們?cè)谠O(shè)置后應(yīng)該保持不變。
在進(jìn)行基準(zhǔn)測(cè)試時(shí)會(huì)出現(xiàn)這種情況。新手會(huì)像下面這樣對(duì) Julia 進(jìn)行基準(zhǔn)測(cè)試:
a=3.0@timefori =1:4globalaa+= iend0.000006seconds (4allocations:64bytes)
但是,如果我們將它放在一個(gè)函數(shù)中,就可以實(shí)現(xiàn)優(yōu)化。
function timetest()a =3.0@timefori =1:4a += iendendtimetest()# First time compilestimetest()0.000001seconds0.000000seconds
這個(gè)問(wèn)題非常容易解決:不要在 REPL 的全局作用域內(nèi)進(jìn)行基準(zhǔn)測(cè)試或計(jì)算執(zhí)行時(shí)間。始終將代碼放在函數(shù)中,或?qū)⑺鼈兟暶鳛?const。
結(jié)論
速度是 Julia 的設(shè)計(jì)目標(biāo)。類(lèi)型穩(wěn)定性和多重分派對(duì) Julia 編譯的專(zhuān)門(mén)化起到了關(guān)鍵的作用。而要達(dá)到如此精細(xì)的類(lèi)型處理水平,以便盡可能有效地實(shí)現(xiàn)類(lèi)型穩(wěn)定性,并在不完全可能的情況下實(shí)現(xiàn)性能優(yōu)化,需要一個(gè)健壯的類(lèi)型系統(tǒng)。
-
編程語(yǔ)言
+關(guān)注
關(guān)注
10文章
1942瀏覽量
34707 -
python
+關(guān)注
關(guān)注
56文章
4792瀏覽量
84627 -
腳本語(yǔ)言
+關(guān)注
關(guān)注
0文章
48瀏覽量
8223
原文標(biāo)題:碾壓 Python!為什么 Julia 速度這么快?
文章出處:【微信號(hào):AI_era,微信公眾號(hào):新智元】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論