int a, b, c, d, e, f, g; int *h = &b;
而在这种情况下,可能是a、c、d、e、h在寄存器,b在内存,因为
&b
需要一个内存地址。指针h通常在寄存器,因为这样更快。至于a、c、d、e、f、g哪个在寄存器哪个在内存,取决于它们的使用情况。
@老虎会游泳,
this
符合【完全优化至寄存器的条件】吗?我记得是只读的啊。。那篇文章里,也没有要&this
。。
@无名啊,举个例子:
int a, b, c, d, e, f, g;
如果有5个空闲寄存器,那么a到e就只在寄存器,完全不在内存,f和g在内存,根据需要读取到寄存器。
既然可以完全优化至寄存器,自然不需要考虑缓存失效问题。
而指向缓冲区的局部指针变量恰好符合完全优化至寄存器的条件。
@无名啊,
this
是一个内存地址,既然是内存地址,所以当然在内存。编译器的问题只是在没有必要的情况下反复读取了它。而我说的寄存器变量,是完全不在内存,在且仅在寄存器的变量,这样的变量根本没有内存地址。
@老虎会游泳,我能看懂一点点汇编,你说的我能理解。(看过半本墨绿色的 x86 汇编书,但没咋写过,又都忘得差不多了。。
)
@无名啊,寄存器没有内存地址,所以任何有地址的内容必然在内存,但是在内存不意味着慢,因为它还可以同时存在于L3/L2/L1缓存中,而且也不必马上写回内存。
任何不需要有内存地址的内容,都可以放进寄存器,只要寄存器还没满。
内容在不在寄存器是静态分配的,编译的时候就决定了,查看汇编代码就能看出来。
如果寄存器还没满,并且内容不需要有内存地址,编译器没有理由不把它放进寄存器,除非专门指示它不进行此类优化(
-O0
或volatile
)。
@无名啊,以下是可能的分配:
char buf[16]; // 数组只能在内存,因为数组访问操作涉及取地址。内存中的数据会自动逐级缓存在L3/L2/L1 Cache,该操作由CPU自动完成。 char *read_next = buf; // 指针本身可以在寄存器,指向的内容当然不在寄存器,只可能在内存和缓存中 char *write_next = buf + 16; // 指针本身可以在寄存器,指向的内容当然不在寄存器,只可能在内存和缓存中
如果一个内容可以被指针指向,意味着它一定有一个内存地址,也就是说它一定在内存中,当然它也可以同时在L3/L2/L1 Cache中,但不会在寄存器中。在寄存器中的内容没有内存地址。
但是指针本身(也就是内存地址这个数值本身)可以在寄存器中。
@老虎会游泳,意思是,拿某个寄存器来存
read_next
,而不存入栈上了好像写入数组末尾之后,是未定义行为。这么说,编译器认为
read_next
未被修改也无不可。。哎,还是等写完之后,再加
restrict
试试吧。反正也不是啥速度极为严苛的场景,只是想顺便学学restrict
而已
@无名啊,
缓存至寄存器
没有这种操作。
一个变量要么在寄存器,要么在内存,不会同时位于两者。
位于内存的变量只会被缓存到L3/L2/L1 Cache中,不会位于寄存器。而这个缓存操作是CPU自动进行的,不需要程序控制。
所以,变量在不在寄存器,看汇编代码就能知道,不需要运行时确定。
指针本身是否被优化到寄存器与restrict无关。如果不对指针进行取地址操作,它就可以被优化到寄存器。
char buf[16]; char *read_next = buf, *write_next = buf + 16; *write_next = '\0'; // 此时 read_next 岂不就被修改了吗?(假设 read_next 就在 buf 后面)
编译器咋会放心把
read_next
缓存至寄存器的。。
@无名啊,至于43楼的设计到底能不能认为所有权发生了转移,我认为是值得争议的问题。
因为从瞬时来看,每个单独的时刻,所有权都从读指针转移到了写指针。
但从全局来看,所有权在读指针和写指针之间共享。
所以到底算不算转移,可能是“实现定义的”
@老虎会游泳,老虎用
restrict
的情景多吗?我是不是要写完那个小插件,再试试加
restrict
,才理解得更快呢。。
@无名啊,
restrict
针对的是指针指向的内容,不是指针本身。指针本身是否被优化到寄存器与restrict
无关。如果不对指针进行取地址操作,它就可以被优化到寄存器。如果不确定,你可以用gcc -O2 -S
查看汇编代码。
@老虎会游泳,感觉 43 楼的设计,没法加
restrict
?那岂不读指针和结束指针都没法缓存至寄存器,每次读取前都要读一次内存?(因为编译器认为,写指针可能会修改
char *read_next, *end
?)
@无名啊,我知道你想采用的方法。我的观点是如果不确定就不要使用。
注解
restrict 限定符(像寄存器存储类)是有意使用以促进优化的。而从所有组成一致程序的预处理翻译单元中,删除所有此限定符的实例不会影响其含义(即可观的行为)。
编译器可以忽略任何一个或全部使用 restrict 的别名使用暗示。
欲避免未定义行为,程序员应该确保 restrict 限定指针所做的别名引用断言不会违规。此外实现类型转换解引用的另一个方案:
许多编译器提供作为 restrict 对立面的语言扩展:指示即使指针类型不同,也可以别名使用的属性: may_alias (gcc)
@老虎会游泳,画了个草图,容易理解:
@无名啊,有一个实验方法,就是用 gcc -O2 -S 编译一段函数,看看加
restrict
和不加有什么区别。
@无名啊,至于性能问题,据我所知编译器很少生成主动刷新CPU缓存的代码,大部分工作都是交由CPU自动完成的,除非涉及同步原语(信号量、互斥锁等)。
@无名啊,如果问题是阅读理解,我会回答“不行”。
若某个可由 P (直接或间接)访问的对象会被任何手段修改,则该块中所有对该对象(读或写)的访问,都必须经由 P 出现,否则行为未定义。
之前读和之后读都是读。文段中没有体现出时间前后的区别,只强调了定义域的区别。如果读访问发生在不含指针P的块中,则不会有问题。