您現在的位置是:首頁 > 攝影首頁攝影

反射真的很耗時嗎?射10萬次用時多久?

由 不落魄Android開發 發表于 攝影2022-12-04
簡介反射最佳化將中間操作(獲取屬性的程式碼)從迴圈中提出來之後,五輪測試平均耗時 1

androidaccessible怎麼關

無論是在面試過程中,還是看網路上各種技術文章,只要提到反射,不可避免都會提到一個問題,反射會影響效能嗎?影響有多大?如果在寫業務程式碼的時候,你用到了反射,都會被 review 人發出靈魂拷問,為什麼要用反射,有沒有其它的解決辦法。

而網上的答案都是千篇一律,比如反射慢、反射過程中頻繁的建立物件佔用更多記憶體、頻繁的觸發 GC 等等。那麼反射慢多少?反射會佔用多少記憶體?建立 1 個物件或者建立 10 萬個物件耗時多少?單次反射或者 10 萬次反射耗時多少?在我們的腦海中沒有一個直觀的概念,而今天這篇文章將會告訴你。

這篇文章,設計了幾個常用的場景,一起討論一下反射是否真的很耗時?最後會以圖表的形式展示。

測試工具及方案

在開始之前我們需要定義一個反射類 Person。

反射真的很耗時嗎?射10萬次用時多久?

針對上面的測試類,設計了以下幾個常用的場景,驗證反射前後的耗時。

建立物件

方法呼叫

屬性呼叫

伴生物件

測試工具及程式碼:

JMH (Java Microbenchmark Harness),這是 Oracle 開發的一個基準測試工具,他們比任何人都瞭解 JIT 以及 JVM 的最佳化對測試過程中的影響,所以使用這個工具可以儘可能的保證結果的可靠性。

基準測試是測試應用效能的一種方法,在特定條件下對某一物件的效能指標進行測試

本文的測試程式碼已經上傳到 github 倉庫 KtPractice 歡迎前往檢視。

github 倉庫 KtPractice:

https://github。com/hi-dhl/KtPractice

為什麼使用 JMH

因為 JVM 會對程式碼做各種最佳化,如果只是在程式碼前後列印時間戳,這樣計算的結果是不置信的,因為忽略了 JVM 在執行過程中,對程式碼進行最佳化產生的影響。而 JMH 會盡可能的減少這些最佳化對最終結果的影響。

測試方案

在單程序、單執行緒中,針對以上四個場景,每個場景測試五輪,每輪迴圈 10 萬次,計算它們的平均值

在執行之前,需要對程式碼進行預熱,預熱不會作為最終結果,預熱的目的是為了構造一個相對穩定的環境,保證結果的可靠性。因為 JVM 會對執行頻繁的程式碼,嘗試編譯為機器碼,從而提高執行速度。而預熱不僅包含編譯為機器碼,還包含 JVM 各種最佳化演算法,儘量減少 JVM 的最佳化,構造一個相對穩定的環境,降低對結果造成的影響。

JMH 提供 Blackhole,透過 Blackhole 的 consume 來避免 JIT 帶來的最佳化

Kotlin 和 Java 的反射機制

本文測試程式碼全部使用 Kotlin,Koltin 是完美相容 Java 的,所以同樣也可以使用 Java 的反射機制,但是 Kotlin 自己也封裝了一套反射機制,並不是用來取代 Java 的,是 Java 的增強版,因為 Kotlin 有自己的語法特點比如

擴充套件方法

伴生物件

可空型別的檢查

等等,如果想使用 Kotlin 反射機制,需要引入以下庫。

反射真的很耗時嗎?射10萬次用時多久?

在開始分析,我們需要對比 Java 瞭解一下 Kotlin 反射基本語法。

kotlin 的 KClass 對應 Java 的 Class,我們可以透過以下方式完成 KClass 和 Class 之間互相轉化

反射真的很耗時嗎?射10萬次用時多久?

kotlin 的 KProperty 對應 Java 的 Field,Java 的 Field 有 getter/setter 方法,但是在 Kotlin 中沒有 Field,分為了 KProperty 和 KMutableProperty,當變數用 val 宣告的時候,即屬性為 KProperty,如果變數用 var 宣告的時候,即屬性為 KMutableProperty

反射真的很耗時嗎?射10萬次用時多久?

在 Kotlin 中

函式

屬性

以及

建構函式

的超型別都是 KCallable,對應的子型別是 KFunction (函式、構造方法等等) 和 KProperty / KMutableProperty (屬性),而 Kotlin 中的 KCallable 對應 Java 的 AccessibleObject, 其子型別分別是 Method 、 Field 、 Constructor

反射真的很耗時嗎?射10萬次用時多久?

無論是使用 Java 還是 Kotlin 最終測試出來的結論都是一樣的,瞭解完基本反射語法之後,我們分別測試上述四種場景反射前後的耗時。

建立物件

正常建立物件

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 0。578 ms/op 。需要重點注意,這裡使用了 JMH 提供 Blackhole,透過 Blackhole 的 consume() 方法來避免 JIT 帶來的最佳化, 讓結果更加接近真實。

在物件建立過程中,會先檢查類是否已經載入,如果類已經載入了,會直接為物件分配空間,其中最耗時的階段其實是類的載入過程(載入->驗證->準備->解析->初始化)。

透過反射建立物件

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 4。710 ms/op,是正常建立物件的 9。4 倍,這個結果是很驚人,如果將中間操作(獲取構造方法)從迴圈中提取出來,那麼結果會怎麼樣呢。

反射最佳化

反射真的很耗時嗎?射10萬次用時多久?

正如你所見,我將中間操作(獲取構造方法)從迴圈中提取出來,五輪測試平均耗時 1。018 ms/op,速度得到了很大的提升,相比反射最佳化前速度提升了 4。7 倍,但是如果我們在將安全檢查功能關掉呢。

反射真的很耗時嗎?射10萬次用時多久?

isAccessible 是用來判斷是否需要進行安全檢査,設定為 true 表示關掉安全檢查,將會減少安全檢査產生的耗時,五輪測試平均耗時 0。943 ms/op,反射速度進一步提升了。

幾輪測試最後的結果如下圖示。

反射真的很耗時嗎?射10萬次用時多久?

方法呼叫

正常呼叫

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 0。422 ms/op。

反射呼叫

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 10。533 ms/op,是正常呼叫的 26 倍。如果我們將中間操作(獲取 getName 程式碼)從迴圈中提取出來,結果會怎麼樣呢。

反射最佳化

反射真的很耗時嗎?射10萬次用時多久?

將中間操作(獲取 getName 程式碼)從迴圈中提取出來了,五輪測試平均耗時 0。844 ms/op,速度得到了很大的提升,相比反射最佳化前速度提升了 13 倍,如果在將安全檢查關掉呢。

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 0。687 ms/op,反射速度進一步提升了。

幾輪測試最後的結果如下圖示。

反射真的很耗時嗎?射10萬次用時多久?

屬性呼叫

正常呼叫

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 0。241 ms/op 。

反射呼叫

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 12。432 ms/op,是正常呼叫的 62 倍,然後我們將中間操作(獲取屬性的程式碼)從迴圈中提出來。

反射最佳化

反射真的很耗時嗎?射10萬次用時多久?

將中間操作(獲取屬性的程式碼)從迴圈中提出來之後,五輪測試平均耗時 1。362 ms/op,速度得到了很大的提升,相比反射最佳化前速度提升了 8 倍,我們在將安全檢查關掉,看一下結果。

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 1。202 ms/op,反射速度進一步提升了。

幾輪測試最後的結果如下圖示。

反射真的很耗時嗎?射10萬次用時多久?

伴生物件

正常呼叫

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 0。470 ms/op 。

反射呼叫

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 5。661 ms/op,是正常呼叫的 11 倍,然後我們在看一下將中間操作(獲取 getAddress 程式碼)從迴圈中提出來的結果。

反射最佳化

反射真的很耗時嗎?射10萬次用時多久?

將中間操作(獲取 getAddress 程式碼)從迴圈中提出來,五輪測試平均耗時 0。840 ms/op,速度得到了很大的提升,相比反射最佳化前速度提升了 7 倍,現在我們在將安全檢查關掉。

反射真的很耗時嗎?射10萬次用時多久?

五輪測試平均耗時 0。702 ms/op,反射速度進一步提升了。

幾輪測試最後的結果如下圖所示。

反射真的很耗時嗎?射10萬次用時多久?

總結

我們對比了四種常用的場景:

建立物件

方法呼叫

屬性呼叫

伴生物件

。分別測試了反射前後的耗時,最後彙總一下五輪 10 萬次測試平均值。

反射真的很耗時嗎?射10萬次用時多久?

每個場景反射前後的耗時如下圖所示。

反射真的很耗時嗎?射10萬次用時多久?

在我們的印象中,反射就是惡魔,影響會非常大,但是從上面的表格看來,反射確實會有一定的影響,但是如果我們合理使用反射,最佳化後的反射結果並沒有想象的那麼大,這裡有幾個建議。

在頻繁的使用反射的場景中,將反射中間操作提取出來快取好,下次在使用反射直接從快取中取即可

關掉安全檢查,可以進一步提升效能

最後我們在看一下單次建立物件和單次反射建立物件的耗時,如下圖所示。

反射真的很耗時嗎?射10萬次用時多久?

Score 表示結果,Error 表示誤差範圍,在考慮誤差的情況下,它們的耗時差距在

微妙別以內

當然根據裝置的不同(高階機、低端機),還有系統、複雜的類等等因素,反射所產生的影響也是不同的。反射在實際專案中應用的非常的廣泛,很多設計和開發都和反射有關,比如透過反射去呼叫位元組碼檔案、呼叫系統隱藏 Api、動態代理的設計模式,Android 逆向、著名的 Spring 框架、各類 Hook 框架等等。