剛好看到一些跟編譯器相關的議題,所以來紀錄一下。
在一些語言中會有行內函式(inline function)的設計,使用的話一般會讓程式的效能變好。
最知名應該是 C 跟 C++ 的 inline
。
Inline function 會在編譯時期直接將函式內容展開到程式碼中,不過展開與否是由編譯器決定的,inline
的標記只是告訴編譯器這個函式可以成為 inline function。
Inline expansion 就是編譯時期會由編譯器執行的一個動作,看起來與 macro expansion 相似,但不同的是 macro expansion 是在前處理(preprocessing)時期做的,會直接展開在原始碼裡頭,而 inline expansion 則是在編譯時期做的,會在呼叫位點(call site)直接展開。
展開後編譯器便可以進行最佳化,執行時,就不需要做函式呼叫,也不會在 function stack 上多配置空間。一般使用在短小的函式上會有好處,在巨大的函式上使用不一定會有好處。然而過多的 inline function 反而可能造成過多的指令快取的消耗,造成反效果。
在 Julia 中,編譯器會自動偵測哪些函式可以被展開,會自動做 inline expansion。一般短小的函式會自動被編譯器判定要 inline,不過也可以由程式設計師自己指定哪些巨大函式可以 inline,可以參考[文件]](https://docs.julialang.org/en/v1.2/base/base/#Base.@inline)。
除了 @inline
以外,還有 @noinline
。為了避免過多的 inline 反而傷害效能,也可以標記一些短小的函式不要 inline。
範例:
1 2 3
| @inline function bigfunc(x) ... end
|
Julia 什麼時候 inline expansion?
我們來實驗看看,以下有兩個函式,foo
跟 bar
。其中讓 foo
為 inline。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| julia> @inline foo(x) = 3x foo (generic function with 1 method)
julia> bar(x) = foo(x)^2 bar (generic function with 1 method)
julia> bar(5) 225
julia> @code_lowered bar(5) CodeInfo( 1 ─ %1 = Main.foo(x) │ %2 = Core.apply_type(Base.Val, 2) │ %3 = (%2)() │ %4 = Base.literal_pow(Main.:^, %1, %3) └── return %4 )
julia> @code_typed bar(5) CodeInfo( 1 ─ %1 = Base.mul_int(3, x)::Int64 │ %2 = Base.mul_int(%1, %1)::Int64 └── return %2 ) => Int64
|
你會發現在 lower 的階段仍保留有 foo
的函式呼叫的過程,但是到了 typed 的階段就已經剩下計算的部份了。
所以其實在進入 LLVMIR 以前就已經先做完 inline expansion 了。
相關技術:Inline caching
留言與分享