- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
这可能是一个愚蠢的问题,但是每次我想“优化”大量参数(例如结构)传递给只读取它们的函数时,都会使我有些slightly跷。我在传递指针之间犹豫不决:
struct Foo
{
int x;
int y;
int z;
} Foo;
int sum(struct Foo *foo_struct)
{
return foo_struct->x + foo_struct->y + foo_struct->z;
}
struct Foo
{
int x;
int y;
int z;
} Foo;
int sum(const struct Foo foo_struct)
{
return foo_struct.x + foo_struct.y + foo_struct.z;
}
最佳答案
结构与数组很像,是数据的容器。每次使用容器时,都会将其数据布置在连续的内存块中。容器本身由其起始地址标识,并且每次使用容器进行操作时,您的程序都需要通过专用指令进行低级指针算术运算,以便应用偏移量从起始地址到达所需字段(或数组中的元素)。编译器唯一需要了解的用于结构的东西是(大致):
const
限定符对于理解将结构作为指针或按值传递之间的区别没有用。它只是告诉编译器,在函数内部,参数本身的值不会被修改。作为值或作为指针传递之间的性能差异通常不受
const
影响。
const
关键字仅对其他类型的优化有用,而对这一优化则无效。
void first(const struct mystruct x);
void second(struct mystruct *x);
#include <stdio.h>
struct mystruct {
unsigned a, b, c, d, e, f, g, h, i, j, k;
};
unsigned long __attribute__ ((noinline)) first(const struct mystruct x) {
unsigned long total = x.a;
total += x.b;
total += x.c;
total += x.d;
total += x.e;
total += x.f;
total += x.g;
total += x.h;
total += x.i;
total += x.j;
total += x.k;
return total;
}
unsigned long __attribute__ ((noinline)) second(struct mystruct *x) {
unsigned long total = x->a;
total += x->b;
total += x->c;
total += x->d;
total += x->e;
total += x->f;
total += x->g;
total += x->h;
total += x->i;
total += x->j;
total += x->k;
return total;
}
int main (void) {
struct mystruct x = {0};
scanf("%u", &x.a);
unsigned long v = first(x);
printf("%lu\n", v);
v = second(&x);
printf("%lu\n", v);
return 0;
}
__attribute__ ((noinline))
只是为了避免自动内联函数,出于测试目的,该函数非常简单,因此很可能会使用
-O3
内联。
objdump
编译和反汇编结果。
main()
调用first()
的方式: 86a: 48 89 e0 mov rax,rsp
86d: 48 8b 55 c0 mov rdx,QWORD PTR [rbp-0x40]
871: 48 89 10 mov QWORD PTR [rax],rdx
874: 48 8b 55 c8 mov rdx,QWORD PTR [rbp-0x38]
878: 48 89 50 08 mov QWORD PTR [rax+0x8],rdx
87c: 48 8b 55 d0 mov rdx,QWORD PTR [rbp-0x30]
880: 48 89 50 10 mov QWORD PTR [rax+0x10],rdx
884: 48 8b 55 d8 mov rdx,QWORD PTR [rbp-0x28]
888: 48 89 50 18 mov QWORD PTR [rax+0x18],rdx
88c: 48 8b 55 e0 mov rdx,QWORD PTR [rbp-0x20]
890: 48 89 50 20 mov QWORD PTR [rax+0x20],rdx
894: 8b 55 e8 mov edx,DWORD PTR [rbp-0x18]
897: 89 50 28 mov DWORD PTR [rax+0x28],edx
89a: e8 81 fe ff ff call 720 <first>
0000000000000720 <first>:
720: 55 push rbp
721: 48 89 e5 mov rbp,rsp
724: 8b 45 10 mov eax,DWORD PTR [rbp+0x10]
727: 89 c0 mov eax,eax
729: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
72d: 8b 45 14 mov eax,DWORD PTR [rbp+0x14]
730: 89 c0 mov eax,eax
732: 48 01 45 f8 add QWORD PTR [rbp-0x8],rax
736: 8b 45 18 mov eax,DWORD PTR [rbp+0x18]
739: 89 c0 mov eax,eax
... same stuff happening over and over ...
783: 48 01 45 f8 add QWORD PTR [rbp-0x8],rax
787: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
78b: 5d pop rbp
78c: c3 ret
DWORD PTR [rbp + offset]
)。 main()
调用second()
的方式: 8bf: 48 8d 45 c0 lea rax,[rbp-0x40]
8c3: 48 89 c7 mov rdi,rax
8c6: e8 c2 fe ff ff call 78d <second>
000000000000078d <second>:
78d: 55 push rbp
78e: 48 89 e5 mov rbp,rsp
791: 48 89 7d e8 mov QWORD PTR [rbp-0x18],rdi
795: 48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18]
799: 8b 00 mov eax,DWORD PTR [rax]
79b: 89 c0 mov eax,eax
79d: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
7a1: 48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18]
7a5: 8b 40 04 mov eax,DWORD PTR [rax+0x4]
7a8: 89 c0 mov eax,eax
... same stuff happening over and over ...
81f: 48 01 45 f8 add QWORD PTR [rbp-0x8],rax
823: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8]
827: 5d pop rbp
828: c3 ret
lea
+ mov
)。但是,由于现在该函数必须使用->
运算符与指针一起使用,因此我们看到,每次需要访问结构中的值时,都需要将内存解除引用两次,而不是一次(第一个将指针指向)。堆栈中的结构,然后获取结构中指定偏移量处的值)。 struct
时,我们并不真正在意它在内存中的位置(堆栈,堆,数据段等)。只要我们知道它从哪里开始,就可以归结为应用相同的简单指针算法来访问字段。无论结构位于何处或是否已动态分配,都始终需要这样做。-O3
优化上面的代码,我们现在将看到以下内容:main()
调用first()
的方式: 61a: 48 83 ec 30 sub rsp,0x30
61e: 48 8b 44 24 30 mov rax,QWORD PTR [rsp+0x30]
623: 48 89 04 24 mov QWORD PTR [rsp],rax
627: 48 8b 44 24 38 mov rax,QWORD PTR [rsp+0x38]
62c: 48 89 44 24 08 mov QWORD PTR [rsp+0x8],rax
631: 48 8b 44 24 40 mov rax,QWORD PTR [rsp+0x40]
636: 48 89 44 24 10 mov QWORD PTR [rsp+0x10],rax
63b: 48 8b 44 24 48 mov rax,QWORD PTR [rsp+0x48]
640: 48 89 44 24 18 mov QWORD PTR [rsp+0x18],rax
645: 48 8b 44 24 50 mov rax,QWORD PTR [rsp+0x50]
64a: 48 89 44 24 20 mov QWORD PTR [rsp+0x20],rax
64f: 8b 44 24 58 mov eax,DWORD PTR [rsp+0x58]
653: 89 44 24 28 mov DWORD PTR [rsp+0x28],eax
657: e8 74 01 00 00 call 7d0 <first>
00000000000007d0 <first>:
7d0: 8b 44 24 0c mov eax,DWORD PTR [rsp+0xc]
7d4: 8b 54 24 08 mov edx,DWORD PTR [rsp+0x8]
7d8: 48 01 c2 add rdx,rax
7db: 8b 44 24 10 mov eax,DWORD PTR [rsp+0x10]
7df: 48 01 d0 add rax,rdx
7e2: 8b 54 24 14 mov edx,DWORD PTR [rsp+0x14]
7e6: 48 01 d0 add rax,rdx
7e9: 8b 54 24 18 mov edx,DWORD PTR [rsp+0x18]
7ed: 48 01 c2 add rdx,rax
7f0: 8b 44 24 1c mov eax,DWORD PTR [rsp+0x1c]
7f4: 48 01 c2 add rdx,rax
7f7: 8b 44 24 20 mov eax,DWORD PTR [rsp+0x20]
7fb: 48 01 d0 add rax,rdx
7fe: 8b 54 24 24 mov edx,DWORD PTR [rsp+0x24]
802: 48 01 d0 add rax,rdx
805: 8b 54 24 28 mov edx,DWORD PTR [rsp+0x28]
809: 48 01 c2 add rdx,rax
80c: 8b 44 24 2c mov eax,DWORD PTR [rsp+0x2c]
810: 48 01 c2 add rdx,rax
813: 8b 44 24 30 mov eax,DWORD PTR [rsp+0x30]
817: 48 01 d0 add rax,rdx
81a: c3 ret
main()
调用second()
的方式: 671: 48 89 df mov rdi,rbx
674: e8 a7 01 00 00 call 820 <second>
0000000000000820 <second>:
820: 8b 47 04 mov eax,DWORD PTR [rdi+0x4]
823: 8b 17 mov edx,DWORD PTR [rdi]
825: 48 01 c2 add rdx,rax
828: 8b 47 08 mov eax,DWORD PTR [rdi+0x8]
82b: 48 01 d0 add rax,rdx
82e: 8b 57 0c mov edx,DWORD PTR [rdi+0xc]
831: 48 01 d0 add rax,rdx
834: 8b 57 10 mov edx,DWORD PTR [rdi+0x10]
837: 48 01 c2 add rdx,rax
83a: 8b 47 14 mov eax,DWORD PTR [rdi+0x14]
83d: 48 01 c2 add rdx,rax
840: 8b 47 18 mov eax,DWORD PTR [rdi+0x18]
843: 48 01 d0 add rax,rdx
846: 8b 57 1c mov edx,DWORD PTR [rdi+0x1c]
849: 48 01 d0 add rax,rdx
84c: 8b 57 20 mov edx,DWORD PTR [rdi+0x20]
84f: 48 01 c2 add rdx,rax
852: 8b 47 24 mov eax,DWORD PTR [rdi+0x24]
855: 48 01 c2 add rdx,rax
858: 8b 47 28 mov eax,DWORD PTR [rdi+0x28]
85b: 48 01 d0 add rax,rdx
85e: c3 ret
first()
情况下,我们看到所有字段都通过[rsp + offset]
访问,这意味着堆栈本身上的某个地址(rsp
)用于计算字段的位置,而在second()
情况下,我们看到[rdi + offset]
,这意味着地址而是使用作为参数传递(在rdi
中)。尽管偏移量仍然相同。first()
函数仍需要按值传递结构,因此即使启用了优化,整个结构仍需要复制到堆栈上,因此我们可以看到 first()
函数较重并添加了调用方中有很多代码。const
函数的first()
限定符可能会给编译器敲响钟声,并使其理解,实际上并不需要复制堆栈上的数据,并且调用者只需传递一个指针即可。但是,编译器应严格遵守ABI针对给定签名所规定的调用约定,而不是尽力优化代码。毕竟,在这种情况下,并不是真正的编译器错误,而是程序员的错误。struct
本身。
关于c - 结构和 union :从性能的角度来看哪个更好?通过值或指针传递参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59976295/
据我所知,根本不为元素呈现 HTML,或添加 display:none,似乎具有完全相同的行为:两者都使元素消失并且不与 HTML 交互。 我正在尝试禁用和隐藏一个复选框。所以HTML的总量很小;我无
我刚刚读了Android Architecture Tutorial: Developing an App with a Background Service (using IPC) .基本上是 让服
我有两个查询具有相同的结果,现在我想知道哪个查询更优化? 在选择中: select t1.*, sum(t2.value) as total_votes from table1 t1 left joi
有人告诉我,对于 I/O 绑定(bind)的应用程序,非阻塞 I/O 会更好。对于 CPU 密集型应用程序,阻塞 I/O 会好得多。我找不到这种说法的原因。试过谷歌,但很少有文章只是触及这个话题而没有
我有一个算法可以在数字列表中寻找好的对。一个好的配对被认为是索引 i 小于 j 且 arr[i] 1: # Finding the mid of the array
我有一个算法可以在数字列表中寻找好的对。一个好的配对被认为是索引 i 小于 j 且 arr[i] 1: # Finding the mid of the array
我从 API 收到一个 json,我需要解析并修改一个属性值。问题是,我收到的 json 数据的嵌套结构不一致,我无法控制它。 这将禁止我指定在特定深度(如 parsedJson.children[0
我有 451 个城市的坐标。现在我想计算每个城市之间的距离,然后根据该距离对一些结果进行排序。现在我有两个选择: 我可以运行一个循环来计算每个可能的城市组合的距离并将它们存储到一个表中,这将产生大约
对于返回相同结果的不同查询,我有两个查询计划我想知道是否有人可以告诉我哪个“更好”,以及为什么。 SELECT * FROM bids order by (select ranking from us
关闭。这个问题需要更多focused .它目前不接受答案。 想改进这个问题吗? 更新问题,使其只关注一个问题 editing this post . 关闭 7 年前。 Improve this qu
我有一个二维数组。我需要尽可能快地对其执行一些操作(函数每秒将被调用十几次,所以让它变得高效会很好)。 现在,假设我想获取元素 A[i][j],简单地使用 A[i][j] 在速度上有什么不同吗和 *(
在声明或使用字符串的代码中,我通常会看到开发人员这样声明它: string randomString = @"C:\Random\RandomFolder\ThisFile.xml"; 代替: str
这个问题在这里已经有了答案: 关闭 10 年前。 Possible Duplicate: Why don't CSS resets use '*' to cover all elements? 我正
如果我有一个包含许多重复项的 python 列表,并且我想遍历每个项目,而不是重复项,最好使用一个集合(如 set(mylist),或者找到另一种方法来创建没有重复的列表?我想只是循环遍历列表并检查重
在阅读常量接口(interface)反模式时,我发现没有实例的最终常量类比常量接口(interface)更好。 请解释一下怎么做? public interface ConstIfc { publ
我正在查看我继承的一些旧代码,我真的不喜欢某些地方的风格。我真的不喜欢它的外观的一件事是: bool func() { bool ret = true; ret &= test1();
关闭。这个问题是opinion-based 。目前不接受答案。 想要改进这个问题吗?更新问题,以便 editing this post 可以用事实和引文来回答它。 . 已关闭 4 年前。 Improv
我经常发现自己试图使用 boost/QT 信号解耦对象。实现这一点的简单方法是针对我要通信的每个具体类型,创建一个新的信号和插槽签名并连接所有相关对象。这导致了访问者模式,理想情况下我想发出一个访问者
我正在 https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html 上阅读有关 lambda 的内容 在方法
public List getInts() { List xs = new ArrayList(); xs.add(1); // return Collections.unmo
我是一名优秀的程序员,十分优秀!