如何讓代碼并發效率更高
隨著計算硬件的快速發展,多核多處理器已經廣泛應用于企業和個人環境中,開發人員利用多線程技術努力提高軟件的計算速度,資深系統架構師 Gurudutt Kumar 總結了如何讓代碼并發效率更高的實踐經驗。
Gurudutt 首先列舉了幾種影響軟件可伸縮性的問題:
- 效率低下的并行化:單片應用程序或軟件無法有效使用可用的計算資源。您需要將應用程序組織成并行任務。在傳統的不支持多線程的應用程序或軟件中,我們會經常看到這個問題。這些應用程序在多核、多處理器、芯片多線程硬件上無法伸縮,并且無法實現更好的吞吐量。線程太多可能會和線程太少一樣,都不會產生好的結果。
- 串行瓶頸:在多個線程或進程之間共享數據結構的應用程序可能會有串行瓶頸。為了保持數據完整性,可能必須使用鎖定和串行化技術(例如,讀取鎖、讀寫鎖、寫入鎖、自旋鎖、互斥等)將這些共享數據結構的訪問串行化。設計得效率低下的鎖可能會由于多個線程或進程之間的高度鎖爭用而導致串行瓶頸,從而嘗試獲取鎖。這可能會潛在地降低應用程序或軟件的性能。應用程序的性能可能會隨著核心或處理器數量的增加而降低。
- 對操作系統 (OS) 或運行時環境的過度依賴:您不能依賴操作系統、運行時環境或編譯器來完成伸縮應用程序或軟件所需的一切操作。但是,編譯器和運行時環境可以幫助提供一定的優化,您不能依賴它們解決所有可伸縮性問題。例如,不能依賴 Java? 虛擬機 (JVM) 通過自動并行來發現 Java 應用程序的最佳可伸縮的機會。
- 工作負載的不平衡可能是一個瓶頸:工作負載的不均勻分布可能導致無法有效地利用計算資源。您可能必須將較大的任務劃分成可以并行運行的較小的任務,還可能必須將串行算法更改為并行算法,以便提高性能和可伸縮性。
- I/O 瓶頸:由于阻止磁盤輸入/輸出 (I/O) 或高網絡延遲而導致的瓶頸可能會嚴重抑制應用程序的可伸縮性。
- 無效的內存管理:在多核平臺上,因為有很多處理單元,因此純計算可能非常廉價,并且主要內存可能也不是問題,因為它正在變得越來越大。但是,內存帶寬一直是一個瓶頸,因為所有處理器核心都貢獻了一個通用的總線。無效的內存管理可能導致一些難以檢測到的性能問題,比如偽共享。 </ul>
- 為了最大程度地減少內存總線通信,可以通過最小化共享位置/數據盡可能地減少核心交互,即使共享數據沒有鎖保護,而有一些硬件級別原子指令(如 Microsoft? Windows? 32 位平臺上的 InterlockedExchangeAdd64)保護也是如此。
- 減少線程之間的內存爭用的一個方法是從多個線程中消除對共享內存區域的更新。例如,即便是在多個線程需要更新全局計數器或累計總數(如統計數據)時,各個線程也可以保持線程本地總數,并讓全局總數僅在需要時通過一個通用的線程進行更新。因此,在共享內存區域上的爭用會大大減少。
- 趨向于減少鎖爭用的模式會減少內存通信,因為它是一個共享的可寫入狀態,該狀態需要使用鎖并產生爭用。 </ul>
- 避免在數據結構中發生鎖爭用的方法之一是采用并發數據結構設計和無鎖算法,這會消除鎖以及傳統的同步技巧(比如互斥)。有多種并發數據結構的設計并不需要利用同步機制,比如互斥。
- 無鎖算法的一些示例如下:
- 使用 相對論編程 的可伸縮并發哈希表:該技巧的最簡單示例是 Read Copy Update (RCU),它專用于 Linux 內核,大大提高了 Linux 內核的性能,并簡化了 Linux 內核的代碼。
- 無鎖可擴展有序分割的哈希列表:這個無鎖遞歸可擴展哈希算法使用了無鎖的鏈接列表,這些列表使用原子指令來修改鏈接的列表。
- 在 Linux 內核中,廣泛使用了每處理器變量,系統上的每個處理器都獲得了自己的一個給定變量的副本。訪問每處理器變量不需要使用鎖,此外,因為在不同的處理器上,這些變量未在線程之間共享,因此沒有偽共享或內存爭用。這種技巧非常適合收集統計信息。 </ul>
- 當使用傳統鎖或同步技巧(如自旋鎖)時,必須注意的是,不要使用單片鎖或全局鎖,而是將這些鎖分成更細小的部分。因此,鎖會保護數據結構中的某個特定區域以及較小的區域。這樣多個線程就能夠通過獲取保護這些成員的相應鎖,在同一數據結構的不同成員上并發進行操作。這種方法可以實現更多并發。
- 甚至當軟件設計中的同步機制能夠實現更好的并發和減少鎖爭用時,也可能會由于偽共享而導致發生性能問題。例如,考慮一個哈希數據結構。如果存在一個自旋鎖數組,用于保護哈希中的每個哈希桶,那么在自旋鎖數組中可能會出現偽共享。兩個線程在兩個不同的處理器上運行,每個線程都鎖定哈希中的不同哈希桶,那么當它們所需的自旋鎖位于同一個緩存行上時,可能會發生偽共享。因此,在設計此類算法時需要考慮采用避免發生偽共享的通用技巧。 </ul>
以“避免內存爭用”為例,Gurudut 做了解釋:
在內存和緩存中,各種不同的核共享一個通用的數據區域,這需要在它們之間進行同步。當不同的核同時訪問同一個數據區域時,會發生內存爭用。在不同的核之間同步數據會因總線通信、鎖定成本以及緩存缺失而有很大的性能損失。如果應用程序有多個線程,并且所有線程都更新或修改同一個內存地址,那么正如前面部分所討論的那樣,為了保持緩存一致性,可能會產生一次重大的乒乓效應。這會導致性能降低。
</blockquote>如何避免內存爭用呢?Gurudut 認為“不要在核之間共享可寫入的狀態”:
對于鎖爭用,Gurudut 認為要從“避免”和“減少”兩個方面來解決這個問題:
避免
減少
來自: InfoQ本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!