更新:相關 GCC 錯誤報告:https : //gcc.gnu.org/bugzilla/show_bug.cgi?id=103798
我測驗了以下代碼:
#include <string_view>
size_t findFirstE_slow(std::string_view sv) {
return sv.find_first_of("eE");
}
size_t findFirstE_fast(std::string_view sv) {
auto it{sv.begin()};
for (; it != sv.end() && *it != 'e' && *it != 'E'; it)
;
return it == sv.end() ? std::string_view::npos : size_t(it - sv.begin());
}
快速臺架測驗:https : //quick-bench.com/q/dSU3EBzI8MtGOFn_WLpK3ErT3ok
編譯器資源管理器輸出:https : //godbolt.org/z/eW3sx61vz
這兩個findFirstE_slow()和firstFirstE_fast()功能是為了做同樣的事情,但findFirstE_slow()運行速度較慢顯著(至少5倍的快速臺架試驗)。
這是x86-64 gcc (trunk) -std=c 20 -O3.
findFirstE_slow():
.LC0:
.string "eE"
findFirstE_slow(std::basic_string_view<char, std::char_traits<char> >):
push r12
push rbp
push rbx
test rdi, rdi
je .L4
mov rbx, rdi
mov rbp, rsi
xor r12d, r12d
jmp .L3
.L8:
add r12, 1
cmp rbx, r12
je .L4
.L3:
movsx esi, BYTE PTR [rbp 0 r12]
mov edx, 2
mov edi, OFFSET FLAT:.LC0
call memchr
test rax, rax
je .L8
mov rax, r12
pop rbx
pop rbp
pop r12
ret
.L4:
mov r12, -1
pop rbx
pop rbp
mov rax, r12
pop r12
ret
findFirstE_fast():
findFirstE_fast(std::basic_string_view<char, std::char_traits<char> >):
add rdi, rsi
cmp rdi, rsi
je .L13
mov rax, rsi
jmp .L12
.L15:
add rax, 1
cmp rdi, rax
je .L13
.L12:
movzx edx, BYTE PTR [rax]
and edx, -33
cmp dl, 69
jne .L15
sub rax, rsi
ret
.L13:
mov rax, -1
ret
有趣的是,findFirstE_slow()要求memchr("eE", *current_char, 2)在每一個字符sv。另一方面,findFirstE_fast()通過將每個字符sv與 'e' 和 'E'進行比較,執行我們合理預期的操作。
Clang 生成類似的輸出。
問題:對于像我的測驗中那樣的短字串,這里是否遺漏了優化?我是否缺少讓 GCC 生成更快代碼的東西?
uj5u.com熱心網友回復:
libstdc std::string_view::find_first_of看起來像:
size_type find_first_of(std::string_view v, std::size_t pos = 0) {
if (v.empty()) return npos;
for (; pos < size(); pos) {
const char_type* p = traits_type::find(v.data(), v.size(), this->data()[pos]);
if (p) return pos;
}
return npos;
}
您可以看到如何traits_type::find轉換為memchr.
問題的關鍵在于memchr("eE", this->data()[pos], 2) != nullptr它的編譯方式與 不同 this->data()[pos] == 'e' || this->data()[pos] == 'E',即使后者效率更高。
您可以通過嘗試編譯來檢查這一點:
constexpr unsigned char characters[] = "eE";
bool a(unsigned char* p) {
return __builtin_memchr(characters, *p, 2);
}
bool b(unsigned char* p) {
return *p == characters[0] || *p == characters[1];
}
這是一個遺漏的優化,但您可以提示編譯器不要將 memchr 與自定義特征型別一起使用:
struct char_traits : std::char_traits<char> {
static constexpr const char_type* find(const char_type* p, std::size_t count, const char_type& ch) {
if (__builtin_constant_p(count) && count < 5) {
switch (count) {
case 0: return nullptr;
case 1: return ch == *p ? p : nullptr;
case 2: return ch == *p ? p : ch == * p ? p : nullptr;
case 3: return ch == *p ? p : ch == * p ? p : ch == * p ? p : nullptr;
case 4: return ch == *p ? p : ch == * p ? p : ch == * p ? p : ch == * p ? p : nullptr;
}
}
return std::char_traits<char>::find(p, count, ch);
}
};
using string_view = std::basic_string_view<char, char_traits>;
size_t findFirstE_slow(string_view sv) {
return sv.find_first_of(characters);
}
// Also your "fast" version needs to return
// return it == sv.end() ? string_view::npos : size_t(it - sv.begin());
// to be equivalent
( https://godbolt.org/z/bhPPxjboE )
而https://quick-bench.com/q/QVxVTxGEagUUCPuhFi9T8wjI1qQ說的慢版,現在只有1.3倍速度較慢。使用更大的字串(https://quick-bench.com/q/el0ukDywBNMoGsEb33PM_g4WUaY;在 a 之前的 8000 個字符'e'),差異幾乎不明顯。
現在的主要區別是一個迭代索引,另一個迭代指標(最后回傳差異)。匯編中的兩個不同指令是movzx edx, BYTE PTR [rsi rax]and movzx edx, BYTE PTR [rax] sub rax, rsi,您應該發現第二個版本稍微快一點(特別是漸近,因為減法發生在回圈之外)
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/395674.html
