平行集合

效能評量

語言

JVM 上的效能

JVM 上的效能模型有時會在相關評論中被混淆,因此不太容易理解。由於各種原因,有些程式碼可能不如預期般高效能或可擴充。在此,我們提供幾個範例。

原因之一是 JVM 應用程式的編譯程序與靜態編譯語言不同(請參閱 [2])。Java 和 Scala 編譯器會將原始碼轉換為 JVM 位元組碼,並執行極少的最佳化。在大多數現代 JVM 上,一旦執行程式位元組碼,就會將其轉換為執行該程式碼的電腦架構的機器碼。這稱為即時編譯。但是,由於即時編譯必須快速,因此其程式碼最佳化程度較低。為了避免重新編譯,所謂的 HotSpot 編譯器只會最佳化程式碼中執行頻率高的部分。對基準測試撰寫者而言,這表示程式在每次執行時都可能有不同的效能。在同一個 JVM 執行個體中多次執行同一程式碼片段(例如方法)可能會產生非常不同的效能結果,具體取決於在執行期間是否已最佳化特定程式碼。此外,測量某段程式碼的執行時間可能會包括 JIT 編譯器本身執行最佳化的時間,因此會產生不一致的結果。

在 JVM 上執行的另一個隱藏執行是自動記憶體管理。程式執行會不時停止,並執行垃圾收集器。如果基準測試程式配置任何堆疊記憶體(而且大多數 JVM 程式都會配置),垃圾收集器就必須執行,因此可能會扭曲測量結果。為了攤銷垃圾收集效應,應執行測量程式多次以觸發多次垃圾收集。

效能惡化的另一個常見原因是將基本型別作為引數傳遞給泛型方法時隱含發生的裝箱和拆箱。在執行階段,基本型別會轉換為代表它們的物件,以便可以傳遞給具有泛型型別參數的方法。這會導致額外的配置,而且速度較慢,也會在堆疊上產生額外的垃圾。

在平行效能方面,一個常見的問題是記憶體競爭,因為程式設計師無法明確控制物件配置的位置。事實上,由於 GC 的影響,競爭可能會在物件在記憶體中移動後,在應用程式的生命週期中後續階段發生。撰寫基準測試時,需要考慮這種影響。

微基準測試範例

在測量期間,有幾種方法可以避免上述影響。首先,目標微基準測試必須執行足夠多次,以確保即時編譯器已將其編譯為機器碼,且已最佳化。這稱為熱身階段。

微基準測試本身應在單獨的 JVM 執行個體中執行,以減少來自程式不同部分配置的物件垃圾回收,或無關的即時編譯所產生的雜訊。

應使用 HotSpot JVM 的伺服器版本執行,這會進行更積極的最佳化。

最後,為降低基準測試過程中發生垃圾回收的機率,理想情況下,垃圾回收週期應在基準測試執行前發生,並盡可能延後下一個週期。

有關適當的基準測試範例,您可以在 Scala 函式庫基準測試 中查看原始碼。

一個集合要達到什麼大小才需要平行處理?

這是個常見的問題。答案有點複雜。

並行化開始發揮效益的集合大小實際上取決於許多因素。其中包括(但不限於)

  • 機器架構。不同的 CPU 類型有不同的效能和可擴充性特性。與此正交的是,機器是多核心,還是有多個處理器透過主機板進行通訊。
  • JVM 供應商和版本。不同的 VM 會在執行階段對程式碼套用不同的最佳化。它們實作不同的記憶體管理和同步技術。有些並不支援 ForkJoinPool,改用 ThreadPoolExecutor,導致更多開銷。
  • 每元素工作負載。平行運算的函數或謂詞決定每元素工作負載的大小。工作負載越小,在平行執行時,要獲得加速所需元素的數量就越高。
  • 特定集合。例如,ParArrayParTrieMap 有以不同速度遍歷集合的分割器,表示在遍歷本身中就有更多每元素工作。
  • 特定運算。例如,ParVector 對轉換器方法(例如 filter)的速度遠慢於存取器方法(例如 foreach)。
  • 副作用。當同時修改記憶體區域或在 foreachmap 等主體內使用同步時,可能會發生競爭。
  • 記憶體管理。當配置大量物件時,可能會觸發垃圾回收週期。根據傳遞新物件參考的方式,GC 週期可能需要更多或更少時間。

參考

  1. 有缺陷的微基準測試的剖析,Brian Goetz
  2. 動態編譯和效能測量,Brian Goetz
  3. Scala 函式庫基準測試

此頁面的貢獻者