基于C++ 語言庫的GCC和Clang編譯器基準(zhǔn)測試報(bào)告(ETL)
從我使用 C++ 代碼完成了不同編譯器的基準(zhǔn)測試到現(xiàn)在,已經(jīng)有一段時間了。由于我最近發(fā)布了 ETL 項(xiàng)目的 1.1 版(一個具有表達(dá)式模板的優(yōu)化矩陣/向量計(jì)算庫),所以我決定使用它作為我的基準(zhǔn)測試的基版本。它是一個帶有大量模板的 C++ 14 庫。我要編譯完整的測試套件(124 個測試用例)。這是直接在最新版本(1.1)的代碼上完成的。我將在調(diào)試模式下編譯一次,并在 release_debug(release + debug 符號和斷言)下進(jìn)行一次編譯,并記錄每個編譯器的執(zhí)行時間。該測試將使用支持 ETL 中的每個選項(xiàng)的配置進(jìn)行編譯,以此計(jì)算最大的編譯時間。每次編譯都使用四個線程(make -j4)。 我還做了一些基準(zhǔn)測試,以了解每個編譯器生成的代碼間的運(yùn)行時的性能差異?;鶞?zhǔn)測試將編譯為發(fā)布模式,并記錄其編譯時間。
我將測試以下編譯器:
- GCC-4.9.4
- GCC-5.4.0
- GCC-6.3.0
- GCC-7.1.0
- clang-3.9.1
- clang-4.0.1
- zapcc-1.0 (商業(yè)版,基于 clang-5.0 主分支)
所有這些都是直接使用 Portage(Gentoo 軟件包管理器)安裝的,除了從源代碼安裝的 clang-4.0.1 以及沒有 Gentoo 軟件包的 zapcc。由于 Gentoo 上的 clang 包不支持多進(jìn)程,所以我不得不從源代碼中安裝一個版本,從包管理器中安裝另一個版本。這也是我測試較少版本的 clang 的原因,更實(shí)用點(diǎn)。
為了實(shí)現(xiàn)這些測試的目標(biāo),所有編譯器都使用了完全相同的選項(xiàng)。通常,我在 clang 上使用比 GCC 更多不同的選項(xiàng)(主要是考慮到在 clang 上更嚴(yán)格的向量化選項(xiàng))。這可能不會使得每個編譯器達(dá)到最佳性能,但可以對使用默認(rèn)優(yōu)化級別的輸出之間進(jìn)行比較。以下是使用的主要選項(xiàng):
- 調(diào)試模式下: -g
- 發(fā)布+調(diào)試模式下: -g -O2
- 發(fā)布模式下: -g -O3 -DNDEBUG -fomit-frame-pointer
每種情況都啟用了許多警告,ETL 選項(xiàng)也是一樣的。
所有的測試結(jié)果都是運(yùn)行在 Intel Core i7-2600(Sandy Bridge ...)@ 3.4GHz 上的 Gentoo 機(jī)器上收集的,該機(jī)器具有 4 核和 8 線程、12G 的 RAM 和一個 SSD。我盡可能地從干擾項(xiàng)中分離出基準(zhǔn)數(shù)據(jù),并且我的基準(zhǔn)代碼是相當(dāng)健全的,但是有些結(jié)果可能并不完全準(zhǔn)確。此外,一些基準(zhǔn)測試是在使用多線程,這可能會增加一些干擾和不可預(yù)測性。當(dāng)我對測試結(jié)果不太確定時,我會多次運(yùn)行基準(zhǔn)測試以對此確認(rèn),并且總體而言,我對結(jié)果很有信心。
編譯時間
讓我們從編譯器自身的性能結(jié)果開始:
注: 在 Release_Debug 和 Benchmark,我對 zapcc 只使用了三個線程, 因?yàn)?12Go 的內(nèi)存對于四個線程并不足夠。
不同的編譯器之間有一些非常重要的區(qū)別??偟貋碚f,clang-4.0.1 是迄今為止調(diào)試模式下最快的免費(fèi)編譯器。然而,當(dāng)測試代碼被添加優(yōu)化選項(xiàng)加以編譯,clang 就落后了。在調(diào)試模式和發(fā)布模式下,clang-4.0.1 比 clang-3.9.1 快得多,這一點(diǎn)令人印象深刻。在這一點(diǎn)上 clang 團(tuán)隊(duì)干得不錯!這些優(yōu)化,使得 clang-4.0.1 在發(fā)布模式下幾乎與 gcc-7.1 平分秋色。對于 GCC 來說,優(yōu)化的成本似乎一直在顯著地上升。然而, GCC 7.1 似乎使得優(yōu)化加快,也使得標(biāo)準(zhǔn)編譯快了許多。如果我們考慮 zapcc,這是調(diào)試模式下最快的編譯器,但它的速度在發(fā)布模式下比幾個 gcc 版本要慢。
總地來說,我對 clang-4.0.1 的性能印象深刻,它看起來真快!在不久的將來,我一定會用這個新版本做更多的測試。看到 g++-7.1 的編譯速度確實(shí)快于 gcc-6.3,也同樣令人欣慰。然而,對優(yōu)化而言,最快的 gcc 版本仍然是 gcc-4.9.4 ,這已經(jīng)是一個對 C++ 標(biāo)準(zhǔn)低支持的老版本。
運(yùn)行時性能
現(xiàn)在來看看生成的代碼的質(zhì)量。對于一些基準(zhǔn)測試,我已經(jīng)包含了兩個版本的算法。 std 是最簡單的算法(原始版),vec 是手工向量化和優(yōu)化的實(shí)現(xiàn)版本。所有的測試都是在單精度浮點(diǎn)上完成的。
點(diǎn)乘
運(yùn)行的第一個基準(zhǔn)是計(jì)算兩個向量之間的點(diǎn)積。讓我們先看看原始版的性能:
不同的編譯器之間的差異不是很大?;?clang 的編譯器似乎是生成速度最快代碼的編譯器。有趣的是,gcc-6.3 似乎在大數(shù)據(jù)量的容器中有一個很大的性能衰減,但在 gcc-7.1 中已經(jīng)解決了。
如果我們查看優(yōu)化版本的結(jié)果,其中差異更小。同樣,基于 clang 的編譯器生成的可執(zhí)行文件是最快的,但緊隨其后的是 gcc,除了 gcc-6.3 之外,我們?nèi)匀豢梢钥吹脚c之前相同的性能衰退。
Logistic Sigmoid
下一個測試是檢查 sigmoid 操作的性能。在這種情況下,庫的評估者將嘗試使用并行化和向量化來計(jì)算。讓我們看看不同編譯器的開銷如何:
有趣的是,我們可以看到,gcc-7.1 在少量數(shù)據(jù)時是最快的,而 clang-4.0 最適合生成較大數(shù)據(jù)時的代碼。然而,除了最大的向量大小,差異并不是很明顯。顯然,zapcc(或 clang-5.0)有一個回歸,因?yàn)樗?clang-4.0 慢,并與 clang-3.9 相同速度。
Y = Alpha * X + Y (axpy)
第三個基準(zhǔn)是著名的 axpy(y = alpha * x + y)。這是完全由庫中的表達(dá)式模板決定的,沒有使用特定的算法。我們來看看結(jié)果:
即使是最大的 vector,一旦向量化和并行化之后,這也是一個非??焖俚牟僮鳌R赃@種速度,觀察到的一些差異可能不是很重要。再次,基于 clang 的版本是這段代碼中最快的版本,但差異還是很小。在 gcc-7.1 中似乎還有一點(diǎn)回歸,但這也是相當(dāng)小的。
矩陣間的乘法 (GEMM)
下一個基準(zhǔn)測試是測試 Matrix-Matrix 乘法的性能,這是在 BLAS 命名中被稱為 GEMM 的操作。在這種情況下,我們同時測試原始的和優(yōu)化的向量化實(shí)現(xiàn)。為了節(jié)省一些橫向空間,我把表分成兩部分。
這一次,不同編譯器之間的性能差異非常大。clang 編譯器現(xiàn)在是大幅度領(lǐng)先,其中 clang-4.0 是他們中最快的(也有不錯的提升幅度)。事實(shí)上,clang-4.0.1 生成代碼,平均比最好的 GCC 編譯器生成的代碼速度快兩倍。非常有趣的是,從 GCC-5.4 開始,我們可以看到一個巨大的性能衰退,而且這種衰退還在 GCC-7.1 中。事實(shí)上,測試版本中最好的 GCC 版本依然是 GCC-4.9.4。Clang 真的在編譯 GEMM 代碼方面做得很好。
至于優(yōu)化的版本,這兩大家族是相反的。的確,GCC 在這方面做的工作比 clang 要好,盡管差距沒有以前那么大了,但還是值得注意。我們還是可以觀察到 GCC 版本中的一個小回歸,因?yàn)?4.9 版本依然是最快的。至于 clang 版本,似乎 clang-5.0 (在 zapcc 中使用)在這個例子中有了很多的性能改進(jìn)。
在這個例子中矩陣相乘,它是非常令人印象深刻的,優(yōu)化與非優(yōu)化代碼在性能上差異非常巨大。并且,令人印象深刻的是,每種類型的編譯器都有它們的長處,clang 看起來更適合處理沒優(yōu)化過的代碼,而 GCC 更適合處理向量化的代碼。