cpu(基準方式)復制字串的最有效方式是什么?
我是 c 新手,我目前正在復制這樣的字串
char a[]="copy me";
char b[sizeof(a)];
for (size_t i = 0; i < sizeof(a); i ) {
b[i] = a[i];
}
printf("%s",b); // copy me
這是另一種選擇,while回圈比for回圈快一點(我聽說過)
char a[]="copy me";
char b[sizeof(a)];
char c[sizeof(a)];
void copyAString (char *s, char *t)
{
while ( (*s = *t ) != '\0');
};
copyAString(b,a);
printf("%s",c);
uj5u.com熱心網友回復:
當您可以使用標準函式時,不要撰寫自己的復制回圈,例如memcpy(當長度已知時)或strcpy(當它不知道時)。
現代編譯器將這些視為“內置”函式,因此對于常量大小可以將它們擴展為一些 asm 指令,而不是實際設定對庫實作的呼叫,這將不得不根據大小進行分支等等。因此,如果您memcpy因為庫函式呼叫短副本的開銷而避免使用,請不要擔心,如果長度是編譯時常量,則不會有一個。
但即使在未知/運行時可變長度的情況下,庫函式通常也是用 asm 手寫的優化版本,比純 C 中可以做的任何事情都要快得多(尤其是對于中大型字串),尤其是對于 strcpy 沒有讀取緩沖區末尾的未定義行為。
您的第一個代碼塊具有編譯時常量大小(您可以使用sizeof而不是strlen)。您的復制回圈實際上會被現代編譯器識別為固定大小的副本,并且(如果很大)變成對 的實際呼叫memcpy,否則通常會進行類似的優化。
如何進行陣列索引并不重要;優化編譯器可以看穿 size_t 索引或指標,并為目標平臺制作好的 asm。有關代碼實際編譯方式的示例,請參閱此和此Q&A。請記住,CPU 運行 asm,而不是直接運行 C。
不過,這個例子太小太簡單,實際上不能用作基準。請參閱績效評估的慣用方式?
您的第二種方式等效于strcpy隱式長度字串。這比較慢,因為它必須搜索終止的 0 位元組,如果在行內和展開回圈后編譯時不知道它。
特別是如果您像這樣手動為非常量字串執行此操作;現代 gcc/clang 無法自動矢量化回圈,程式無法在第一次迭代之前計算行程計數。即它們在strlen 和strcpy 等搜索回圈中失敗。
如果您實際上只是呼叫strcpy(dst, src),編譯器將以某種有效的方式行內擴展它,或者發出對庫函式的實際呼叫。libc 函式使用手寫 asm 來高效地執行此操作,尤其是在 SIMD 可以提供幫助的 x86 等 ISA 上。例如對于 x86-64,glibc 的 AVX2 版本(https://code.woboq.org/userspace/glibc/sysdeps/x86_64/multiarch/strcpy-avx2.S.html)應該能夠在每個時鐘周期復制 32 個位元組在 Zen2 和 Skylake 等主流 CPU 上的中型副本,在快取中具有熱源和目標。
現代 GCC/clang 似乎不像他們識別 memcpy 等效回圈那樣將此模式識別為 strcpy,因此如果您想要有效地復制未知大小的 C 字串,則需要使用實際strcpy的 . (或者更好的是,stpcpy得到一個指向 end 的指標,這樣你就可以知道字串的長度,允許你使用顯式長度的東西,而不是下一個函式也必須掃描字串的長度。)
一次寫一個char,最終會使用位元組加載/存盤指令,因此每個時鐘周期最多可以走 1 個位元組。(或者在 Ice Lake 上接近 2,可能在 5 寬前端用于加載/宏融合測驗/jz/存盤的瓶頸。)因此,對于具有運行時變數源的中型到大型副本來說,這是一場災難,其中編譯器無法洗掉回圈。
(https://agner.org/optimize/了解 x86 CPU 的性能。其他架構大致相似,除了 SIMD 對 strcpy 有多大用處。沒有 x86 的高效 SIMD->integer 能夠在 SIMD 比較結果上進行分支的 ISA 可能需要使用通用整數 bithacks,例如為什么 glibc 的 strlen 需要如此復雜才能快速運行? - 但請注意,這是 glibc 的可移植 C 回退,僅在沒有人撰寫手動調整的 asm 的少數平臺上使用。)
@0______ 聲稱對于 1024 個字符的字串,他們展開char的一次回圈比 glibc 快strcpy,但這是不可信的,可能是基準方法錯誤的結果。(比如編譯器優化失敗了基準,或者頁面錯誤開銷或 libc strcpy 的惰性動態鏈接。)
相關問答:
memcpy() 通常比 strcpy() 快嗎? - 是的,雖然 x86 上的大型副本 strcpy 幾乎可以跟上;x86 SIMD 可以有效地檢查整個塊的任何零位元組。
比 memcpy 更快的方法來復制 0 終止的字串
績效評估的慣用方式?- 微基準測驗很難:您需要編譯器優化應該優化的部分,但仍然在基準回圈中重復作業,而不是只做一次。
在 x86 和 x64 的同一頁面內讀取緩沖區末尾是否安全?- 是的,以及所有其他在對齊頁面中記憶體保護作業的 ISA。(它在技術上仍然是 C UB,但在 asm 中是安全的,因此庫函式的手寫 asm 可以 100% 安全地做到這一點。)
效率:陣列與指標
在 C 中,訪問我的陣列索引更快還是通過指標訪問更快?
uj5u.com熱心網友回復:
通常,復制字串最有效的方法是手動展開回圈以最小化所需的運算元量。
例子:
char *mystrcpy(char *restrict dest, const char * restrict src)
{
char *saveddest = dest;
while(1)
{
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
if(!(*dest = *src )) break;
}
return saveddest;
}
https://godbolt.org/z/q3vYeWzab
實作使用了一種非常相似的方法glibc。
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/418559.html
標籤:
