clang 实现 char8_t 的方式是否存在缺陷,或者标准的某些暗角是否禁止优化?
Is there a flaw in how clang implements char8_t or does some dark corner of the standard prohibit optimization?
clang 8.0.0 引入了对来自 c++20 的 char8_t
类型的支持。但是,我希望以下函数具有相同的编译器输出
#include <algorithm>
bool compare4(char const* pcha, char const* pchB, int n) {
return std::equal(pcha, pcha+4, pchB);
}
bool compare4(char8_t const* pchA, char8_t const* pchB, int n) {
return std::equal(pchA, pchA+4, pchB);
}
但是,他们 compile 在 -std=c++2a -O2
到
compare4(char const*, char const*, int): # @compare4(char const*, char const*, int)
mov eax, dword ptr [rdi]
cmp eax, dword ptr [rsi]
sete al
ret
_Z8compare4PKDuS0_i: # @_Z8compare4PKDuS0_i
mov al, byte ptr [rdi]
cmp al, byte ptr [rsi]
jne .LBB1_4
mov al, byte ptr [rdi + 1]
cmp al, byte ptr [rsi + 1]
jne .LBB1_4
mov al, byte ptr [rdi + 2]
cmp al, byte ptr [rsi + 2]
jne .LBB1_4
mov al, byte ptr [rdi + 3]
cmp al, byte ptr [rsi + 3]
sete al
ret
.LBB1_4:
xor eax, eax
ret
其中后者显然没有那么优化。
这是有原因的吗(我在标准中找不到任何原因)或者这是 clang 中的错误?
这不是 Clang 中的 "bug";只是错过了优化的机会。
您可以 replicate the Clang compiler output 通过使用接受基础类型为 unsigned char
的 enum class
的相同函数。相比之下,GCC 识别具有基础类型 unsigned char
和 char8_t
的枚举器之间的差异。它为 unsigned char
和 char8_t
发出相同的代码,但为 enum class
情况发出更复杂的代码。
因此,关于 Clang 对 char8_t
的实现,似乎更像是将其视为用户定义的枚举,而不是基本类型。最好将其视为标准的早期实施。
应该注意 unsigned char
和 char8_t
之间最重要的区别之一是别名要求。 unsigned char
指针可能与其他任何东西都有别名。相比之下,char8_t
指针不能。因此,有理由期望(在 成熟的 实现上,而不是在市场上超过它实现的标准的东西上)在不同情况下发出不同的代码。诀窍是 char8_t
代码应该 更多 高效,因为编译器不再需要发出执行额外工作的代码来处理存储的潜在别名。
在 libstdc++ 中,std::equal
在检测到参数为 "simple" 时调用 __builtin_memcmp
,否则它会使用朴素的 for 循环。 "Simple" 这里表示指向相同整数或指针类型的指针(或某些围绕指针的迭代器包装器)。(relevant source code)
- 内部
__is_integer
特征检测类型是否为整数类型,但libstdc++ 8.2.0(godbolt.org上使用的版本)没有专门为char8_t
, 所以后者没有被检测为整数类型。(relevant source code)
Clang(具有此特定配置)在 for 循环情况下比在 __builtin_memcmp
情况下生成更冗长的程序集。 (但前者在性能方面未必优化得少。见Loop_unrolling。)
所以这种差异是有原因的,这不是 clang IMO 中的错误。
clang 8.0.0 引入了对来自 c++20 的 char8_t
类型的支持。但是,我希望以下函数具有相同的编译器输出
#include <algorithm>
bool compare4(char const* pcha, char const* pchB, int n) {
return std::equal(pcha, pcha+4, pchB);
}
bool compare4(char8_t const* pchA, char8_t const* pchB, int n) {
return std::equal(pchA, pchA+4, pchB);
}
但是,他们 compile 在 -std=c++2a -O2
到
compare4(char const*, char const*, int): # @compare4(char const*, char const*, int)
mov eax, dword ptr [rdi]
cmp eax, dword ptr [rsi]
sete al
ret
_Z8compare4PKDuS0_i: # @_Z8compare4PKDuS0_i
mov al, byte ptr [rdi]
cmp al, byte ptr [rsi]
jne .LBB1_4
mov al, byte ptr [rdi + 1]
cmp al, byte ptr [rsi + 1]
jne .LBB1_4
mov al, byte ptr [rdi + 2]
cmp al, byte ptr [rsi + 2]
jne .LBB1_4
mov al, byte ptr [rdi + 3]
cmp al, byte ptr [rsi + 3]
sete al
ret
.LBB1_4:
xor eax, eax
ret
其中后者显然没有那么优化。 这是有原因的吗(我在标准中找不到任何原因)或者这是 clang 中的错误?
这不是 Clang 中的 "bug";只是错过了优化的机会。
您可以 replicate the Clang compiler output 通过使用接受基础类型为 unsigned char
的 enum class
的相同函数。相比之下,GCC 识别具有基础类型 unsigned char
和 char8_t
的枚举器之间的差异。它为 unsigned char
和 char8_t
发出相同的代码,但为 enum class
情况发出更复杂的代码。
因此,关于 Clang 对 char8_t
的实现,似乎更像是将其视为用户定义的枚举,而不是基本类型。最好将其视为标准的早期实施。
应该注意 unsigned char
和 char8_t
之间最重要的区别之一是别名要求。 unsigned char
指针可能与其他任何东西都有别名。相比之下,char8_t
指针不能。因此,有理由期望(在 成熟的 实现上,而不是在市场上超过它实现的标准的东西上)在不同情况下发出不同的代码。诀窍是 char8_t
代码应该 更多 高效,因为编译器不再需要发出执行额外工作的代码来处理存储的潜在别名。
在 libstdc++ 中,
std::equal
在检测到参数为 "simple" 时调用__builtin_memcmp
,否则它会使用朴素的 for 循环。 "Simple" 这里表示指向相同整数或指针类型的指针(或某些围绕指针的迭代器包装器)。(relevant source code)- 内部
__is_integer
特征检测类型是否为整数类型,但libstdc++ 8.2.0(godbolt.org上使用的版本)没有专门为char8_t
, 所以后者没有被检测为整数类型。(relevant source code)
- 内部
Clang(具有此特定配置)在 for 循环情况下比在
__builtin_memcmp
情况下生成更冗长的程序集。(但前者在性能方面未必优化得少。见Loop_unrolling。)
所以这种差异是有原因的,这不是 clang IMO 中的错误。