说一个反直觉的事情,一个巨无霸 CPP 源文件的编译速度,要快于实现相同功能的一堆小文件。

2023-02-14 14:11:37 +08:00
 tool2d
理由是 C++是按照 CPP 文件单独编译的。一堆小 CPP 文件,头文件会很重复包含多次,编译器每次都会解析一遍,就会很慢。

但是把所有函数硬塞进一个巨无霸 CPP 文件,显然也是很不合理的。

那么问题来了,有没有办法即兼顾编译速度,又能合理按照小功能划分源文件?
4854 次点击
所在节点    C++
33 条回复
learningman
2023-02-14 14:18:29 +08:00
"头文件会很重复包含多次,编译器每次都会解析一遍,就会很慢"
#pragma once

#ifundef AAA
#def AAA
#endif
msg7086
2023-02-14 14:19:43 +08:00
换一个不会每次都会解析一遍的编译器?
AllenTsui
2023-02-14 14:20:41 +08:00
可读性 >>> 运行性能 >>> 编译性能
yanqiyu
2023-02-14 14:21:38 +08:00
https://en.cppreference.com/w/cpp/language/modules


@learningman
OP 指的应该是不同翻译单元都用到的头文件解析耗时,比如 STL 里面模板库的展开,每个翻译单元都要一次

没啥好方法,要么做一些结构性的更改,要么就用 C++20 的现在编译工具链支持不是很好的 modulws
codehz
2023-02-14 14:22:20 +08:00
不可以并行化吗
然后如果源文件没变的话,不可以直接用缓存的结果吗
然后,还有 pch 和 c++模块系统呢
ysc3839
2023-02-14 14:22:49 +08:00
实际开发时基本都会开启预编译头,拆分成多个 cpp 是更快的,而且拆分的话改了其中一个 cpp 后只需要重新编译这个 cpp 即可,不需要全部重新编译,也会快很多。
cnbatch
2023-02-14 14:23:14 +08:00
长久以来的做法是,使用 Precompiled Header (预编译头)功能

如果用上了 C++20 ,可以尝试下 Modules
tool2d
2023-02-14 14:23:22 +08:00
@learningman 没说清楚,比如有 10 个小的 cpp ,每个模块都需要重复解析一次头文件。

也就是一共要解析 10 次。
acctv2
2023-02-14 14:24:21 +08:00
没有具体实践过,理论上一大堆小文件链接时间会更长,所以有可能。

头文件一遍遍解析? ifndef 不行吗
tool2d
2023-02-14 14:29:00 +08:00
@ysc3839 感觉预编译头也不是万能的,我看很多开源 linux 项目很少用到预编译头。

因为这意味着你写 C++代码,不能直接去 include 指定的 fileA.h 和 fileB.h 。必须 include stdafx.h ,其中 stdafx.h 又包含了 fileA.h 和 fileB.h 。

最后用起来也没那么直观。只能说是 trade off 。
DIMOJANG
2023-02-14 14:33:05 +08:00
是的,方法可以平衡编译速度和源文件的合理划分。

一种方法是使用预编译的头文件,它允许你预编译常用的头文件,减少每个包含它们的 CPP 文件的解析时间。
另一种方法是使用模块,这是 C++中一个较新的功能,允许你编译和链接较小的代码单元。
此外,你可以将你的源文件组织成逻辑模块或命名空间,以更好地反映你的代码结构,减少不必要的头文件的包含。
tool2d
2023-02-14 14:55:32 +08:00
说说我自己的解决办法,就是用.inl (INLINE)文件写代码,IDE 不会编译,只需要在 CPP 里包含.inl 就可以了。

一个 CPP 是单独的模块,模块里的子功能,全部都在分散在若干个.inl 文件里。

但是感觉就我一个人这样规划代码结构,有那么一点奇葩。
codehz
2023-02-14 14:56:47 +08:00
@tool2d stdafx 只是一个约定,实践中只要构建系统支持,你可以任意指定某个头文件生成 pch ,并不是一个项目只能有一个 pch
tool2d
2023-02-14 15:02:33 +08:00
@codehz 很好奇为什么编译器不能自动无感知来进行 pch 生成。

让人工指定,还需要去分析每个头文件的复杂程度,对很复杂的头文件才建立 pch ,多累人啊。
lixile
2023-02-14 15:11:19 +08:00
? 实践中 常用场景不是增量编译次数远大于 全量编译场景吗
主流的构建系统(指代我熟悉的 linux 体系下的 makefile cmake 等)应该都支持增量编译才对 那明显是多 cpp 更合适吧速度更快 否则单个巨无霸 往往只能使用一个 cpu 核心进行编译 完全没办法并行编译。
ligiggy
2023-02-14 15:18:27 +08:00
@tool2d #12 ,你说的就是用纯使用 内联函数,很显然这个方法不通用。把一个又一个的大函数,分解成一个又一个的 内联函数,理想很美好,现实很骨感。

其次,你的 inl 文件或者说头文件,有这么多的内联实现,不就暴露了大部分接口。

然后在针对每个类的成员函数,你把一个函数,在多个文件里面实现,是不是也要每个文件定义成类的一部分,编译的时候也很复杂啊,或者你在函数里面将类型当成参数,写着写着就丢失了 C++的灵魂。
tool2d
2023-02-14 15:28:34 +08:00
@ligiggy 我用的.inl ,没用到 INLINE 关键字,就只是当成普通的 CPP 源文件使用。

和普通 CPP 的唯一区别,是.cpp 后缀 IDE 会自动编译成 obj ,而.inl 后缀 IDE 会选择忽视。

可能和官方有冲突,我也想不出别的扩展名了。
Nazz
2023-02-14 15:33:30 +08:00
很符合直觉, 只是这样不方便维护.
Shuenhoy
2023-02-14 15:35:40 +08:00
https://cmake.org/cmake/help/latest/prop_tgt/UNITY_BUILD.html

各种构建工具有一种被称为 unity build 的功能,可以自动帮你把一些小文件合并。

不过需要注意,这种情况下比一堆小文件快只是对从 0 编译而言的,如果你需要一边改一边编译,可能还是分多个编译单元更快。

当然,整这些其实都没啥用,正解还是 C++20 的 module (
yolee599
2023-02-14 15:40:10 +08:00
并不反直觉。多个文件需要经历预编译,把文件都整合到一起,编译出来的中间文件还要链接到一起组成可执行文件,当然比单独一个文件慢了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/916003

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX