@老虎会游泳,我能看懂一点点汇编,你说的我能理解。(看过半本墨绿色的 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的块中,则不会有问题。
@老虎会游泳,写指针 和 读指针,都指向同一块缓冲区。只是写指针写入过后的字符对象,都绝不再读取。(因为写指针不会超出读指针,如上所述)
@老虎会游泳,只是【通过
char * restrict
修改后,不再使用其他别名】,也不行?因为解析了一个字符,并写入后,就不再需要读取这个及之前的字符对象了
@无名啊,根据以下文段,我觉得所有权的转移是在声明时发生的,而非使用时发生的。所以只要这个定义域内存在“restrict 指针 P”,就不能通过其他手段访问。
在每个声明了 restrict 指针 P 的块(典型例子是函数体的执行,其中 P 为参数)中,若某个可由 P (直接或间接)访问的对象会被任何手段修改,则该块中所有对该对象(读或写)的访问,都必须经由 P 出现,否则行为未定义。
只有一种情况可以存在其他别名:“restrict 指针 P”指向的内容不会进行任何修改。
若对象决不被修改,则它可以被别名引用,并被异于 restrict 限定的指针访问。
@老虎会游泳,“对象类型”是指非函数类型。。
另外,只是指向
char
类型的读写指针。另外,根据严格别名说的,
char *
可修改任何类型的数据。所以,我认为给
char *
添加restrict
,可防止【写指针写入数据后,编译器认为所有指针的数据缓存都失效不能用了,需要重新读取】。不知这个想法对不对