疯狂的编译器优化

作者: Zack Brown

内核开发总是很奇怪。Andrea Parri 最近发布了一个补丁,用于更改多线程操作期间的内存读取顺序,这样如果一个读取依赖于下一个读取,则第二个读取实际上不会在第一个读取之前发生。

这个问题在于这个 bug 实际上永远不会发生,而这个修复程序使得内核的行为对于开发人员来说不太直观。特别是 Peter Zijlstra 对这个补丁投了反对票,他说不可能构建一个物理系统来触发所讨论的 bug。

尽管 Andrea 同意这一点,但他仍然认为这个 bug 值得修复,即使只是为了它的理论价值。Andrea 认为,bug 就是 bug,应该修复。但是 Peter 反对让内核做额外的工作来处理永远不会发生的条件。他说,“我反对的是一个比任何可能的健全硬件都弱的模型。”

Will Deacon 在这一点上站在 Peter 一边,他说底层硬件以某种方式运行,而内核当前的行为反映了这种方式。他评论说,“大多数开发人员在编写代码时都考虑到了底层硬件,因此允许内存模型中出现与真实机器运行方式相反的行为可能会使事情更加混乱,而不是简化它们!”

尽管如此,仍有一些开发人员支持 Andrea 的补丁。特别是 Alan Stern 认为,在发现 bug 时修复它们是有道理的,但在代码中添加注释来解释默认行为和修复背后的理由也是有道理的,即使承认这个 bug 永远不会被触发。

但是,Andrea 对强迫反对的开发人员接受他的补丁不感兴趣。他很乐意退让,因为他已经表达了自己的观点。

实际上是 Paul McKenney 最初赞成 Andrea 的补丁,并考虑将其发送给 Linus Torvalds 以包含在内核中,他发现了围绕整个辩论的一些更深层次和更令人不安的问题。显然,这触及了内核代码实际编译成机器语言的方式的核心。Paul 说

上周我们在 C++ 标准委员会会议上就这类事情进行了一些辩论。

指针来源和并发算法,虽然这一次没有影响 RCU!我们实际上可能正在走向一个修复方案,该方案在保留相关优化的同时,仍然允许大多数(如果不是全部)现有的并发 C/C++ 代码继续正确工作。(目前的想法是,涉及内联汇编、C/C++ 原子操作或 volatile 的加载和存储会剥离其来源。在某些情况下,可能还需要一些其他机制来处理普通的 C 语言加载和存储。)

但是,如果您知道 Linux 内核中有任何代码需要比较指针,其中一个指针可能正在被释放,请告诉我。我以为 smp_call_function() 代码符合条件,但它避免了这个问题,因为只有发送 CPU 被允许推送到待处理的 smp_call_function() 调用的堆栈上。

使用 cmpxchg() 原子地推送和 xchg() 原子地弹出完整列表的相同并发链接堆栈模式 - 会 - 存在这个问题。传递给 cmpxchg() 的旧指针可能引用一个对象,该对象在加载旧指针的时间和执行 cmpxchg() 的时间之间被释放。避免这种情况的一种方法是在 RCU 读取端临界区中执行推送操作,并使用 kfree_rcu() 而不是 kfree()。当然,空闲循环中的代码或可能在离线 CPU 上运行的代码不能使用 RCU,而且某些数据结构对 kfree_rcu() 延迟不满意,所以...

换句话说,C 编译器应如何处理指针的问题在一定程度上取决于它们是否是指针。指针本身没有任何东西可以将其与其他数字区分开来,除非编译器知道它是一个指针,因此可以对其执行某些在其他上下文中没有意义的操作。正是数字的起源问题——即它们的来源——标准委员会试图解决的问题。这一切之所以有用和相关,是因为编译器只有在能够理解正在发生什么和将要发生什么的情况下,才能优化代码以使其更快、更高效。

Peter 在网上搜索,直到找到一篇 详细描述情况的论文

这让他感到震惊。他的结论是,“这完全是胡说八道。这太疯狂了。即使是提议的语义也很疯狂。”

Paul 没有反对这种观点,尽管显然更高效的代码比效率较低的代码更好,并且编译器应该尽其所能来实现它。

Paul 说这一切都不是新鲜事。事实上,这一切都可以追溯到 20 多年前,多线程操作的相对早期阶段。Paul 说,有多种方法,他希望能够向内核人员展示 GCC 人员在这件事上的一些想法,以获得反馈和建议。

Peter 仍然对这种情况感到有点害怕。特别是,他担心编译器是否能够生成可靠的代码。他评论说,“至少我们应该修复这个问题,并使用修复后的编译器编译内核,看看生成的代码中发生了什么变化(如果有的话),并分析这些变化(如果有的话),以确保我们没事(或不没事)。”

GNU C 编译器绝对充满了疯狂。如何将 C 代码转换为最佳机器代码的整个问题是一个永远无法完全回答的问题——事实上,随着新的 CPU 在市场上推出,这个问题不断变化。更不用说编译器还必须解决处理器特定的安全漏洞,例如近年来困扰 Intel 芯片的漏洞。

此外,GCC 不仅需要为 Linux 内核生成良好的代码,还需要为人们可能想到的任何编码项目生成良好的代码。因此,GCC 必须同时保持高度专业化和高度通用化。它的黑暗内部会是黑暗和内在的,这完全说得通。

注意:如果您在上面被提及,并希望在评论区上方发布回复,请将您的回复文本发送消息至 ljeditor@linuxjournal.com。

Zack Brown 是 Linux JournalLinux Magazine 的科技记者,并且是前“Kernel Traffic”每周新闻通讯和“Learn Plover”速记打字教程的作者。他于 1993 年在他的配备 8MB 内存的 386 计算机上首次安装了 Slackware Linux,并被开源社区永久震撼。他是 Crumble 纯策略棋盘游戏的发明者,您可以用几块纸板自己制作。他还喜欢写小说、尝试动画、改革拉班舞谱、设计和缝制自己的衣服、学习法语以及与朋友和家人共度时光。

加载 Disqus 评论