我试图用 SO 找到这个问题的答案。有很多问题列出了在 c++ 中构建仅头文件库的各种优缺点,但我还没有找到一个以可量化的方式做到这一点的问题。
那么,从可量化的角度来看,使用传统上分离的 C++ 头文件和实现文件与仅使用头文件有什么不同?
为简单起见,我假设不使用模板(因为它们仅需要标头)。
为了详细说明,我列出了我从文章中看到的优点和缺点。显然,有些是不容易量化的(例如易用性),因此对于量化比较来说是无用的。我将用(可量化)标记那些我期望可量化的指标。
仅标题的优点
- 它更容易包含,因为您不需要在构建系统中指定链接器选项。
- 您始终使用与其余代码相同的编译器(选项)来编译所有库代码,因为库的函数内联在您的代码中。
- 可能会快很多。 (可量化)
- 可以为编译器/链接器提供更好的优化机会(解释/可量化,如果可能)
- 如果您无论如何使用模板,则这是必需的。
仅标题的缺点
- 它使代码变得臃肿。 (可量化)(这如何影响执行时间和内存占用)
- 编译时间更长。 (可量化)
- 失去接口和实现的分离。
- 有时会导致难以解决的循环依赖。
- 防止共享库/DLL 的二进制兼容性。
- 这可能会激怒那些更喜欢使用 C++ 的传统方式的同事。
我们非常感谢您可以使用更大的开源项目(比较类似大小的代码库)中的任何示例。或者,如果您知道一个项目可以在仅标头版本和分离版本之间切换(使用包含这两个版本的第三个文件),那将是理想的选择。轶事数字也很有用,因为它们给了我一个大概的范围,我可以从中获得一些见解。
优点和缺点的来源:
-
https://stackoverflow.com/a/6200793/278976 https://stackoverflow.com/a/6200793/278976
- https://stackoverflow.com/a/1783905/278976 https://stackoverflow.com/a/1783905/278976
提前致谢...
UPDATE:
对于稍后可能阅读本文并且有兴趣获取有关链接和编译的背景信息的任何人,我发现这些资源很有用:
- 第7章http://www.amazon.com/Computer-Systems-Programmers-Perspective-Edition/dp/0136108040 https://rads.stackoverflow.com/amzn/click/com/0136108040
- http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html http://www.yolinux.com/TUTORIALS/LibraryArchives-StaticAndDynamic.html
- http://www.cyberciti.biz/tips/linux-shared-library-management.html http://www.cyberciti.biz/tips/linux-shared-library-management.html
更新:(回应下面的评论)
仅仅因为答案可能有所不同,并不意味着测量毫无用处。你必须从某个点开始测量。测量的数据越多,图片就越清晰。我问这个问题并不是要了解整个故事,而是要了解整个故事的一瞥。当然,如果任何人想要不道德地宣扬自己的偏见,他们都可以使用数字来歪曲论点。然而,如果有人对两个选项之间的差异感到好奇并发布这些结果,我认为这些信息是有用的。
难道就没有人对这个话题感到好奇,足以衡量一下吗?
我喜欢枪战项目。我们可以从删除大部分变量开始。仅在一种版本的 Linux 上使用一种版本的 gcc。仅对所有基准测试使用相同的硬件。不要使用多线程进行编译。
然后,我们可以测量:
- 可执行文件大小
- runtime
- 内存占用
- 编译时间(对于整个项目和更改一个文件)
- 链接时间
总结(值得注意的点):
- 对两个软件包进行了基准测试(一个有 78 个编译单元,一个有 301 个编译单元)
- 传统编译(多单元编译)使应用程序速度提高了 7%(在 78 个单元包中); 301 单元包中的应用程序运行时没有变化。
- 传统编译和仅标头基准测试在运行时(在两个包中)使用相同数量的内存。
- 仅标头编译(单单元编译)导致 301 单元包中的可执行文件大小减小了 10%(78 单元包中仅减小了 1%)。
- 传统编译使用了大约三分之一的内存来构建这两个包。
- 传统编译的编译时间是传统编译的三倍(第一次编译时),而重新编译只花费了 4% 的时间(因为 header-only 必须重新编译所有源代码)。
- 传统编译在第一次编译和后续编译上的链接时间都较长。
Box2D 基准测试,数据:
box2d_data_gcc.csv https://gist.github.com/4155964#file_box2d_data_gcc.csv
Botan 基准,数据:
botan_data_gcc.csv https://gist.github.com/4155964#file_botan_data_gcc.csv
Box2D 摘要(78 单元)
牡丹概要(301 单位)
漂亮的图表:
Box2D 可执行文件大小:
Box2D 编译/链接/构建/运行时:
Box2D 编译/链接/构建/运行最大内存使用量:
Botan 可执行文件大小:
Botan 编译/链接/构建/运行时:
Botan 编译/链接/构建/运行最大内存使用量:
基准详情
TL;DR
所测试的项目,Box2D http://box2d.org/ and Botan http://botan.randombit.net/之所以选择它们,是因为它们的计算成本可能很高,包含大量单元,并且实际上作为单个单元进行编译时很少或没有错误。尝试了许多其他项目,但花费了太多时间来“修复”为一个单元进行编译。内存占用量是通过定期轮询内存占用量并使用最大值来测量的,因此可能不完全准确。
此外,此基准测试不会自动生成标头依赖项(以检测标头更改)。在使用不同构建系统的项目中,这可能会增加所有基准测试的时间。
基准测试中有 3 个编译器,每个编译器有 5 种配置。
编译器:
编译器配置:
- 默认 - 默认编译器选项
- 优化原生 -
-O3 -march=native
- 尺寸优化 -
-Os
- LTO/IPO 本机 -
-O3 -flto -march=native
与 clang 和 gcc 一起,-O3 -ipo -march=native
与icpc/icc
- 零优化——
-Os
我认为这些对于单单元和多单元构建之间的比较都有不同的影响。我将 LTO/IPO 纳入其中,以便我们可以了解如何比较实现单一单位有效性的“正确”方法。
csv字段说明:
-
Test Name
- 基准的名称。例子:Botan, Box2D
.
- 测试配置 - 命名此测试的特定配置(特殊 cxx 标志等)。通常与
Test Name
.
-
Compiler
- 使用的编译器的名称。例子:gcc,icc,clang
.
-
Compiler Configuration
- 使用的编译器选项配置的名称。例子:gcc opt native
-
Compiler Version String
- 编译器本身的编译器版本输出的第一行。例子:g++ --version
产生g++ (GCC) 4.6.1
在我的系统上。
-
Header only
- 值为True
如果这个测试用例是作为一个单元构建的,False
如果它是作为一个多单元项目构建的。
-
Units
- 测试用例中的单元数量,即使它是作为单个单元构建的。
-
Compile Time,Link Time,Build Time,Run Time
- 听起来。
-
Re-compile Time AVG,Re-compile Time MAX,Re-link Time AVG,Re-link Time MAX,Re-build Time AVG,Re-build Time MAX
- 接触单个文件后重建项目的时间。每个单元都会被触及,并且对于每个单元,项目都会被重建。最大次数和平均次数记录在这些字段中。
-
Compile Memory,Link Memory,Build Memory,Run Memory,Executable Size
- 正如他们听起来的那样。
重现基准:
- 斗牛是run.py https://gist.github.com/realazthat/4155964#file-run-py.
- 需要psutil http://code.google.com/p/psutil/(用于内存占用测量)。
- 需要 GNUMake。
- 事实上,路径中需要 gcc、clang、icc/icpc。当然可以进行修改以删除其中任何一个。
- Each benchmark should have a data-file that lists the units of that benchmarks. run.py https://gist.github.com/4155964#file-run-py will then create two test cases, one with each unit compiled separately, and one with each unit compiled together. Example: box2d.data https://gist.github.com/realazthat/4155964#file-box2d-data.data. The file format is defined as a json string, containing a dictionary with the following keys
-
"units"
- 的列表c/cpp/cc
构成该项目单元的文件
-
"executable"
- 要编译的可执行文件的名称。
-
"link_libs"
- 要链接到的已安装库的空格分隔列表。
-
"include_directores"
- 要包含在项目中的目录列表。
-
"command"
- 选修的。执行运行基准测试的特殊命令。例如,"command": "botan_test --benchmark"
- 并非所有 C++ 项目都可以轻松完成此操作;单个单元中不得存在冲突/歧义。
- 要将项目添加到测试用例,请修改列表
test_base_cases
in run.py https://gist.github.com/4155964#file_run.py包含项目信息,包括数据文件名。
- 如果一切顺利,输出文件
data.csv
应包含基准测试结果。
要生成条形图:
- 您应该从基准测试生成的 data.csv 文件开始。
- Get chart.py https://gist.github.com/realazthat/4155964#file-chart-py。需要绘图库 http://matplotlib.org/.
- 调整
fields
列表来决定生成哪些图表。
- Run
python chart.py data.csv
.
- A file,
test.png
现在应该包含结果。
Box2D
- Box2D 的使用时间为svn 原样 http://code.google.com/p/box2d/source/checkout,修订版 251。
- 基准取自here https://github.com/joelgwebber/bench2d/blob/master/c/Bench2d.cpp, 修改的here https://gist.github.com/4155964#file_box2d_bench1.cpp并且可能无法代表良好的 Box2D 基准测试,并且它可能没有使用足够的 Box2D 来公正地执行此编译器基准测试。
- box2d.data 文件是通过查找所有 .cpp 单元手动编写的。
Botan
- Using Botan-1.10.3 http://botan.randombit.net/download.html.
- 数据文件:botan_bench.data https://gist.github.com/4155964#file_botan_bench.data.
- 第一次跑
./configure.py --disable-asm --with-openssl --enable-modules=asn1,benchmark,block,cms,engine,entropy,filters,hash,kdf,mac,bigint,ec_gfp,mp_generic,numbertheory,mutex,rng,ssl,stream,cvc
,这会生成头文件和 Makefile。
- 我禁用了汇编,因为汇编可能会干扰当函数边界不阻止优化时可能发生的优化。然而,这只是推测,可能是完全错误的。
- 然后运行如下命令
grep -o "\./src.*cpp" Makefile
and grep -o "\./checks.*" Makefile
获取 .cpp 单元并将它们放入botan_bench.data https://gist.github.com/4155964#file_botan_bench.data file.
- 修改的
/checks/checks.cpp
由于 Botan typedef 和 openssl 之间的冲突,不调用 x509 单元测试,并删除了 x509 检查。
- 使用了 Botan 源代码中包含的基准。
系统规格:
- OpenSuse 11.4,32 位
- 4GB RAM
Intel(R) Core(TM) i7 CPU Q 720 @ 1.60GHz
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)