assembly 结构体和包含相同字节的基元变量,哪个更快?

hrysbysz  于 2023-01-13  发布在  其他
关注(0)|答案(3)|浏览(132)

下面是一段示例代码:

#include <stdint.h> 
#include <iostream>

typedef struct {
    uint16_t low;
    uint16_t high;
} __attribute__((packed)) A;

typedef uint32_t B;

int main() {
    //simply to make the answer unknowable at compile time
    uint16_t input;
    cin >> input;
    A a = {15,input};
    B b = 0x000f0000 + input;
    //a equals b
    int resultA = a.low-a.high;
    int resultB = b&0xffff - (b>>16)&0xffff;
    //use the variables so the optimiser doesn't get rid of everything
    return resultA+resultB;
}

resultA和resultB计算完全相同的东西--但哪个更快(假设您在编译时不知道答案)。
我试着使用编译器资源管理器查看输出,我得到了一些东西-但无论我尝试什么优化,它都比我聪明,优化了整个计算(起初,它优化了所有东西,因为它没有被使用)-我尝试使用cin使答案在运行时不可知,但是我甚至不知道它是如何得到答案的(我想它在编译时还是设法弄清楚了?)
下面是“编译器资源管理器”的输出,没有优化标志:

push    rbp
        mov     rbp, rsp
        sub     rsp, 32
        mov     dword ptr [rbp - 4], 0
        movabs  rdi, offset std::cin
        lea     rsi, [rbp - 6]
        call    std::basic_istream<char, std::char_traits<char> >::operator>>(unsigned short&)
        mov     word ptr [rbp - 16], 15
        mov     ax, word ptr [rbp - 6]
        mov     word ptr [rbp - 14], ax
        movzx   eax, word ptr [rbp - 6]
        add     eax, 983040
        mov     dword ptr [rbp - 20], eax
Begin calculating result A
        movzx   eax, word ptr [rbp - 16]
        movzx   ecx, word ptr [rbp - 14]
        sub     eax, ecx
        mov     dword ptr [rbp - 24], eax
End of calculation
Begin calculating result B
        mov     eax, dword ptr [rbp - 20]
        mov     edx, dword ptr [rbp - 20]
        shr     edx, 16
        mov     ecx, 65535
        sub     ecx, edx
        and     eax, ecx
        and     eax, 65535
        mov     dword ptr [rbp - 28], eax
End of calculation
        mov     eax, dword ptr [rbp - 24]
        add     eax, dword ptr [rbp - 28]
        add     rsp, 32
        pop     rbp
        ret

我还将发布-O 1输出,但我无法理解它(我对低级汇编内容还很陌生)。

main:                                   # @main
        push    rax
        lea     rsi, [rsp + 6]
        mov     edi, offset std::cin
        call    std::basic_istream<char, std::char_traits<char> >::operator>>(unsigned short&)
        movzx   ecx, word ptr [rsp + 6]
        mov     eax, ecx
        and     eax, -16
        sub     eax, ecx
        add     eax, 15
        pop     rcx
        ret

需要考虑的一些问题。虽然使用整数进行操作稍微困难一些,但与结构体相比,简单地将其作为整数访问更容易(我认为必须使用位移位进行转换?)。这有区别吗?
这最初出现在内存的上下文中,我看到有人将内存地址Map到一个结构体,该结构体带有一个低位和高位字段。我认为这不可能比简单地使用大小合适的整数和移位(如果需要低位或高位)更快。在这个特定的情况下-哪个更快?
[Why我是否将C添加到标记列表中?虽然我使用的示例代码是C++,但结构与变量的概念也非常适用于C]

bihw5rsg

bihw5rsg1#

除了一些ABI要求结构体的传递方式不同于整数之外,不会有什么区别。
现在,两个16位int和一个32位int之间有重要的语义差异。如果你对低16位int进行加法运算,它不会“溢出”到高16位,而如果你对一个32位int的低16位进行加法运算,它会溢出到高16位。(即使您自己“知道”它不可能发生在您的代码中)可能会更改编译器生成的汇编代码,并影响性能。
这两个问题中哪一个能更快地得出结果,在没有实际测试或对实际问题的完整描述之前,是不可能知道的,所以这是一个问题。
这意味着唯一真实的关注的是ABI,也就是说,如果没有整体程序优化,一个接受struct的函数和一个接受int的函数在相同的二进制布局下,对于数据的位置会有不同的假设。
然而,这只对按值的单个参数有影响。
90/10规则适用;90%的代码运行时间少于10%,这对关键路径没有影响。

gwbalxhn

gwbalxhn2#

当试图回答性能问题时,检查未优化的代码在很大程度上是无关紧要的。
事实上,即使检查-O 1优化的结果也不是特别有用,因为它没有给予编译器所能达到的最佳效果。您至少应该尝试-O2。
尽管如此,您提供的示例代码不适合进行检查,因为您应该确保ab的值是***编译器单独未知的***。就代码而言,编译器不知道input的值是什么,但它知道ab将具有相同的值。因此它优化代码的方式使得不可能从中得出任何有用的结论。
一般来说,编译器在处理机器字中的struct时往往表现得非常出色,一般来说,在您考虑的两种场景之间以及在您考虑的任何特殊情况之间,性能绝对没有差异。

i1icjdpr

i1icjdpr3#

compiler explorer上使用GCC时,带结构的版本在-O3模式下生成的指令较少。
代码:

#include <stdint.h> 

typedef struct {
    uint16_t low;
    uint16_t high;
} __attribute__((packed)) A;

typedef uint32_t B;

int f1(A a)
{
    return a.low - a.high;
}

int f2(B b)
{
    return b&0xffff - (b>>16)&0xffff;
}

组装:

_Z2f11A:
    movzwl  %di, %eax
    shrl    $16, %edi
    subl    %edi, %eax
    ret
_Z2f2j:
    movl    %edi, %edx
    movl    $65535, %eax
    shrl    $16, %edx
    subl    %edx, %eax
    andl    %edi, %eax
    ret

但这可能是因为这两个函数做的事情不一样,因为-precedence&高。当比较B的情况时,它做的事情与A相同,那么完全相同的汇编是produced
代码:

int f3(B b)
{
    return (b&0xffff) - ((b>>16)&0xffff);
}

组装:

_Z2f3j:
    movzwl  %di, %eax
    shrl    $16, %edi
    subl    %edi, %eax
    ret

注意,确定某个东西是否更快的唯一方法是在真实世界的用例中对其进行基准测试。

相关问题