关于纯头文件 C++ 库使用情况的可量化指标(基准)

2023-12-22

我试图用 SO 找到这个问题的答案。有很多问题列出了在 c++ 中构建仅头文件库的各种优缺点,但我还没有找到一个以可量化的方式做到这一点的问题。

那么,从可量化的角度来看,使用传统上分离的 C++ 头文件和实现文件与仅使用头文件有什么不同?

为简单起见,我假设不使用模板(因为它们仅需要标头)。

为了详细说明,我列出了我从文章中看到的优点和缺点。显然,有些是不容易量化的(例如易用性),因此对于量化比较来说是无用的。我将用(可量化)标记那些我期望可量化的指标。

仅标题的优点

  1. 它更容易包含,因为您不需要在构建系统中指定链接器选项。
  2. 您始终使用与其余代码相同的编译器(选项)来编译所有库代码,因为库的函数内联在您的代码中。
  3. 可能会快很多。 (可量化)
  4. 可以为编译器/链接器提供更好的优化机会(解释/可量化,如果可能)
  5. 如果您无论如何使用模板,则这是必需的。

仅标题的缺点

  1. 它使代码变得臃肿。 (可量化)(这如何影响执行时间和内存占用)
  2. 编译时间更长。 (可量化)
  3. 失去接口和实现的分离。
  4. 有时会导致难以解决的循环依赖。
  5. 防止共享库/DLL 的二进制兼容性。
  6. 这可能会激怒那些更喜欢使用 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 种配置。

编译器:

  • gcc
  • icc
  • clang

编译器配置:

  • 默认 - 默认编译器选项
  • 优化原生 --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(使用前将#替换为@)

关于纯头文件 C++ 库使用情况的可量化指标(基准) 的相关文章

  • 与 for_each 或 std::transform 一起使用时,如何调用 C++ 函子构造函数

    我以前从未使用过 C 函子 所以我只是想了解它们是如何工作的 例如假设我们有这个函子类 class MultiplyBy private int factor public MultiplyBy int x factor x int ope
  • 为什么 C 程序使用 Scanf 给出奇怪的输出?

    我目前正在学习 C 编程 并且遇到了这个奇怪的输出 Program will try functionalities of the scanf function include
  • strlen() 编译时优化

    前几天我发现你可以找到编译时strlen使用这样的东西 template
  • 在 C++ 代码中转换字符串

    我正在学习 C 并开发一个项目来练习 但现在我想在代码中转换一个变量 字符串 就像这样 用户有一个包含 C 代码的文件 但我希望我的程序读取该文件并插入将其写入代码中 如下所示 include
  • 2个对象,完全相同(除了命名空间)c#

    我正在使用第三方的一组网络服务 但遇到了一个小障碍 在我手动创建将每个属性从源复制到目标的方法之前 我想我应该在这里寻求更好的解决方案 我有 2 个对象 一个是 Customer CustomerParty 类型 另一个是 Appointm
  • 为什么这个 makefile 在“make clean”上执行目标

    这是我当前的 makefile CXX g CXXFLAGS Wall O3 LDFLAGS TARGET testcpp SRCS main cpp object cpp foo cpp OBJS SRCS cpp o DEPS SRCS
  • Linux 上的 RTLD_LOCAL 和dynamic_cast

    我们有一个由应用程序中的一些共享库构成的插件 我们需要在应用程序运行时更新它 出于性能原因 我们在卸载旧插件之前加载并开始使用新插件 并且只有当所有线程都使用旧插件完成后 我们才卸载它 由于新插件和旧插件的库具有相同的符号 我们dlopen
  • 来自嵌入图像的 BitmapSource

    我的目标是在 WPF 窗口上重写 OnRender 方法中绘制图像 someImage png 它是嵌入资源 protected override void OnRender System Windows Media DrawingCont
  • 条件类型定义

    如果我有一小段这样的代码 template
  • C# 获取数据表中所有重复行的计数

    我通过运行存储过程来填充数据集 并且从数据集中填充数据表 DataSet RawDataSet DataAccessHelper RunProcedure storedprocedureName this will just return
  • wordexp 失败时我们需要调用 wordfree 吗?

    wordexp 失败时我们需要调用 wordfree 吗 在某些情况下 调用 wordfree 似乎会出现段错误 例如 当 wordfree 返回字符串为 foo bar 的错误代码时 这在手册页中并不清楚 我已经看到在某些错误情况下使用了
  • ASP.NET Core 中间件与过滤器

    在阅读了 ASP NET Core 中间件之后 我对何时应该使用过滤器以及何时应该使用中间件感到困惑 因为它们似乎实现了相同的目标 什么时候应该使用中间件而不是过滤器 9频道有一个关于此的视频 ASP NET 怪物 91 中间件与过滤器 h
  • 读取依赖步行者输出

    I am having some problems using one of the Dlls in my application and I ran dependency walker on it i am not sure how to
  • 使用 gcc 时在头文件中查找定义的好方法是什么?

    在使用 gcc 时 有人有推荐的方法在头文件中查找定义吗 使用 MSVC 时 我只需右键单击并选择 转到定义 这非常好 我使用过 netbeans gcc 它确实有代码帮助 包括到定义的超链接 所以这是一种选择 但是 我想知道是否有任何其他
  • C++ 指针引用混淆

    struct leaf int data leaf l leaf r struct leaf p void tree findparent int n int found leaf parent 这是 BST 的一段代码 我想问一下 为什么
  • C:设置变量范围内所有位的最有效方法

    让我们来int举个例子 int SetBitWithinRange const unsigned from const unsigned to To be implemented SetBitWithinRange应该返回一个int其中所有
  • 如何从 Windows Phone 7 模拟器获取数据

    我有一个 WP7 的单元测试框架 它在手机上运行 结果相当难以阅读 因此我将它们写入 XDocument 我的问题是 如何才能将这个 XML 文件从手机上移到我的桌面上 以便我可以实际分析结果 到目前为止 我所做的是将 Debugger B
  • 从后面的代码添加外部 css 文件

    我有一个 CSS 文件 例如 SomeStyle css 我是否可以将此样式表文档从其代码隐藏应用到 aspx 页面 您可以将文字控件添加到标头控件中 Page Header Controls Add new System Web UI L
  • 声明一个负长度的数组

    当创建负长度数组时 C 中会发生什么 例如 int n 35 int testArray n for int i 0 i lt 10 i testArray i i 1 这段代码将编译 并且启用 Wall 时不会出现警告 并且似乎您可以分配
  • 如果找不到指定的图像文件,显示默认图像的最佳方式?

    我有一个普通的电子商务应用程序 我将 ITEM IMAGE NAME 存储在数据库中 有时经理会拼错图像名称 为了避免 丢失图像 IE 中的红色 X 每次显示产品列表时 我都会检查服务器中是否有与该产品相关的图像 如果该文件不存在 我会将其

随机推荐

  • Azure Blob 存储容器有文件数量限制吗?

    正如问题提到的 Azure存储帐户中的每个容器是否有存储的文件数量限制 或者我们可以在每个容器中存储无限数量的文件 只要存储帐户下仍有可用空间 有什么官方文档可以参考吗 只要存储帐户下仍有可用空间 您就可以在每个容器中存储无限数量的文件 存
  • 如何在 PyQt5 Python 中根据窗口大小调整 PyQt5 堆叠小部件的大小?

    我正在设计一个ui in qt desginer 我必须创建一个用户界面 其中有一个带有按钮的侧面菜单栏 无论按下哪个按钮 其相应的数据都会显示在右侧 为了设计这个 我放置了两个QFrame in horizontal layout在中央小
  • ember-simple-auth deferReadiness 直到用户加载

    我正在使用 ember cli simple auth 并扩展了会话对象以包含从 me端点 但是 当重新加载页面并且用户登录时 会出现延迟 直到加载登录的用户信息为止 我想推迟应用程序准备就绪 直到检索到用户 我有这个在custom ses
  • 如何使用 Nvidia Visual Profile 和 Matlab 来分析 CUDA

    我需要从 Matlab 分析我的 CUDA 代码 我已经将一些 matlab 代码转换为 CUDA 以提高性能 我通过调用调用 CUDA 的 mexFunction 来完成此操作 matlab feval 函数没有提供足够的控制 我可以通过
  • Eclipse 关于 Java 中私有静态嵌套类的合成访问器的警告?

    我的同事建议使一些 Eclipse 代码格式和警告设置更加严格 大多数这些更改都是有意义的 但我在 Java 中收到了一个奇怪的警告 这是一些重现 问题 的测试代码 package com example bugs public class
  • Python 根据字典中的值绘制图表

    我有一本看起来像这样的字典 test 1092268 81 90 524292 80 80 892456 88 88 现在我想从这本字典中绘制一个简单的图 如下所示 test 1092268 x y 524292 x y 892456 x
  • JFrame 接近后台并听按键

    Working on a new personal project with jframe questions tagged jframe My goal is to close the frame in an ActionListener
  • 对 eval() 的调用被 CSP 使用 Selenium IDE 阻止

    我开发了一个硒测试Selenium IDE 我在这个套件中有一个步骤 应该在文本字段中输入一个值 它在该步骤失败并给出以下错误 18 click on id firstName Failed 11 12 59 call to eval bl
  • C 中的二维数组初始化

    我知道这是一个老栗子 但我想要在我的代码中静态分配一个小的二维数组 我知道做到这一点的方法是 static int A 3 2 1 2 3 4 5 6 没关系 我可以访问它的所有成员 但是 我在将其传递给函数时遇到了几个问题 例如 void
  • 为什么“VolatileQualifiedExpr + VolatileQualifiedExpr”在C中不一定是UB,而在C++中却不一定是UB?

    当我今天读 C 标准时 它提到了副作用 访问易失性对象 修改对象 修改文件或调用函数 这些操作中的任何一个都是副作用 C 标准说 访问由易失性泛左值 3 10 指定的对象 修改对象 调用库 I O 函数或调用执行任何这些操作的函数都是副作用
  • Elastic beanstalk 上的 wsgi 用户权限

    我正在使用弹性豆茎和 django 我的requirements txt 文件中的一个依赖项有一些在最初导入时执行的设置 设置的一部分是检查目录是否存在 否则创建它 我收到权限错误 因为用户 我假设是 wsgi 没有创建目录的权限 OSEr
  • Python 中的快速简单的文件对话框?

    我有一个简单的脚本 它解析文件并将其内容加载到数据库中 我不需要 UI 但现在我提示用户使用以下命令解析文件raw input这是最不友好的 特别是因为用户无法复制 粘贴路径 我想要一种快速简便的方法来向用户呈现文件选择对话框 他们可以选择
  • ExpandableListView 组项上的 Android LongClickListener

    我在本教程的帮助下创建了一个 ExpandableListView link http blog csdn net avenleft article details 7192972 我或多或少地理解了代码 并尝试在组上设置一个 longcl
  • 如何知道事务方案何时可序列化?

    我正在研究SQL 需要知道某个事务方案是否可序列化 我理解确定这一点的方法是制作一个以事务作为节点和节点之间的方向的图 如果该图是循环的 则该方案不可序列化 但这是什么意思以及什么决定了图中是否存在从一笔交易到另一笔交易的有向边 在这种情况
  • USE_CREDENTIALS 在新的 Android M API 中不可用

    在尝试新的权限 API 时 值得注意的是 ActivityCompat checkSelfPermission ActivityCompat shouldShowRequestPermissionRationale ActivityComp
  • 从 C 创建一个实现 __dict__ 的 Python 类型?

    如何创建一个类型以具有 dict 按照 正常 类的说法 它是在 Python 中定义的吗 是否有任何非动态类型的示例 dict s 通过 Python 定义类型通过 有一个tp dict成员PyTypeObject 但我找不到有关如何使用它
  • 如何在根视图中隐藏导航控制器?

    请帮助我在根视图中隐藏导航控制器 我找到了写的解决方案 navigationController setNavigationBarHidden YES 在我需要的每个视图控制器中 好吧 它可以工作 但只是第一次 我运行应用程序 在根视图中我
  • JMSTemplate 检查主题是否存在并获取订阅者数量

    我一直在寻找一些文档 示例来检查动态创建的主题是否存在 如果存在 如何获取该主题的订阅者计数 我使用以下代码向主题发送消息 jmsTemplate send destination new MessageCreator Override p
  • 在不同数组中添加不同部分的选定单元格,UICollectionView

    我想添加选定的单元格UICollectionView在数组中 按部分划分在不同的数组中 意味着每个部分有不同的数组 问题是部分的数量是动态的 下面是我的代码 void collectionView UICollectionView coll
  • 关于纯头文件 C++ 库使用情况的可量化指标(基准)

    我试图用 SO 找到这个问题的答案 有很多问题列出了在 c 中构建仅头文件库的各种优缺点 但我还没有找到一个以可量化的方式做到这一点的问题 那么 从可量化的角度来看 使用传统上分离的 C 头文件和实现文件与仅使用头文件有什么不同 为简单起见