跳过正文
  1. Articles/

手动优化 C++ 代码来加快编译速度?!

·1172 字·3 分钟· ·
ykiko
作者
ykiko

事情的起因是我最近在编写的一个库 magic cpp ,正在编写其中enum的相关部分。打算参考一下magic enum的相关实现,在翻 issue的时候翻到这么一个神奇的PR

我们都知道C++constexpr/consteval函数可以在编译期执行,目前编译器对此的实现大概是内部实现了一个小型的解释器,用来直接执行代码。然而这个解释器具体是什么表现我们无从得知,但是这个 pr 的作者仅仅改了几行代码就让编译速度提升了不少。

原代码

char const* str = name.data();
  for (std::size_t i = name.size(); i > 0; --i) {
    if (!((name[i - 1] >= '0' && name[i - 1] <= '9') ||
          (name[i - 1] >= 'a' && name[i - 1] <= 'z') ||
          (name[i - 1] >= 'A' && name[i - 1] <= 'Z')


** 优化代码**

char const* str = name.data();
  for (std::size_t i = name.size(); i > 0; --i) {
    char c = str[i - 1];
    if (!((c >= '0' && c <= '9') ||
          (c >= 'a' && c <= 'z') ||
          (c >= 'A' && c <= 'Z')

这两份代码唯一的区别在于第二份代码对数组的元素str[i - 1]做了一次缓存。如果编译器在编译期解释执行这个函数的时候不执行任何优化,那么第一种写法每次判断都得额外寻一次址,相比之下第二种做了缓存的效果明显会快很多。这也和作者的测试结果相符合,优化后的写法编译更快。

作者还提到了,STL实现的许多容器有越界检测,但是在编译期这实际上是不必要的,编译期越界(读取未初始化的内存)的话会直接编译错误,例如下面这段代码

constexpr char f()
{
    char a[3];
    return a[0];
}

constexpr auto c = f(); // compile error

直接编译错误,所以这些检测其实并没有任何实际的作用,反倒是无用的检查拖慢了constexpr函数的编译期执行速度,更好的办法是自己实现一份不带检查的编译期使用的数据结构。另外一个有关编译速度优化相关的PR是 这个 compile-time optimization · Issue #219


实际上,如果对于运行期代码,编译器完全会把这两种代码优化成一种形式,我们是不用考虑这个问题的。但是这个PR的确表现出来一个问题,那就是C++编译器对于constexpr expression求值的效率问题,在以后C++引入静态反射之后,constexpr函数的使用会更加泛滥,如果编译器不能通过有效的手段加快它的执行速度,恐怕会更进一步加剧C++编译速度慢的问题。

后来我在clang的社区提出了这个 问题 。他们回复表示,目前(即clang18以及之前),clangconstexpr expression的求值效率的确是有问题的,现在的tree evaluator效率低下,并且在将来会有有一个新的 Interpreter 来解决这个问题,现在在clang18中可以用-fexperimental-new-constant-interpreter来开启这个实验性的功能。

这里有其主要贡献者 Timm Baeder 的两篇相关介绍文章:

如果这个新的解释器被正式加入了,有关的情况应该会得到比较大的改善。但是在那之前如果你的项目中大量使用了常量求值相关的代码,可能需要你手动进行优化编译期求值的代码来换取更快的编译速度


还剩下gccmsvc的相关实现未调查,未完待续......