天天看點

使用 C++ 中的 final 關鍵字,到底能否提升性能?

作者:CSDN
使用 C++ 中的 final 關鍵字,到底能否提升性能?

使用 C++ 中的 final 關鍵字,到底能否提升性能?不少開發者認為可以,卻沒能給出資料依據。為此,本文作者進行了一次測試,親自驗證這個說法的真實性。

原文連結:https://16bpp.net/blog/post/the-performance-impact-of-cpp-final-keyword/

譯者 | 鄭麗媛出品 | 程式人生(ID:coder_life)

如果你選擇用 C++ 寫代碼,一定是有理由的,而這個理由很可能就是性能。

在很多有關 C++ 的文章中,我們經常會看到各種“性能提示和技巧”或“這樣做效率更高”的建議。有時這些建議會給你一個合理的詳細解釋,但更多時候,你會發現沒有任何實際資料支援這些說法。

最近我發現了一個奇怪的東西,那就是 final 關鍵字。說起來有些慚愧,我之前沒怎麼了解過這個關鍵詞。許多部落格文章都說,使用 final 可以提升性能,而且是免費的,隻需進行微小改動。

但是,讀完這些文章後,你會發現一個有趣的事實:沒有人給出任何相關資料,基本上就純靠一個“相信我吧,兄弟”。

從我的經驗來看,除非有資料支援,否則任何性能提升的說法就全是空談,甚至就算有了資料也得能夠複現才行——是以,作為一名有着高性能 C++ 項目的優秀工程師,我真的很想驗證這個說法。

有一個我認為非常适合測試 final 關鍵字的絕佳項目:PSRayTracing(https://github.com/define-private-public/PSRayTracing)。

簡單介紹一下這個項目:這是一個用 C++ 實作的光線追蹤器,源自 Peter Shirley 所寫的光線跟蹤系列書籍。它主要用于學術目的,但也結合了我編寫 C++ 時的專業經驗。項目目标是向讀者展示如何(重新)編寫 C++,使其性能更強、更簡潔、結構更合理,是以在 Shirley 博士原始代碼的基礎上進行了補充和改進。PSRayTracing 有一個重要特性,能通過 CMake 切換代碼的更改,還可以提供其他選項,如随機種子、多核渲染。

使用 C++ 中的 final 關鍵字,到底能否提升性能?
使用 C++ 中的 final 關鍵字,到底能否提升性能?

這是如何做到的?

利用建構系統,我在 CMakeLists.txt 中添加了一個額外選項:

option(WITH_FINAL "Use the `final` specifier on derived classes (faster?)" OFF)               # ...               if (WITH_FINAL)               message(STATUS "Using `final` spicifer (faster?)")               target_compile_definitions(PSRayTracing_StaticLibrary PUBLIC USE_FINAL)               else()               message(STATUS "Turned off use of `final` (slower?)")               endif()           

然後在 C++ 中,我們可以使用預處理器來建立一個 FINAL 宏:

#ifdef USE_FINAL               #define FINAL final               #else               #define FINAL               #endif           

而且,它可以輕松地添加到任何你感興趣的類中:

$ rg FINAL               RandomGenerator.hpp               185:class RandomGenerator FINAL : public _GeneralizedRandomGenerator<std::uniform_real_distribution, rreal, RNG_ENGINE> {               Materials/Lambertian.hpp               8:class Lambertian FINAL : public IMaterial {               ...               Materials/SurfaceNormal.hpp               7:class SurfaceNormal FINAL : public IMaterial {               ...               PDFs/HittablePDF.hpp               7:class HittablePDF FINAL : public IPDF {               ...               Objects/Sphere.hpp               19:class Sphere FINAL : public IHittable {           

這樣,我們就可以在代碼庫中随時開始和停止對 final 關鍵字的使用了。

當然,你可能會說這個方法太過繁瑣,我也這麼覺得。但不得不說,這很适合用來做對照試驗:将 final 關鍵字應用到代碼中,并根據實驗需要使用或關閉它。

幾乎每個接口都有 final 關鍵字。在架構中,我們有 IHittable、IMaterial、ITexture 等。在 Peter Shirley 光線跟蹤系列的第二本書中,最後一個場景有很多超過 10K 個虛拟對象:

使用 C++ 中的 final 關鍵字,到底能否提升性能?

另外,有些場景的數量并不多(可能隻有 10 個):

使用 C++ 中的 final 關鍵字,到底能否提升性能?
使用 C++ 中的 final 關鍵字,到底能否提升性能?

最初的擔憂

對于 PSRT 來說,在測試可能提高性能的東西時,我首先使用的是預設場景 book2::final。啟用 final 後,控制台報告如下:

$ ./PSRayTracing -n 100 -j 2              Scene: book2::final_scene              ...              Render took 58.587 seconds           

可随後又恢複了更改:

$ ./PSRayTracing -n 100 -j 2              Scene: book2::final_scene              ...              Render took 57.53 seconds           

我有點困惑,用了 final 反而更慢了?又跑了幾次後,我發現性能下降得非常小,那些部落格文章一定是在忽悠我……

不過在完全放棄之前,我想最好還是把驗證測試腳本拿出來看看。在之前的版本中,這個腳本主要用于對 PSRayTracing 進行模糊測試,版本庫中已經包含了一套小型的知名測試用例。

這套腳本最初運作大約需要 20 分鐘,此時情況開始變得有趣:腳本報告稱,使用 final 時速度稍快,運作時間為 11 分 29 秒;不使用 final 則為 11 分 44 秒。

看似隻相差了 2% 的時長,實際上卻很重要——我決定,要進一步調查。

使用 C++ 中的 final 關鍵字,到底能否提升性能?

大型測試

由于對以上結果不滿意,我建立了一個“大型測試套件”,主要提高了一些測試參數以加強測試強度。在我的開發機器上,它需要運作 8 個小時。以下是調整後的詳情:

● 場景測試次數:10 → 30

● 圖像尺寸:[320x240, 400x400, 852x480] → [720x1280, 720x720, 1280x720]

● 光線深度:[10, 25, 50] → [20, 35, 50]

● 每像素采樣次數:[5, 10, 25] → [25, 50, 75]

我認為這樣更全面:現在有些測試用例隻需 10 秒就能完成,有些則需要 10 分鐘才能完成;小型測試套件在 20 多分鐘内完成了約 350 個測試用例,而這個套件在 8 多小時内完成了 1150 多個測試用例。

考慮到 C++ 程式的性能與編譯器(和系統)密切相關,是以為了更加徹底,我們在三台機器、三種作業系統和三種不同的編譯器上都進行了測試;一次使用了 final,一次沒使用。經過計算,這些計算機累計運作了 125 多小時。

具體情況請參見下表,另外配置如下:

● AMD Ryzen 9:

Linux:GCC & Clang

Windows:GCC & MSVC

● Apple M1 Mac:GCC & Clang

● Intel i7:Linux GCC

例如,一種配置是“AMD Ryzen 9,使用 Ubuntu Linux 和 GCC”,另一種是“Apple M1 Mac,使用 macOS 和 Clang”。注意,并非所有編譯器的版本都相同,有些很難獲得。另外在我發文的時候,Clang 還釋出了一個新版本。下面是測試結果的總體摘要:

使用 C++ 中的 final 關鍵字,到底能否提升性能?

通過對比測試,我們可以看到一些有趣的結論,同時也告訴了我們一件事:從整體上看,使用 final 不能保證總是提速,甚至某些情況下速度還會更慢。

雖然在這個測試中對編譯器進行比較可能也很有趣,但認為這樣做并不公平:僅把“使用 final”和“不使用 final”進行比較才是公平的。如果想要比較編譯器(以及不同系統),需要更全面的測試系統。

盡管如此,我們還是觀察到了一些有趣的結果:

  • x86_64 上的 Clang 運作速度較慢。
  • Windows 性能較差,微軟自己的編譯器也很落後。
  • 蘋果公司的晶片則是絕對的強者。

但每個場景都不同,包含的标記為 final 的對象數量也不盡相同。按百分比來看,有多少測試用例在使用 final 後更快或更慢都很有趣。将這些資料清單,我們可以得到以下結果:

使用 C++ 中的 final 關鍵字,到底能否提升性能?

對于某些 C++ 應用程式來說,那 1% 的性能提升非常令人期待(例如,高頻交易)。如果我們 50% 以上的測試用例都能達到這一點,那我們似乎應該考慮使用 final。但另一方面,我們還需要看看相反的情況:例如速度慢了多少?又有多少測試用例變慢了?

使用 C++ 中的 final 關鍵字,到底能否提升性能?

在 x86_64 Linux 上的 Clang 就絕對是一個典型:超過 90% 的測試用例在使用 final 後至少慢了 5%!!還記得我說過,對于某些應用程式來說,1% 的速度提升是件天大的好事嗎?是以相對的,就算隻慢了 1% 也絕不能容忍。此外,使用 MSVC 的 Windows 也表現不佳。

如上所述,這與場景有很大關系。有些場景隻有少量的虛拟對象,有些則有一大堆。下面平均來看,使用 final 後一個場景的速度快了/慢了多少:

使用 C++ 中的 final 關鍵字,到底能否提升性能?

我對 Pandas 不是很了解,在建立一個多級索引表格(從數組中建立)并使其具有良好的樣式和格式方面遇到了一些問題。是以,我在每一列名稱的末尾都附加了一個配置編号。以下是每個數字的含義:

0 - GCC 13.2.0 AMD Ryzen 9 6900HX Ubuntu 23.10

1 - Clang 17.0.2 AMD Ryzen 9 6900HX Ubuntu 23.10

2 - MSVC 17 AMD Ryzen 9 6900HX Windows 11 Home (22631.3085)

3 - GCC 13.2.0 (w64devkit) AMD Ryzen 9 6900HX Windows 11 Home (22631.3085)

4 - Clang 15 M1 macOS 14.3 (23D56)

5 - GCC 13.2.0 (homebrew) M1 macOS 14.3 (23D56)

6 - GCC 12.3.0 i7-10750H Ubuntu 22.04.3

這就是讓人眼前一亮的地方:在某些配置和特定場景下,性能可能會提升 10%!例如,在 AMD 和 Linux 上使用 GCC 的 book1::final_scene。但其他場景(在相同的配置下)僅有 0.5% 的提升,比如 fun::three_spheres。

但是,隻是将編譯器切換到 Clang(仍在 AMD 和 Linux 上運作)後,這兩個場景的性能就分别下降了 5% 和 17%!MSVC(在 AMD 上)的情況有點複雜,有些場景在使用 final 時性能更高,有些場景則受到了很大影響。

蘋果的 M1 有點意思,提速和降速幅度看起來都非常小,但 GCC 在兩個場景上有顯著優勢。

另外,使用 final 後性能的提升或降低,幾乎與虛拟對象數量是多是少沒有關系。

使用 C++ 中的 final 關鍵字,到底能否提升性能?

我比較關注 Clang 的情況

PSRayTracing 也可在 Android 和 iOS 上運作。在這兩個平台上,可能隻有一小部分應用程式為了性能是用 C++ 編寫的,而 Clang 正是用于這兩個平台的編譯器。

不幸的是,我沒有像在桌面系統上那樣的性能測試架構,但我可以做一個簡單的“使用相同參數渲染場景,一個使用 final,一個不使用 final”的測試,因為應用程式會報告程序耗時。

根據上面的資料,我的假設是,這兩個平台在使用 final 後性能會變差,但具體變差多少不清楚。以下是測試結果:

  • iPhone 12:我認為沒有差別;無論使用 final 與否,渲染相同的場景都需要大約 2 分鐘 36 秒。
  • Pixel 6 Pro:使用 final 後速度變慢了。渲染時間分别為 49 秒和 46 秒,3 秒的差異可能看起來不是很大,這相當于 6% 的減速,意義相當重大。

我不知道這是 Clang 的問題還是 LLVM 的問題。如果是後者,這可能對其他 LLVM 語言(如 Rust 和 Swift)也有影響。

使用 C++ 中的 final 關鍵字,到底能否提升性能?

未來的計劃(以及我希望自己做的事情)

總的來說,我對這次測試發現的東西非常滿意。如果我能重做一些事情(或得到一筆錢來做這個項目),我希望能做到以下幾點:

  • 讓每個場景都能報告一些中繼資料。例如,對象數量、材質等。
  • 對 Jupyter+Pandas 有更好的了解。雖然我是一位 C++ 開發者,不是資料科學家,但我希望能了解如何更好地轉換測量結果,使其看起來更美觀。
  • 找到一種在 Android 和 iOS 上運作自動化測試的方法。目前這兩個平台都不容易測試,這是一個很明顯的問題。
  • run_verfication_tests.py 更像是一個應用程式(而不是一個小腳本)。
  • PNG 開始變得有些龐大,有一次我磁盤空間都不足了。無損 WebP 作為渲染輸出可能更好。
  • 比較更多的英特爾晶片,并使用更多的編譯器。
使用 C++ 中的 final 關鍵字,到底能否提升性能?

結論

如果你隻是匆匆翻到結尾,以下是總結:

  • 使用 GCC 可能會得到一些好處。
  • 對蘋果晶片的影響不大。
  • 不要在 Clang 和 MSVC 上使用 final。
  • 這完全取決于你的配置/平台,請自主測試并衡量是否值得。

最後,就我個人而言,我應該不會用 C++ 的 final 關鍵字來提升性能,本文的測試結果說明了這種方式并不穩定。