下面是一段示例代码:
#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]
3条答案
按热度按时间bihw5rsg1#
除了一些ABI要求结构体的传递方式不同于整数之外,不会有什么区别。
现在,两个16位int和一个32位int之间有重要的语义差异。如果你对低16位int进行加法运算,它不会“溢出”到高16位,而如果你对一个32位int的低16位进行加法运算,它会溢出到高16位。(即使您自己“知道”它不可能发生在您的代码中)可能会更改编译器生成的汇编代码,并影响性能。
这两个问题中哪一个能更快地得出结果,在没有实际测试或对实际问题的完整描述之前,是不可能知道的,所以这是一个问题。
这意味着唯一真实的关注的是ABI,也就是说,如果没有整体程序优化,一个接受
struct
的函数和一个接受int
的函数在相同的二进制布局下,对于数据的位置会有不同的假设。然而,这只对按值的单个参数有影响。
90/10规则适用;90%的代码运行时间少于10%,这对关键路径没有影响。
gwbalxhn2#
当试图回答性能问题时,检查未优化的代码在很大程度上是无关紧要的。
事实上,即使检查-O 1优化的结果也不是特别有用,因为它没有给予编译器所能达到的最佳效果。您至少应该尝试-O2。
尽管如此,您提供的示例代码不适合进行检查,因为您应该确保
a
和b
的值是***编译器单独未知的***。就代码而言,编译器不知道input
的值是什么,但它知道a
和b
将具有相同的值。因此它优化代码的方式使得不可能从中得出任何有用的结论。一般来说,编译器在处理机器字中的
struct
时往往表现得非常出色,一般来说,在您考虑的两种场景之间以及在您考虑的任何特殊情况之间,性能绝对没有差异。i1icjdpr3#
在compiler explorer上使用
GCC
时,带结构的版本在-O3
模式下生成的指令较少。代码:
组装:
但这可能是因为这两个函数做的事情不一样,因为
-
的precedence比&
高。当比较B
的情况时,它做的事情与A
相同,那么完全相同的汇编是produced。代码:
组装:
注意,确定某个东西是否更快的唯一方法是在真实世界的用例中对其进行基准测试。