【问题标题】:Is there a flaw in how clang implements char8_t or does some dark corner of the standard prohibit optimization?clang 实现 char8_t 的方式是否存在缺陷,或者标准的某些暗角是否禁止优化?
【发布时间】:2023-03-05 01:16:01
【问题描述】:

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中的错误?

【问题讨论】:

  • 看起来像一个缺陷/缺少优化。 GCC 为这两个函数生成相同的代码。
  • 请注意char8_t is actually a unsigned char,但即使对第一个函数进行更改,编译器仍会优化代码,因此不可能是这样:godbolt.org/z/uUhMkW
  • 我怀疑这可能与 char8_t 是库提供的类型,编译器没有内在理解有关。
  • 不完全相关,但仍然很有趣:将-stdlib=libc++ 添加到 Godbolt 中的编译选项 具有 相同的编译器输出,但优化程度较低(看起来像 Godbolt默认使用libstdc++)。见here。所以显然标准库版本似乎也很重要
  • 另请注意author's comment“此实现是实验性的,将被删除或大幅修改以匹配通过 C++ 委员会的提案。”

标签: c++ clang compiler-optimization c++20


【解决方案1】:
  1. 在 libstdc++ 中,std::equal 在检测到参数“简单”时调用__builtin_memcmp,否则它使用一个幼稚的 for 循环。这里的“简单”是指指向相同整数或指针类型的指针(或某些迭代器包装器)。(relevant source code)

    • 一个类型是否为整数类型由内部__is_integer trait 检测,但libstdc++ 8.2.0(godbolt.org 上使用的版本)没有专门针对char8_t,所以后者没有被检测到作为整数类型。(relevant source code)
  2. Clang(使用此特定配置)在 for 循环情况下比在 __builtin_memcmp 情况下生成更详细的程序集。 (但前者在性能方面不一定优化得少。见Loop_unrolling。)

所以这种差异是有原因的,这不是 clang IMO 中的错误。

【讨论】:

  • 我完全同意你的第一点,感谢指出相关源代码!但我不同意你的第二点:短程序集是在一条指令中比较四个字节,所以这比从详细程序集的简单展开更 loop-unrolled
【解决方案2】:

这不是 Clang 中的“错误”;只是错失了优化的机会。

您可以通过使用相同的函数来获取replicate the Clang compiler output,该函数采用基础类型为unsigned charenum class。相比之下,GCC 识别出具有unsigned charchar8_t 基础类型的枚举数之间的区别。它为unsigned charchar8_t 发出相同的代码,但为enum class 发出更复杂的代码。

因此,Clang 的char8_t 实现似乎更多地将其视为用户定义的枚举,而不是基本类型。最好将其视为标准的早期实施。

应该注意unsigned charchar8_t 之间最重要的区别之一是别名要求。 unsigned char 指针几乎可以与其他任何东西混淆。相比之下,char8_t 指针不能。因此,可以合理地预期(在成熟 实现上,而不是超过其实现的市场标准)在不同情况下发出不同的代码。诀窍是 char8_t 代码如果不同的话应该高效,因为编译器不再需要发出执行额外工作来处理来自商店的潜在别名的代码。

【讨论】:

  • 我认为别名不会以任何方式影响std::equal
猜你喜欢
  • 2012-03-06
  • 1970-01-01
  • 2019-06-03
  • 2014-12-16
  • 1970-01-01
  • 2011-02-22
  • 1970-01-01
  • 2020-10-09
  • 2020-02-25
相关资源
最近更新 更多