为什么编译器不内联所有内容,分析它然后生成自己的优化函数?

use*_*055 6 compiler-construction gcc inlining compiler-optimization native-aot

问题总结

我正在尝试使用事务创建内存数据库。我遇到了编译器级别的瓶颈,特别是内联限制。我对编译器了解不多,我想知道为什么有些事情是这样完成的。

目标

我的内存数据库的绝对优先级是时间性能。一定要超级快,这就是目标。我想把一切都放在共享内存中。每次数据库访问都是直接访问内存。为了解决竞争条件和事务,自旋锁是在内存级别实现的。

预期结果

当我有这个伪代码时:

var garage = DB.GetGarage(123);
var car = DB.CreateCar();
car.Color = 2;
garage.ConcurrentBag.Add(car);
Run Code Online (Sandbox Code Playgroud)

为了实现所有这些(自动生成的)方法,GetGarage我启用了内联。为什么?我发现它更快。CreateCarConcurrentBad.Add

但它更快的原因可能不是调用函数的开销。看起来,当内联时,编译器可以比不内联时找出更好的机器代码。换句话说,这些方法具有编译器可能可以简化的共同点,但前提是它们是内联的,否则无法简化它们。

现在我要谈谈这个问题的实质了。所以我将所有内容都内联,并且我希望编译器将内联所有内容。

实际结果

嗯,事情不是这样的。编译器可能不是内联的,让我们在这里具体说明:

.NET C# - AggressiveInlined - 该属性可能会导致遇到实现限制,从而导致生成的代码速度变慢。

C99、C++ -内联- C++ 和 C99(但不是其前身 K&R C 和 C89)支持内联函数,尽管语义不同。在这两种情况下,inline 不会强制内联;编译器可以自由选择根本不内联函数,或者仅在某些情况下内联函数。

我已经尝试过什么?

我尝试过 C# 并达到了极限。在该限制之后,所有内容都不再内联。我认为 GCC 也会发生类似的情况:

max-inline-insns-single:有几个参数控制 gcc 中使用的树内联器。该数字设置树内联器将考虑进行内联的单个函数中的最大指令数(以 GCC 的内部表示形式计数)。这仅影响内联声明的函数和类声明 (C++) 中实现的方法。默认值为500

至少对于 GCC 我可以选择限制,对于 .NET 我不能。

为什么编译器不内联所有内容,分析它然后生成自己的优化函数?

我真的很想得到关于这个问题的反馈。我知道并非所有内容都可以内联(例如递归调用)。让我们跳过根本不可能的情况。

我也知道内联并不能保证具有更好的性能。但我认为链接中提到的所有这些问题都可以通过编译器生成的函数来消除。

我还知道,在内联时,会考虑几个因素:

我们将沿着三个轴衡量内联的质量:生成代码所花费的时间(又名吞吐量 - TP,缩写为 TP)、执行代码所花费的时间(又名代码质量 - CQ)以及生成代码的大小(CS)。

我认为编译器不这样做的原因可能是生成代码所花费的时间。但如果我不在乎怎么办……好吧,我不想等一年,但如果我得到的代码速度提高了 20%,我可以等一天。

你怎么看待这件事?是否有任何编程语言的编译器可以做到这一点(通过一些标志或类似的东西)?

编辑:根据@RaymondChen(参见评论),它类似于“内联所有内容,然后再采取另一个步骤来取消内联内容”:

反内联(也称为“公共子表达式消除”)是编译器已经做的事情。

但根据我的研究,CSE 并不涉及生成新函数,而是使用已保存的数据:

公共子表达式消除是一种转换,它删除公共子表达式的重新计算并使用已保存的数据替换它们。

除了与 C++ 类构造函数、析构函数和运算符相关的一些函数之外,我找不到任何有关编译器生成的函数的信息。所以,我仍在寻找答案,并希望有人可以提供一些来源。

@RaymondChen 还提到:

所分析的代码越大,寻找去内联机会就越困难。要检查的内容数量(天真地)随着代码大小的四次方而增长。一个大型程序所需的时间可能会超过人的一生,而编译器本身早在这之前就会耗尽内存。

这可能是一个很好的回答我的问题,但这也是我很难接受的。如果编译器找到大型程序的去内联机会将花费比人类一生更长的时间,那么我作为一个人怎么可能在合理的时间内自己完成它,只需查看(高级,而不是机器)代码和重构。

我知道有些任务(模式识别、语言翻译等)对于计算机来说确实很难完成。但今天,我们有了神经网络。是否有可能使用神经网络来寻找去内联机会之类的事情?

@PeterCordes 提到:

(在现实世界的编译器中,不会尝试将直线代码重构回函数或循环中)

我又问为什么?我确信编译器可以找出比我更好的函数。为什么编译器只获取我的函数并最多优化或内联它们,但从不(除了 C++ 构造函数、析构函数等)生成新函数?

Bar*_*nax -1

虽然内联是优化程序的强大工具,因为它在调用站点上解锁了额外的优化,但它仍然是一种权衡,因为它增加了程序大小。

这样增加的程序大小可能会对性能产生负面影响,因为它会减少局部性。局部性也很重要,因为如果内存中的数据彼此靠近,计算机内存就会更快。

有些编译器甚至具有可以设置的标志来优化代码大小或性能。

TLDR:内联并不是万能的解决方案。