最近在看tokio源代码,在tokio/src/util/linked_list.rs之中看到了这样一段注释:
/// Previous / next pointers.
pub(crate) struct Pointers<T> {
inner: UnsafeCell<PointersInner<T>>,
}
/// We do not want the compiler to put the `noalias` attribute on mutable
/// references to this type, so the type has been made `!Unpin` with a
/// `PhantomPinned` field.
///
/// Additionally, we never access the `prev` or `next` fields directly, as any
/// such access would implicitly involve the creation of a reference to the
/// field, which we want to avoid since the fields are not `!Unpin`, and would
/// hence be given the `noalias` attribute if we were to do such an access. As
/// an alternative to accessing the fields directly, the `Pointers` type
/// provides getters and setters for the two fields, and those are implemented
/// using `ptr`-specific methods which avoids the creation of intermediate
/// references.
///
/// See this link for more information:
/// <https://github.com/rust-lang/rust/pull/82834>
struct PointersInner<T> {
/// The previous node in the list. null if there is no previous node.
prev: Option<NonNull<T>>,
/// The next node in the list. null if there is no previous node.
next: Option<NonNull<T>>,
/// This type is !Unpin due to the heuristic from:
/// <https://github.com/rust-lang/rust/pull/82834>
_pin: PhantomPinned,
}
这是一个双向链表的指针部分,但是这里的注释的含义我不太理解:好像加入PhantomPinned不是为了避免借出可变引用,改变链表元素在内存之中的位置一样,反而就是为了阻止Rust编译器生成noalias。
然后我就去网上查了一下这个noalias到底是什么,发现在21年在raddit上有很多关于这个的讨论(正好上面这段注释也是在2021年写的)。
大概意思就是说这个noalias可以让LLVM进行更加激进的优化,但是好像一直有bug,经常是修好一阵子就因为另外一些crate出现了编译问题就又关闭了。
所以,我的问题是:
- tokio在这里避免编译器生成noalias是因为21年这个特型还不稳定,防止因为Rustc的版本编译期爆炸。还是说从Rust语意上来说,使用PhantomPinned就是为了防止借出可变引用(后面使用Pin钉住),还是两者兼而有之?
- noalias 的作用具体是什么?我只找到了零散的描述,有没有什么博客我可以学习一下?
- 现在这个noalias Rust默认启用了吗?从讨论里面的描述上来看,应该是有一些性能提升的,如果还不能启用的话,原因是什么?
谢谢大家
1
共 4 条评论, 1 页
评论区
写评论感谢!🙏
--
👇
az: 我也关注过这个话题,查过一些相关的内容。但LLVM我不懂,权当抛转引玉吧。
https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a42462
↑这是Alice遇到帖子上的问题时写的一篇文章,基本把整个问题说清楚了。我按个人理解简单归纳下:通过侵入式链表保存waker可以避免大量的堆分配,但是它在Tokio的场景下会违反Rust的别名模型,而且Rust也没有暴露任何放松别名模型要求的原语,这种实现是unsound的。(其实unsafe链表不违反别名模型,Tokio这里的情况比较特殊)
因为Generator也有类似Tokio的问题,而且它们都和Pin相关,于是编译器给!Unpin的类型开了洞,关闭了别名模型对它们的要求(也就不对它们附加
noalias
声明了)。后来Ralf又提出了UnsafeAliased
(现在改成UnsafePinned
了)作为关闭别名要求的原语,它稳定后这块的实现会干净些。至于现状我也不清楚了。
我也关注过这个话题,查过一些相关的内容。但LLVM我不懂,权当抛转引玉吧。
https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a42462
↑这是Alice遇到帖子上的问题时写的一篇文章,基本把整个问题说清楚了。我按个人理解简单归纳下:通过侵入式链表保存waker可以避免大量的堆分配,但是它在Tokio的场景下会违反Rust的别名模型,而且Rust也没有暴露任何放松别名模型要求的原语,这种实现是unsound的。(其实unsafe链表不违反别名模型,Tokio这里的情况比较特殊)
因为Generator也有类似Tokio的问题,而且它们都和Pin相关,于是编译器给!Unpin的类型开了洞,关闭了别名模型对它们的要求(也就不对它们附加
noalias
声明了)。后来Ralf又提出了UnsafeAliased
(现在改成UnsafePinned
了)作为关闭别名要求的原语,它稳定后这块的实现会干净些。至于现状我也不清楚了。
noalias是一个函数参数属性,专门为ptr类型设计的。 define void foo(ptr noalias %a)... 说明指针a不和其他指针指向同一内存区域。 llvm有个pass可以推断noalias信息,名字应该是funcattr什么的。
谷歌搜了一下 llvm noalias ,在这个 LLVM 文档页面上有相关的定义,这个 noalias(no alias,“没有别名”),我猜是来自“在编程语言中,我们用名字来称呼值”这种说法,所以说一个值“没有别名”意思可能就是说它只在一处被用到。
他们要在这里去掉 noalias 标记很可能与“双向链表中存放每一个节点的内存区域都会在两个地方被引用”有关。
至于为啥要加 PhantomPinned,应该是看到了这里那个“如果是 !Unpin 那我们就不加 noalias 了”。