C++ 参数传递优化普通类大小

C++ parameter passing optimization of trivial classes size

提问人:resu 提问时间:11/4/2023 最后编辑:resu 更新时间:11/4/2023 访问量:106

问:

我有一个微不足道的,它代表了 std::int8_t 的类型安全包装器。 已简化为表示最小的可重现问题。struct AA

出于性能原因,在此示例中将 struct 作为参数传递时,结构应遵循与 but 相同的优化:-O3std::int8_t

#include <cstdint>

using in = std::int8_t;

template<typename T>
struct A {
    T value;
};

template <typename T>
inline constexpr auto operator +( A <T> lhs, A <T> rhs)
noexcept -> decltype(lhs.value + rhs.value)
{ return lhs.value + rhs.value; }

template <typename T>
inline constexpr auto operator -( A <T> lhs, A <T> rhs)
noexcept -> decltype(lhs.value - rhs.value)
{ return lhs.value - rhs.value; }

template <typename T>
inline constexpr auto operator *( A <T> lhs, A <T> rhs)
noexcept -> decltype(lhs.value * rhs.value)
{ return lhs.value * rhs.value; }

auto h(A<in> a, A<in> b){
    return (a*b)*(a-b);
}

auto g(in a, in b){
    return (a*b)*(a-b);
}

该函数(与函数相比,我调用的函数包含两个开销:h(A<std::int8_t>, A<std::int8_t>)movsxg(std::int8_t, std::int8_t)

h(A<signed char>, A<signed char>):                            
        movsx   eax, dil  <-- diff
        movsx   ecx, sil  <-- diff
        mov     edx, ecx
        imul    edx, eax
        sub     eax, ecx
        imul    eax, edx
        ret
g(signed char, signed char):                                  
        mov     eax, esi
        imul    eax, edi
        sub     edi, esi
        imul    eax, edi
        ret
  • 问题是否与标准 C++ 的某些结构空间约束有关?请解释一下背后的理论

  • 提供(如果存在)一种可移植/特定于编译器的优化方式。A

注意:h == g 对于每个 std::int*_t != (std::int8_t 或 std::int16_t) (即 std::int32_t, std::int64_t)

C++ 优化 clang 编译器优化 调用约定

评论

1赞 Some programmer dude 11/4/2023
请使您的问题自成一体,无需链接到外部网站。将最小的可重现示例复制粘贴到问题本身中。
0赞 StoryTeller - Unslander Monica 11/4/2023
我没有看到 16 位整数的等价性
1赞 463035818_is_not_an_ai 11/4/2023
我怀疑宏会使代码更短。我不得不使用才能看到实际的代码。为什么不显示从宏生成的实际代码?-E
1赞 Peter Cordes 11/4/2023
Clang 使用对 x86-64 System V 调用约定的未记录扩展,其中窄整数参数扩展到 32 位。显然,这只适用于原始类型,而不适用于结构包装器。GCC 使函数调用与此兼容,但不依赖于它来表示传入的参数:它将用于这两个函数。请参阅向 x86-64 ABI 的指针添加 32 位偏移量时是否需要符号或零扩展?movsx
0赞 463035818_is_not_an_ai 11/4/2023
这是故意的,例如返回?(用宏也不明显,天真地我以为是返回类型)A<T> + A<T>TA<T>

答:

3赞 Peter Cordes 11/4/2023 #1

Clang 使用对 x86-64 System V 调用约定的未记录扩展,其中窄整数参数扩展到 32 位。显然,这只适用于原始类型,而不适用于结构包装器。GCC 使函数调用与此兼容,但不依赖于它来表示传入的参数:它将用于这两个函数。请参阅向 x86-64 ABI 的指针添加 32 位偏移量时是否需要符号或零扩展?movsx

https://godbolt.org/z/ssYqrhr1n 显示 AArch64 clang 必须用于两个版本,因此此调用约定扩展似乎是特定于目标的。sxtb

函数返回 int(因为您使用返回类型,并且 narrow 将隐式整数提升为 )。如果尚未保证输入的符号扩展为宽度,则必须对输入进行符号扩展,以便从对提升值的操作中获得正确的结果。autoTintint8_tintint

如果你 make 或 return ,clang 足够聪明,可以优化符号扩展,因为它知道像 // 这样的低位操作不依赖于高位的临时;只有像除法和右移这样的东西才能做到这一点。(向 x86-64 ABI 的指针添加 32 位偏移量时是否需要符号或零扩展名?h()g()int8_taddsubmul)

int8_t h(A<in> a, A<in> b){
    return (a*b)*(a-b);
}

戈德博尔特

// x86-64 clang 17 -O3
h(A<signed char>, A<signed char>):
        mov     eax, esi
        mul     dil
        sub     dil, sil
        mul     dil
        ret
// AArch64 clang
h(A<signed char>, A<signed char>):
        mul     w8, w1, w0
        sub     w9, w0, w1
        mul     w0, w8, w9
        ret

正如 @463035818_is_not_an_ai 所指出的,您实际上可能希望模板函数返回 A<T> 而不仅仅是 or ;这也将避免对符号扩展的需要。Tint

评论

0赞 resu 11/4/2023
我知道返回而不是 or : 我更关心在不更改函数类型的情况下解决性能损失的解决方案hintTA<T>
1赞 Peter Cordes 11/4/2023
@resu:与函数体中的工作量相比,内联通常使参数传递开销较小。如果这些类型在 ABI 边界(在共享库和使用它的应用程序之间)不可见,希望 LTO 可以避免在热路径中出现过多的非内联函数调用。实际的模板函数本身将始终内联,但 your 仍然很小,应该只在它可以内联的地方使用。h()
0赞 Peter Cordes 11/4/2023
@resu: I don't know what the rules are for the undocumented extension to the x86-64 SysV calling convention, like perhaps a would be worth trying? Or pass the args as so the caller is required to have them already sign-extended, but has to explicitly specify the template? That isn't helpful for operator overloads. Or perhaps? IDK if anything like that would be acceptable in your real use case or totally defeat the purpose of what you're doing, so I haven't tried these wild guesses.union { int8_t; A<int8_t>; };intunion { int; A<int8_t>; }
0赞 resu 11/4/2023
"IDK if anything like that would be acceptable in your real use case..." I think it would not be acceptable