Kotlin最佳實踐:在高階函數中使用inline - 碼農登陸

2021-01-12 碼農登陸

前言

最近,無意中看到一篇文章,是聊inline在高階函數中的性能提升,說實話之前沒有認真關注過這個特性,所以藉此機會好好學習了一番。

高階函數:入參中含有lambda的函數(方法)。

原文是一位外國小哥寫的,這裡把它翻譯了一下重寫梳理了一遍發出來。也算是技術無國界吧,哈哈~

官方文檔對inline的使用主要提供了倆種方式:內聯類、內聯函數

正文

操作符是我們日常Kotlin開發的利器,如果我們點進去看看源碼,我們會發現這些操作符大多都會使用inline。

inlinefun<T> Iterable<T>.filter(predicate: (T)->Boolean): List<T>{val destination = ArrayList<T>()for (element inthis) if (predicate(element))destination.add(element)return destination}

既然官方標準庫中如果使用,我們則需要驗證一下inline是不是真能有更好的性能:

inlinefunrepeat(times: Int, action: (Int) -> Unit) {for (index in0 until times) {action(index) }}funnoinlineRepeat(times: Int, action: (Int) -> Unit) {for (index in0 until times) { action(index) }}

倆個函數,除了inline沒什麼其他區別。接下來咱們執行個100000000次,看看方法耗時:

var a = 0repeat(100_000_000) {a += 1}var b = 0noinlineRepeat(100_000_000) { b += 1}

跑起來我們會發現:inlineRepeat()平均完成了0.335ns,而noinlineRepeat()平均需要153 980484.884ns。是46.6萬倍!看起來inline的確很重要,那麼這種性能改進是沒有成本的嗎?我們什麼時候應該使用inline?接下來咱們就來聊一聊這個問題,不過咱們先從一個基本的問題開始:inline有什麼作用?

inline有什麼用?

簡單來說被inline修飾過的函數,會在調用的時候把函數體替換過來。說起來可能很從抽象,直接看代碼:

publicinlinefunprint(message: Int) {

System.out.print(message)}funmain(args: Array<String>) { print(2) print(2)}

反編譯class之後,我們會看到是這個樣子的:

publicstaticfinalvoidmain(@NotNull String[] args){int message$iv = 2;int $i$f$print = false;System.out.print(message$iv); message$iv = 2; $i$f$print = false; System.out.print(message$iv); }

接下來咱們看看高階函數中的優化repeat(100) { println("A") },反編譯之後:

for (index in0 until 1000) {println("A")}

看到這我猜大家應該可以理解inline的作用了吧。不過,話又說回來。「僅僅」做了這點改動,會什麼會有如此大的性能提升?解答這個問題,不得不聊一聊JVM是如何實現Lambda的。

Lambda的原理

一般來說,會有倆種方案:

匿名類「附加」類咱們直接通過一個demo來看這倆種實現:

val lambda: ()->Unit = {// body}

對於匿名類的實現來說,反編譯是這樣的:

Function0 lambda = new Function0() {public Object invoke(){// body}};

對於「附加」類的實現來說,反編譯是這樣的:

// Additional class in separate filepublicclassTestInlineKt$lambdaimplementsFunction0{public Object invoke(){// code}}// UsageFunction0 lambda = new TestInlineKt$lambda()

有了上邊的代碼,咱們也就明白高階函數的開銷為什麼這麼大:畢竟每一個Lambda都會額外創建一個類。接下來咱們通過一個demo進一步感受這些額外的開銷:

funmain(args: Array<String>) {var a = 0repeat(100_000_000) { a += 1 }var b = 0 noinlineRepeat(100_000_000) { b += 1 }}

反編譯之後:

publicstaticfinalvoidmain(@NotNull String[] args){int a = 0;int times$iv = 100000000;int var3 = 0;for(int var4 = times$iv; var3 < var4; ++var3) {++a; }final IntRef b = new IntRef(); b.element = 0; noinlineRepeat(100000000, (Function1)(new Function1() {public Object invoke(Object var1){ ++b.element;return Unit.INSTANCE; } }));}

inline的成本

inline並不是沒有任何成本的。其實咱們最上邊看

public inline fun print(message: Int) { System.out.print(message) }

的時候,看反編譯的內容也能看出它得到的成本。

接下來咱們就基於這個print()函數,來對比一下:

fun main(args: Array<String>){print(2) print(2) System.out.print(2)}

反編譯如下:

publicstaticfinalvoidmain(@NotNull String[] args){int message$iv = 2;int $i$f$print = false;System.out.print(message$iv); message$iv = 2; $i$f$print = false; System.out.print(message$iv); System.out.print(2);}

可以看出inline額外生成了一些代碼,這也就是它額外的開銷。因此咱們在使用inline的時候還是需要有一定的規則的,以免適得其反。

最佳實踐

當我們沒有高階函數、沒有使用reified關鍵詞時不應該隨意使用inline,徒增消耗。

尾聲

到此這篇文章就結束了。

但是看了外國小哥這篇文章的時候,的確發現自己有很多內容是有遺漏的。所以接下來如果有機會的話,會繼續寫或者翻譯一些這類「最佳實踐」的文章。

相關焦點

  • C語言陷阱與技巧第2節,使用inline函數可以提升程序效率,但是讓...
    打開 Linux 內核原始碼,會發現內核在定義C語言函數時,有很多都帶有 「inline」關鍵字,請看下圖,那麼這個關鍵字有什麼作用呢?inline 關鍵字的作用在C語言程序開發中,inline 一般用於定義函數,inline 函數也被稱作「內聯函數」,C99 和 GNU C 均支持內聯函數。那麼在C語言中,內聯函數和普通函數有什麼不同呢?
  • Python基礎教程——高階函數
    Python的高階函數,就是map、filter、reduce,說它們是高階函數,只是因為我們平時用的少,所以理解起來也有點費勁,事實上,它們功能很強大,也很好用易用。一起來看看吧。篩選函數例1:對一個數字列表,篩選出其中的偶數,常規的方法是使用for循環,再在for循環中判斷它,符合條件的保存到新列表中,最後再使用新列表,真的是太煩瑣了,我們使用filter函數來個簡單的:
  • Python學習,這些高階函數和高階特性值得一學
    解決問題的思路有的時候會比較單一,其實Python有很多靈活的解法,比如python的幾個高階函數或者特性!推導式列表推導式,使用一句表達式構造一個新列表,可包含過濾、轉換等操作。語法:[exp for item in collection if codition]例子:1-1000內所有偶數組成的列表2種方法,大家自己對比字典推導式,使用一句表達式構造一個新列表,可包含過濾、轉換等操作。
  • 高階函數不會用?教你JS中最實用的幾個高階函數用法
    其實從閉包的例子中我們就已經看到了關於高階函數的相關內容了,哈哈還記得在我們去判斷數據類型的時候,我們都是通過Object.prototype.toString來計算的。每個數據類型之間只是'[object XXX]'不一樣罷了。所以在我們寫類型判斷的時候,一般都是將參數傳入函數中,這裡我簡單寫一下實現,咱們先來看看。
  • 一口氣講透一致性哈希(Hash),助力「碼農變身」
    如今雲計算、大數據、物聯網、AI的興起,使得分布系統得到了前所未有的廣泛應用,然而由於分布式系統具有極高的複雜度,帶來了許多難題,一致性哈希就是為了解決分布式難題之一應運而生的,本篇主要圖示講解一致性哈希的原理及其應用,助力碼農變身。
  • n階即高階導數計算舉例解析
    一階導數的導數稱為二階導數,二階以上的導數可由歸納法逐階定義,二階和二階以上的導數統稱為高階導數,本文主要介紹三階以上導數計算規律。例題解析n 階導數:萊布尼茲公式設函數u(x)、v(x)在點x都具有 n 階導數,則由一階導數乘積的運算法則有:[u(x)*v(x)]'=u'(x)v(x)+u(x)v'(x);二階導數乘積的運算法則有:[u(x)*v(x)]''=u''(x)v(x)+2u'(x)v'(x)+u(x)v''(x);可見導數階數越高,
  • 了解一階高通濾波器傳遞函數
    簡要回顧一下:通過對S域電路的分析,可以得到低通濾波器的輸入輸出特性表達式;電路的VOUT/VIN表達式是濾波器的傳遞函數,如果將該表達式與標準化形式進行比較,可以快速確定兩個關鍵參數,即截止頻率和最大增益;傳遞函數可以寫成分子多項式除以分母多項式,分子多項式的根是傳遞函數的零,分母多項式的根是傳遞函數的極點。
  • Kotlin 能取代 Java 嗎?
    在最近的 Jexenter 調查中,Kotlin 在最熱門的技術發展趨勢榜單上高居第六位。App brain 最新的統計數據顯示,Kotlin 語言在 2018 年的頂級應用程式中佔據了25.30%的市場份額,佔新應用程式安裝量的40.76%。
  • 自然語言處理的最佳實踐
    數據科學家開始從傳統的方法轉向最先進的(SOTA)深度神經網絡(DNN)算法,這種算法使用的語言模型經過了大文本語料庫的預訓練。此存儲庫包含構建 NLP 系統的示例和最佳實踐,在 jupyter notebook 和實用程序函數中提供。知識庫的重點是最先進的方法和常見的場景,這些方法和場景在研究文本和語言問題的研究人員和實踐者中很流行。
  • 具有形式不變性的高階微分
    一種向0奔跑的函數?一種向0奔跑的變量?一種無限靠近0而又不是0的數?本文用無窮分階數域將無窮小和無窮大變成一種數。並在此基礎上研究了真正具有形式不變性的高階微分。在此特別強調:人們用d2y/dx2代表y對x的二階導數,只是一種傳統習慣,在這種表示方法下,d2y/dx2應該作為一個整體符號來看待,上下不可拆。
  • 高階導數公式匯總
    最近很多人問小編關於某函數高階導數的問題,包括在知乎上也有不少。今天的推文小編匯總一下能夠想到的高階導數公式,還有什麼要補充的歡迎留言補充。
  • 了解傳遞函數中的極點和零點
    極點和零點假設我們有一個傳遞函數,其中變量s出現在分子和分母中。在這種情況下,至少一個s值將使分子為零,並且至少一個s值將使分母為零。使分子為零的值是傳遞函數零點,並且使分母為零的值是傳遞函數極點。讓我們考慮以下示例:在這個系統中,在s=0時為零點,在s=–ωo時為極點。極點和零點定義了濾波器的特徵。
  • 基於DSP的高階COSTAS鎖相環的設計
    本文主要介紹了一種用於載波同步的高階COSTAS環路,用於完成MPSK的相干解調中的載波同步。本文提出了一種便於軟體實現的COSTAS環路的簡化結構,用於完成8PSK的載波同步,並詳細討論了採取數位訊號處理器(DSP)編程實現COSTAS環路的一些問題。
  • 去除inline-block元素間間距的N種方法
    我們使用CSS更改非inline-block水平元素為inline-block水平,也會有該問題:.space a { display: inline-block; padding二、方法之移除空格元素間留白間距出現的原因就是標籤段之間的空格,因此,去掉HTML中的空格,自然間距就木有了。
  • 高階電路動態特性的仿真分析
    引言  高階動態電路的分析通常都歸結為高階微分方程或一階微分方程組的求解,需要微分方程和矩陣理論的相關知識,掌握起來比較困難。對於複雜的高階電路,用求解微分方程的方法則更加困難,一是列寫微分方程,二是根據變量及變量的各階導數的初始值確定積分常數。若藉助合適的仿真軟體,則可以使電路的分析變得方便、準確和直觀。
  • 數學分析原理【知識點整理】【微分的連續性、洛必達、高階導數、泰勒定理、向量函數的微分】
    Derivatives of Higher Order [高階微分]Def.[高階微分] (5.14)If Differentiation of Vector-Valued Functions [向量函數的微分]Remark.
  • 請問結構動力學中常說的一階和二階,三階頻率或振型等是什麼關係?
    是時間函數,其二階導數為:求出它的n個根(又稱特徵值)並進行由低到高的排列後,ω1、ω2、...、ωn,即得系統由低階到高階(「由柔到剛」)的自振圓頻率。現在來看振型。前面說 以高層建築為例,這種增大趨勢是因為對於高階 / 頻振動,非結構單元(填充牆、幕牆等)對結構總阻尼的貢獻越來越大。需要說明的是模態阻尼是為了計算模型的而引入的一種概念/手段,用它來近似模擬結構中未知的非線性能量耗損,它並不是結構的實際屬性。下圖描述了一單自由度、質量-彈簧-阻尼系統在時刻
  • 專家:普通高中信息技術課程不是培養「碼農」的
    信息技術迅速發展的今天,各個行業對於計算機應用的依賴正在持續增強,設計、建設、使用、維護和保障等各個環節需要大量的碼農參與其中。國家統計局編制的2018年中國統計年鑑中,僅「信息傳輸、軟體和信息技術」的就業人數就有395.4萬人,在統計的19個行業中排第13位。這一系列數據充分說明碼農已不再是鳳毛麟角的崗位,已經成為事實上的一種職業。
  • 這裡有份最佳實踐清單
    對於如何使用深度學習進行自然語言處理,本文作者 Sebastian Ruder 給出了一份詳細的最佳實踐清單,不僅包括與大多數 NLP 任務相關的最佳實踐,還有最常見任務的最佳實踐,尤其是分類、序列標註、自然語言生成和神經機器翻譯。作者對最佳實踐的選擇很嚴格,只有被證明在至少兩個獨立的群體中有益的實踐才會入選,並且每個最佳實踐作者至少給出兩個參引。