乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      為什么Julia比Python快?因?yàn)樘焐砟罹透冗M(jìn)啊

       東東Wr 2019-12-13
      Julia 語(yǔ)言因?yàn)椤缚臁购汀负?jiǎn)潔」可兼得而聞名,我們可以用類似 Python 的優(yōu)美語(yǔ)句獲得類似 C 的性能。那么你知道為什么 Julia 比 Python 快嗎?這并不是因?yàn)楦玫木幾g器,而是一種更新的設(shè)計(jì)理念,關(guān)注「人生苦短」的 Python 并沒(méi)有將這種理念納入其中。
      為什么Julia比Python快?因?yàn)樘焐砟罹透冗M(jìn)啊

      其實(shí)像以前 C 或其它主流語(yǔ)言在使用變量前先要聲明變量的具體類型,而 Python 并不需要,賦值什么數(shù)據(jù),變量就是什么類型。然而沒(méi)想到正是這種類型穩(wěn)定性,讓 Julia 相比 Python 有更好的性能。

      選擇 Julia 的最主要原因:要比其他腳本語(yǔ)言快得多,讓你擁有 Python/Matlab/R 一樣快速的開(kāi)發(fā)速度,同時(shí)像 C/Fortan 那樣高效的運(yùn)行速度。

      Julia 的新手可能對(duì)下面這些描述略為謹(jǐn)慎:

      1. 為什么其他語(yǔ)言不能更快一點(diǎn)?Julia 能夠做到,其他語(yǔ)言就不能?
      2. 你怎么解釋 Julia 的速度基準(zhǔn)?(對(duì)許多其他語(yǔ)言來(lái)說(shuō)也很難?)
      3. 這聽(tīng)起來(lái)違背沒(méi)有免費(fèi)午餐定律,在其他方面是否有損失?

      許多人認(rèn)為 Julia 快是因?yàn)樗褂玫氖?JIT 編譯器,即每一條語(yǔ)句在使用前都先使用編譯函數(shù)進(jìn)行編譯,不論是預(yù)先馬上編譯或之前先緩存編譯。這就產(chǎn)生了一個(gè)問(wèn)題,即 Python/R 和 MATLAB 等腳本語(yǔ)言同樣可以使用 JIT 編譯器,這些編譯器的優(yōu)化時(shí)間甚至比 Julia 語(yǔ)言都要久。所以為什么我們會(huì)瘋狂相信 Julia 語(yǔ)言短時(shí)間的優(yōu)化就要超過(guò)其它腳本語(yǔ)言?這是一種對(duì) Julia 語(yǔ)言的完全誤解。

      在本文中,我們將了解到 Julia 快是因?yàn)樗脑O(shè)計(jì)決策。它的核心設(shè)計(jì)決策:通過(guò)多重分派的類型穩(wěn)定性是允許 Julia 能快速編譯并高效運(yùn)行的核心,本文后面會(huì)具體解釋為什么它是快的原因。此外,這一核心決策同時(shí)還能像腳本語(yǔ)言那樣令語(yǔ)法非常簡(jiǎn)潔,這兩者相加可以得到非常明顯的性能增益。

      但是,在本文中我們能看到的是 Julia 不總像其他腳本語(yǔ)言,我們需要明白 Julia 語(yǔ)言因?yàn)檫@個(gè)核心決策而有一些「損失」。理解這種設(shè)計(jì)決策如何影響你的編程方式,對(duì)你生成 Julia 代碼而言非常重要。

      為了看見(jiàn)其中的不同,我們可以先簡(jiǎn)單地看看數(shù)學(xué)運(yùn)算案例。

      Julia 中的數(shù)學(xué)運(yùn)算

      總而言之,Julia 中的數(shù)學(xué)運(yùn)算看起來(lái)和其他腳本語(yǔ)言是一樣的。值得注意的一個(gè)細(xì)節(jié)是 Julia 的數(shù)值是「真數(shù)值」,在 Float64 中真的就和一個(gè) 64 位的浮點(diǎn)數(shù)值一樣,或者是 C 語(yǔ)言的「雙精度浮點(diǎn)數(shù)」。一個(gè) Vector{Float64} 中的內(nèi)存排列等同于 C 語(yǔ)言雙精度浮點(diǎn)數(shù)數(shù)組,這都使得它與 C 語(yǔ)言的交互操作變得簡(jiǎn)單(確實(shí),某種意義上 Julia 是構(gòu)建在 C 語(yǔ)言頂層的),且能帶來(lái)高性能(對(duì) NumPy 數(shù)組來(lái)說(shuō)也是如此)。

      Julia 中的一些數(shù)學(xué):

      a = 2+2
      b = a/3
      c = a÷3 #\div tab completion, means integer division
      d = 4*5
      println([a;b;c;d])
      output: [4.0, 1.33333, 1.0, 20.0]

      此外,數(shù)值乘法在后面跟隨著變量的情況下允許不使用運(yùn)算符 *,例如以下的計(jì)算可通過(guò) Julia 代碼完成:

      α = 0.5
      ?f(u) = α*u; ?f(2)
      sin(2π)
      output: -2.4492935982947064e-16

      類型穩(wěn)定和代碼自省

      類型穩(wěn)定,即從一種方法中只能輸出一種類型。例如,從 *(:: Float64,:: Float64) 輸出的合理類型是 Float64。無(wú)論你給它的是什么,它都會(huì)反饋一個(gè) Float64。這里是一種多重分派(Multiple-Dispatch)機(jī)制:運(yùn)算符 * 根據(jù)它看到的類型調(diào)用不同的方法。當(dāng)它看到 floats 時(shí),它會(huì)反饋 floats。Julia 提供代碼自?。╟ode introspection)宏,以便你可以看到代碼實(shí)際編譯的內(nèi)容。因此 Julia 不僅僅是一種腳本語(yǔ)言,它更是一種可以讓你處理匯編的腳本語(yǔ)言!與許多語(yǔ)言一樣,Julia 編譯為 LLVM(LLVM 是一種可移植的匯編語(yǔ)言)。

      @code_llvm 2*5
      ; Function *
      ; Location: int.jl:54
      define i64 @"julia_*_33751"(i64, i64) {
      top:
      %2 = mul i64 %1, %0
      ret i64 %2
      }

      這個(gè)輸出表示,執(zhí)行浮點(diǎn)乘法運(yùn)算并返回答案。我們甚至可以看一下匯編:

      @code_llvm 2*5
      .text
      ; Function * {
      ; Location: int.jl:54
      imulq %rsi, %rdi
      movq %rdi, %rax
      retq
      nopl (%rax,%rax)
      ;}

      這表示*函數(shù)已編譯為與 C / Fortran 中完全相同的操作,這意味著它實(shí)現(xiàn)了相同的性能(即使它是在 Julia 中定義的)。因此,不僅可以「接近」C 語(yǔ)言的性能,而且實(shí)際上可以獲得相同的 C 代碼。那么在什么情況下會(huì)發(fā)生這種事情呢?

      關(guān)于 Julia 的有趣之處在于,我們需要知道什么情況下代碼不能編譯成與 C / Fortran 一樣高效的運(yùn)算?這里的關(guān)鍵是類型穩(wěn)定性。如果函數(shù)是類型穩(wěn)定的,那么編譯器可以知道函數(shù)中所有節(jié)點(diǎn)的類型,并巧妙地將其優(yōu)化為與 C / Fortran 相同的程序集。如果它不是類型穩(wěn)定的,Julia 必須添加昂貴的「boxing」以確保在操作之前找到或者已明確知道的類型。

      這是 Julia 和其他腳本語(yǔ)言之間最為關(guān)鍵的不同點(diǎn)!

      好處是 Julia 的函數(shù)在類型穩(wěn)定時(shí)基本上和 C / Fortran 函數(shù)一樣。因此^(取冪)很快,但既然 ^(:: Int64,:: Int64)是類型穩(wěn)定的,那么它應(yīng)輸出什么類型?

      2^5
      output: 32
      2^-5
      output: 0.03125

      這里我們得到一個(gè)錯(cuò)誤。編譯器為了保證 ^ 返回一個(gè) Int64,必須拋出一個(gè)錯(cuò)誤。如果在 MATLAB,Python 或 R 中執(zhí)行這個(gè)操作,則不會(huì)拋出錯(cuò)誤,這是因?yàn)槟切┱Z(yǔ)言沒(méi)有圍繞類型穩(wěn)定性構(gòu)建整個(gè)語(yǔ)言。

      當(dāng)我們沒(méi)有類型穩(wěn)定性時(shí)會(huì)發(fā)生什么呢?我們來(lái)看看這段代碼:

      @code_native ^(2,5)
      .text
      ; Function ^ {
      ; Location: intfuncs.jl:220
      pushq %rax
      movabsq $power_by_squaring, %rax
      callq *%rax
      popq %rcx
      retq
      nop
      ;}

      現(xiàn)在讓我們定義對(duì)整數(shù)的取冪,讓它像其他腳本語(yǔ)言中看到的那樣「安全」:

      function expo(x,y)
      if y>0
      return x^y
      else
      x = convert(Float64,x)
      return x^y
      end
      end
      output: expo (generic function with 1 method)

      確保它有效:

      println(expo(2,5))
      expo(2,-5)
      output: 32
      0.03125

      當(dāng)我們檢查這段代碼時(shí)會(huì)發(fā)生什么?

      @code_native expo(2,5)
      .text
      ; Function expo {
      ; Location: In[8]:2
      pushq %rbx
      movq %rdi, %rbx
      ; Function >; {
      ; Location: operators.jl:286
      ; Function <; {
      ; Location: int.jl:49
      testq %rdx, %rdx
      ;}}
      jle L36
      ; Location: In[8]:3
      ; Function ^; {
      ; Location: intfuncs.jl:220
      movabsq $power_by_squaring, %rax
      movq %rsi, %rdi
      movq %rdx, %rsi
      callq *%rax
      ;}
      movq %rax, (%rbx)
      movb $2, %dl
      xorl %eax, %eax
      popq %rbx
      retq
      ; Location: In[8]:5
      ; Function convert; {
      ; Location: number.jl:7
      ; Function Type; {
      ; Location: float.jl:60
      L36:
      vcvtsi2sdq %rsi, %xmm0, %xmm0
      ;}}
      ; Location: In[8]:6
      ; Function ^; {
      ; Location: math.jl:780
      ; Function Type; {
      ; Location: float.jl:60
      vcvtsi2sdq %rdx, %xmm1, %xmm1
      movabsq $__pow, %rax
      ;}
      callq *%rax
      ;}
      vmovsd %xmm0, (%rbx)
      movb $1, %dl
      xorl %eax, %eax
      ; Location: In[8]:3
      popq %rbx
      retq
      nopw %cs:(%rax,%rax)
      ;}

      這個(gè)演示非常直觀地說(shuō)明了為什么 Julia 使用類型推斷來(lái)實(shí)現(xiàn)能夠比其他腳本語(yǔ)言有更高的性能。

      核心觀念:多重分派+類型穩(wěn)定性 => 速度+可讀性

      類型穩(wěn)定性(Type stability)是將 Julia 語(yǔ)言與其他腳本語(yǔ)言區(qū)分開(kāi)的一個(gè)重要特征。實(shí)際上,Julia 的核心觀念如下所示:

      (引用)多重分派(Multiple dispatch)允許語(yǔ)言將函數(shù)調(diào)用分派到類型穩(wěn)定的函數(shù)。

      這就是 Julia 語(yǔ)言所有特性的出發(fā)點(diǎn),所以我們需要花些時(shí)間深入研究它。如果函數(shù)內(nèi)部存在類型穩(wěn)定性,即函數(shù)內(nèi)的任何函數(shù)調(diào)用也是類型穩(wěn)定的,那么編譯器在每一步都能知道變量的類型。因?yàn)榇藭r(shí)代碼和 C/Fortran 代碼基本相同,所以編譯器可以使用全部的優(yōu)化方法編譯函數(shù)。

      我們可以通過(guò)案例解釋多重分派,如果乘法運(yùn)算符 * 為類型穩(wěn)定的函數(shù):它因輸入表示的不同而不同。但是如果編譯器在調(diào)用 * 之前知道 a 和 b 的類型,那么它就知道哪一個(gè) * 方法可以使用,因此編譯器也知道 c=a * b 的輸出類型。因此如果沿著不同的運(yùn)算傳播類型信息,那么 Julia 將知道整個(gè)過(guò)程的類型,同時(shí)也允許實(shí)現(xiàn)完全的優(yōu)化。多重分派允許每一次使用 * 時(shí)都表示正確的類型,也神奇地允許所有優(yōu)化。

      我們可以從中學(xué)習(xí)到很多東西。首先為了達(dá)到這種程度的運(yùn)行優(yōu)化,我們必須擁有類型穩(wěn)定性。這并不是大多數(shù)編程語(yǔ)言標(biāo)準(zhǔn)庫(kù)所擁有的特性,只不過(guò)是令用戶體驗(yàn)更容易而需要做的選擇。其次,函數(shù)的類型需要多重分派才能實(shí)現(xiàn)專有化,這樣才能允許腳本語(yǔ)言變得「變得更明確,而不僅更易讀」。最后,我們還需要一個(gè)魯棒性的類型系統(tǒng)。為了構(gòu)建類型不穩(wěn)定的指數(shù)函數(shù)(可能用得上),我們也需要轉(zhuǎn)化器這樣的函數(shù)。

      因此編程語(yǔ)言必須設(shè)計(jì)為具有多重分派的類型穩(wěn)定性語(yǔ)言,并且還需要以魯棒性類型系統(tǒng)為中心,以便在保持腳本語(yǔ)言的句法和易于使用的特性下實(shí)現(xiàn)底層語(yǔ)言的性能。我們可以在 Python 中嵌入 JIT,但如果需要嵌入到 Julia,我們需要真的把它成設(shè)計(jì)為 Julia 的一部分。

      Julia 基準(zhǔn)

      Julia 網(wǎng)站上的 Julia 基準(zhǔn)能測(cè)試編程語(yǔ)言的不同模塊,從而希望獲取更快的速度。這并不意味著 Julia 基準(zhǔn)會(huì)測(cè)試最快的實(shí)現(xiàn),這也是我們對(duì)其主要的誤解。其它編程語(yǔ)言也有相同的方式:測(cè)試編程語(yǔ)言的基本模塊,并看看它們到底有多快。

      Julia 語(yǔ)言是建立在類型穩(wěn)定函數(shù)的多重分派機(jī)制上的。因此即使是最初版的 Julia 也能讓編譯器快速優(yōu)化到 C/Fortran 語(yǔ)言的性能。很明顯,基本大多數(shù)案例下 Julia 的性能都非常接近 C。但還有少量細(xì)節(jié)實(shí)際上并不能達(dá)到 C 語(yǔ)言的性能,首先是斐波那契數(shù)列問(wèn)題,Julia 需要的時(shí)間是 C 的 2.11 倍。這主要是因?yàn)檫f歸測(cè)試,Julia 并沒(méi)有完全優(yōu)化遞歸運(yùn)算,不過(guò)它在這個(gè)問(wèn)題上仍然做得非常好。

      用于這類遞歸問(wèn)題的最快優(yōu)化方法是 Tail-Call Optimization,Julia 語(yǔ)言可以隨時(shí)添加這類優(yōu)化。但是 Julia 因?yàn)橐恍┰虿](méi)有添加,主要是:任何需要使用 Tail-Call Optimization 的案例同時(shí)也可以使用循環(huán)語(yǔ)句。但是循環(huán)對(duì)于優(yōu)化顯得更加魯棒,因?yàn)橛泻芏噙f歸都不能使用 Tail-Call 優(yōu)化,因此 Julia 還是建議使用循環(huán)而不是使用不太穩(wěn)定的 TCO。

      Julia 還有一些案例并不能做得很好,例如 the rand_mat_stat 和 parse_int 測(cè)試。然而,這些很大程度上都?xì)w因于一種名為邊界檢測(cè)(bounds checking)的特征。在大多數(shù)腳本語(yǔ)言中,如果我們對(duì)數(shù)組的索引超過(guò)了索引邊界,那么程序?qū)?bào)錯(cuò)。Julia 語(yǔ)言默認(rèn)會(huì)完成以下操作:

      function test1()
      a = zeros(3)
      for i=1:4
      a[i] = i
      end
      end
      test1()
      BoundsError: attempt to access 3-element Array{Float64,1} at index [4]
      Stacktrace:
      [1] setindex! at ./array.jl:769 [inlined]
      [2] test1() at ./In[11]:4
      [3] top-level scope at In[11]:7

      然而,Julia 語(yǔ)言允許我們使用 @inbounds 宏關(guān)閉邊界檢測(cè):

      function test2()
      a = zeros(3)
      @inbounds for i=1:4
      a[i] = i
      end
      end
      test2()

      這會(huì)為我們帶來(lái)和 C/Fortran 相同的不安全行為,但是也能帶來(lái)相同的速度。如果我們將關(guān)閉邊界檢測(cè)的代碼用于基準(zhǔn)測(cè)試,我們能獲得與 C 語(yǔ)言相似的速度。這是 Julia 語(yǔ)言另一個(gè)比較有趣的特征:它默認(rèn)情況下允許和其它腳本語(yǔ)言一樣獲得安全性,但是在特定情況下(測(cè)試和 Debug 后)關(guān)閉這些特征可以獲得完全的性能。

      核心概念的小擴(kuò)展:嚴(yán)格類型形式

      類型穩(wěn)定性并不是唯一必須的,我們還需要嚴(yán)格的類型形式。在 Python 中,我們可以將任何類型數(shù)據(jù)放入數(shù)組,但是在 Julia,我們只能將類型 T 放入到 Vector{T} 中。為了提供一般性,Julia 語(yǔ)言提供了各種非嚴(yán)格形式的類型。最明顯的案例就是 Any,任何滿足 T:<Any 的類型,在我們需要時(shí)都能創(chuàng)建 Vector{Any},例如:

      a = Vector{Any}(undef,3)
      a[1] = 1.0
      a[2] = "hi!"
      a[3] = :Symbolic
      a
      output:
      3-element Array{Any,1}:
      1.0
      "hi!"
      :Symbolic

      抽象類型的一種不太極端的形式是 Union 類型,例如:

      a = Vector{Union{Float64,Int}}(undef,3)
      a[1] = 1.0
      a[2] = 3
      a[3] = 1/4
      a
      output:
      3-element Array{Union{Float64, Int64},1}:
      1.0
      3
      0.25

      該案例只接受浮點(diǎn)型和整型數(shù)值,然而它仍然是一種抽象類型。一般在抽象類型上調(diào)用函數(shù)并不能知道任何元素的具體類型,例如在以上案例中每一個(gè)元素可能是浮點(diǎn)型或整型。因此通過(guò)多重分派實(shí)現(xiàn)優(yōu)化,編譯器并不能知道每一步的類型。因?yàn)椴荒芡耆珒?yōu)化,Julia 語(yǔ)言和其它腳本語(yǔ)言一樣都會(huì)放慢速度。

      這就是高性能原則:盡可能使用嚴(yán)格的類型。遵守這個(gè)原則還有其它優(yōu)勢(shì):一個(gè)嚴(yán)格的類型 Vector{Float64} 實(shí)際上與 C/Fortran 是字節(jié)兼容的(byte-compatible),因此它無(wú)需轉(zhuǎn)換就能直接用于 C/Fortran 程序。

      高性能的成本

      很明顯 Julia 語(yǔ)言做出了很明智的設(shè)計(jì)決策,因而在成為腳本語(yǔ)言的同時(shí)實(shí)現(xiàn)它的性能目標(biāo)。然而,它到底損失了些什么?下一節(jié)將展示一些由該設(shè)計(jì)決策而產(chǎn)生的 Julia 特性,以及 Julia 語(yǔ)言各處的一些解決工具。

      可選的性能

      前面已經(jīng)展示過(guò),Julia 會(huì)通過(guò)很多方式實(shí)現(xiàn)高性能(例如 @inbounds),但它們并不一定需要使用。我們可以使用類型不穩(wěn)定的函數(shù),它會(huì)變得像 MATLAB/R/Python 那樣慢。如果我們并不需要頂尖的性能,我們可以使用這些便捷的方式。

      檢測(cè)類型穩(wěn)定性

      因?yàn)轭愋头€(wěn)定性極其重要,Julia 語(yǔ)言會(huì)提供一些工具以檢測(cè)函數(shù)的類型穩(wěn)定性,這在 @code_warntype 宏中是最重要的。下面我們可以檢測(cè)類型穩(wěn)定性:

      @code_warntype 2^5
      Body::Int64
      │220 1 ─ %1 = invoke Base.power_by_squaring(_2::Int64, _3::Int64)::Int64
      │ └── return %1

      注意這表明函數(shù)中的變量都是嚴(yán)格類型,那么 expo 函數(shù)呢?

      @code_warntype 2^5
      Body::Union{Float64, Int64}
      │?? >2 1 ─ %1 = (Base.slt_int)(0, y)::Bool
      │ └── goto #3 if not %1
      │ 3 2 ─ %3 = π (x, Int64)
      │? ^ │ %4 = invoke Base.power_by_squaring(%3::Int64, _3::Int64)::Int64
      │ └── return %4
      │ 5 3 ─ %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

      函數(shù)返回可能是 4% 和 10%,它們是不同的類型,所以返回的類型可以推斷為 Union{Float64,Int64}。為了準(zhǔn)確追蹤不穩(wěn)定性產(chǎn)生的位置,我們可以使用 Traceur.jl:

      using Traceur
      @trace expo(2,5)
      ┌ Warning: x is assigned as Int64
      └ @ In[8]:2
      ┌ Warning: x is assigned as Float64
      └ @ In[8]:5
      ┌ Warning: expo returns Union{Float64, Int64}
      └ @ In[8]:2
      output: 32

      這表明第 2 行 x 分派為整型 Int,而第 5 行它被分派為浮點(diǎn)型 Float64,所以類型可以推斷為 Union{Float64,Int64}。第 5 行是明確調(diào)用 convert 函數(shù)的位置,因此這為我們確定了問(wèn)題所在。原文后面還介紹了如何處理不穩(wěn)定類型,以及全局變量 Globals 擁有比較差的性能,希望詳細(xì)了解的讀者可查閱原文。

      結(jié) 論

      設(shè)計(jì)上 Julia 很快。類型穩(wěn)定性和多重分派對(duì) Julia 的編譯做特化很有必要,使其工作效率非常高。此外,魯棒性的類型系統(tǒng)同樣還需要在細(xì)粒度水平的類型上正常運(yùn)行,因此才能盡可能實(shí)現(xiàn)類型穩(wěn)定性,并在類型不穩(wěn)定的情況下盡可能獲得更高的優(yōu)化。

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類似文章 更多