我撰寫了兩個函式來獲取陣列的總和,第一個是用 C 撰寫的,另一個是用行內匯編 (x86-64) 撰寫的,我比較了這兩個函式在我的設備上的性能。
如果在編譯期間未啟用-O標志,則具有行內匯編的函式幾乎比 C 版本快 4-5 倍。
cpp time : 543070068 nanoseconds cpp time : 547990578 nanoseconds asm time : 185495494 nanoseconds asm time : 188597476 nanoseconds
如果-O標志設定為-O1,它們會產生相同的性能。
cpp time : 177510914 nanoseconds cpp time : 178084988 nanoseconds asm time : 179036546 nanoseconds asm time : 181641378 nanoseconds
但是,如果我嘗試將-O標志設定為-O2或-O3,我將獲得一個不尋常的2-3 位數納秒性能,因為它是用行內匯編撰寫的函式,這非常快(至少對我來說,請耐心等待,因為我在匯編編程方面沒有扎實的經驗,所以我不知道它與用 C 撰寫的程式相比有多快或多慢。)
cpp time : 177522894 nanoseconds cpp time : 183816275 nanoseconds asm time : 125 nanoseconds asm time : 75 nanoseconds
我的問題
為什么啟用 -O2 或 -O3 后,使用行內匯編撰寫的陣列求和函式如此之快?
這是正常讀數還是性能的計時/測量有問題?
或者我的行內匯編函式可能有問題?
如果陣列 sum 的行內匯編函式正確且性能讀數正確,為什么 C 編譯器未能針對 C 版本優化簡單的陣列 sum 函式并使其與行內匯編版本一樣快?
我還推測,在編譯程序中可能會改進記憶體對齊和快取未命中以提高性能,但我對此的了解仍然非常有限。
除了回答我的問題,如果您有什么要補充的,請隨時這樣做,希望有人能解釋一下,謝謝!
[編輯]
因此,我洗掉了宏的使用并隔離運行這兩個版本,還嘗試添加volatile關鍵字、“memory”clobber 和“ &r”輸出約束,現在性能與cpp_sum相同。
雖然如果我洗掉volatile關鍵字和“記憶體”破壞它,我仍然可以獲得2-3位數納秒的性能。
代碼:
#include <iostream>
#include <random>
#include <chrono>
uint64_t sum_cpp(const uint64_t *numbers, size_t length) {
uint64_t sum = 0;
for(size_t i=0; i<length; i) {
sum = numbers[i];
}
return sum;
}
uint64_t sum_asm(const uint64_t *numbers, size_t length) {
uint64_t sum = 0;
asm volatile(
"xorq %%rax, %%rax\n\t"
"%=:\n\t"
"addq (%[numbers], %%rax, 8), %[sum]\n\t"
"incq %%rax\n\t"
"cmpq %%rax, %[length]\n\t"
"jne %=b"
: [sum]" &r"(sum)
: [numbers]"r"(numbers), [length]"r"(length)
: "%rax", "memory", "cc"
);
return sum;
}
int main() {
std::mt19937_64 rand_engine(1);
std::uniform_int_distribution<uint64_t> random_number(0,5000);
size_t length = 99999999;
uint64_t *arr = new uint64_t[length];
for(size_t i=1; i<length; i) arr[i] = random_number(rand_engine);
uint64_t cpp_total = 0, asm_total = 0;
for(size_t i=0; i<5; i) {
auto start = std::chrono::high_resolution_clock::now();
#ifndef _INLINE_ASM
cpp_total = sum_cpp(arr, length);
#else
asm_total = sum_asm(arr,length);
#endif
auto end = std::chrono::high_resolution_clock::now();
auto dur = std::chrono::duration_cast<std::chrono::nanoseconds>(end-start);
std::cout << "time : " << dur.count() << " nanoseconds\n";
}
#ifndef _INLINE_ASM
std::cout << "cpp sum = " << cpp_total << "\n";
#else
std::cout << "asm sum = " << asm_total << "\n";
#endif
delete [] arr;
return 0;
}
uj5u.com熱心網友回復:
編譯器正在將行內匯編從重復回圈中提升出來,從而從你的定時區域中提升出來。
如果您的目標是性能,https://gcc.gnu.org/wiki/DontUseInlineAsm。首先花時間學習的有用的事情是 SIMD 內在函式(以及它們如何編譯為 asm)喜歡使用單個 AVX2 指令_mm256_add_epi64
添加 4x 。uint64_t
請參閱https://stackoverflow.com/tags/sse/info(編譯器可以像這樣簡單地自動矢量化,如果您使用較小的陣列并在定時區域內放置一個重復回圈,您可以看到它的好處獲得一些快取命中。)
如果您想使用 asm 來測驗各種 CPU 上的實際速度,您可以在獨立的靜態可執行檔案或從 C 呼叫的函式中執行此操作。https://stackoverflow.com/tags/x86/info有一些很好的性能鏈接。
回復:基準測驗-O0
,是的,編譯器在默認的一致除錯下生成緩慢的 asm-O0
,并且根本不嘗試優化。當它的雙手被綁在背后時,擊敗它并不是什么挑戰。
為什么你asm
可以被吊出計時區域
如果沒有asm volatile
,您的asm
陳述句是您告訴編譯器關于 的輸入的純函式,它們是指標、長度和 的初始值sum=0
。它不包括指向的記憶體,因為您沒有為此使用虛擬"m"
輸入。(如何指示可以使用行內 ASM 引數*指向*的記憶體?)
沒有"memory"
clobber,您的asm 陳述句就不會按順序排列。函式呼叫,因此 GCC 將 asm 陳述句提升到回圈之外。 請參閱Google 的 `DoNotOptimize()` 函式如何強制執行陳述句排序"memory"
以獲取有關clobber效果的更多詳細資訊。
查看https://godbolt.org/z/KeEMfoMvo上的編譯器輸出,看看它是如何行內到main
. -O2
和更高的使能-finline-functions
,而-O1
只有使能-finline-functions-called-once
,這不是static
,或者inline
它必須發出一個獨立的定義,以防來自其他編譯單元的呼叫。
75ns 只是
上一篇:最大并發客戶端連接數GRPC 下一篇:遠程Websocket服務器性能std::chrono
一個幾乎為空的定時區域周圍的函式的時間開銷。 它實際上正在運行,只是不在定時區域內。如果您單步執行整個程式的 asm,或者例如在 asm 陳述句上設定斷點,您可以看到這一點。在對可執行檔案進行 asm 級除錯時,您可以通過像mov $0xdeadbeef,