在 C++ 开发中,错误处理是一个至关重要的组成部分。不同的错误处理策略不仅会影响代码的可读性和可维护性,还会对应用程序的性能产生重大影响。为了深入了解各种错误处理技术在性能上的差异,本文作者进行了基准测试。本次测试旨在提供清晰的性能洞察,帮助开发者在实际应用中选择最合适的错误处理方法。
原文链接:
https://johnfarrier.com/c-error-handling-strategies-benchmarks-and-performance/
声明:未经允许,禁止转载。
错误处理是许多 C++ 应用程序中的关键部分,我们也有许多策略可以用来处理错误。最近,我想更好地了解 C++ 中各种错误处理技术对性能的影响。
C++ 错误处理实验设置
我决定对不同的技术进行基准测试,看看它们的性能如何。为此,我使用了 Celero C++ 微基准测试库,以此清楚地了解每种方法的性能。
例如,std::expected 在可读性和可维护性方面有很多优点,同时考虑它的性能特征也很重要。通常,std::expected 比 exception(异常)更有效,因为它避免了堆栈展开的开销,而这在性能关键型应用程序中可能非常重要。
我构建了一个基准测试来比较以下 C++ 错误处理策略:
● 使用返回 true/false 表示成功/失败(Baseline)
● 使用错误代码(Error Code)返回值
● 使用 std::expected
● 使用 std::optional
● 使用 std::variant
● 使用 Callback
● 使用 std::exception
该 C++ 错误处理基准测试是用 Celero C++ 微基准测试库构建的。关于 C++ 错误处理基准测试的具体代码可查看:
https://github.com/DigitalInBlue/celeroErrorHandlingBenchmark。
错误处理实验结果
以下是原始输出结果:
为简化起见,以下是这些技术相对性能的精简版本:
在基线(Baseline) 归一化性能的情况下,使用 std::expected 慢约 2.1 倍。
排除 std::exception 后,C++ 错误处理基准测试结果表明,各种错误处理技术在性能上存在显著差异:
● Baseline(基线) :基线方法是返回 true 或 false,是最快的技术,平均每次迭代耗时 0.00324 微秒。
● std::expected:使用 std::expected 比基线慢约 2.18 倍,平均每次迭代耗时 0.00707 微秒。
● Error Code(错误代码):使用 Error Code 几乎与基线一样快,平均每次迭代耗时 0.00329 微秒。
● Optional:使用 std::optional 较慢,平均每次迭代耗时 0.00578 微秒,比基线慢约 1.78 倍。
● Variant:使用 std::variant 是非异常技术中最慢的,平均每次迭代耗时 0.00919 微秒,比基线慢约 2.83 倍。
● Error Callback(错误回调):使用 Error Callback 对性能影响的中等,平均每次迭代耗时 0.00429 微秒,比基线慢约 1.32 倍。
● Exception(异常):使用 std::exception 明显比所有其他技术都慢,平均每次迭代耗时 160.59420 微秒,比基线慢约 49,540 倍。
详细分析和意见
(1)可读性与性能之间的权衡:
虽然 std::expected 和类似结构(如 std::optional,std::variant)提供了更具可读性和可维护性的代码,但同时也带来了相应的性能成本。在可读性和可维护性至关重要、而对性能要求不高的应用程序中,这些结构具有优势;然而,在对性能要求较高的应用程序中,可能更偏向于使用简单的错误处理方法。
通常情况下,我们建议在整个应用程序中选择一种错误处理方法,但有时可能也需要一种“默认”的错误处理方式(如使用 std::expected)和一种在特定情况下使用的“高性能”替代方案。
(2)不同情况下的错误处理策略:
高性能系统:对于需要高性能的系统,如实时系统或高频交易平台,应首选开销最小的错误处理技术,如返回代码或错误回调。
现代 C++ 应用程序:受益于现代 C++ 范式且对性能要求不太高的应用程序,可以用 std::expected 和 std::optional 获得更好的代码表达能力。
(3)Exception(异常)的影响:
异常带来的巨大开销(比基线慢 49,540 倍)凸显了为何在性能关键的代码中避免使用异常的原因。异常处理期间的堆栈展开过程会导致相当大的延迟。不过,异常对于处理应用程序中对性能要求较低部分的意外或罕见错误情况非常有用。
(4)基准环境和条件:
需要注意的是,基准测试可能会受到运行环境的影响(例如,CPU,编译器优化等)。本文提供的结果是针对特定基准条件下的结果,应根据你的具体应用进行验证。
(5)内存使用注意事项:
虽然运行时性能至关重要,但内存使用也是一个重要因素。基准测试结果表明,大多数技术(不包括例外情况)的内存使用情况相似,这说明错误处理方法的选择对内存占用的影响可能微乎其微。
(6)可组合性和集成性:
std::expected 和 std::optional 与其他现代 C++ 特性集成得很好,可能会简化复杂的错误传播场景。
关于 C++ 错误处理的建议
根据基准测试结果,C++ 错误处理技术的选择应由应用程序的具体需求决定。以下是一些建议:
● 性能关键型应用:使用简单的错误处理方法,如返回代码或错误回调,尽量减少开销。
● 现代、可维护的代码:使用 std::expected 或 std::optional 以提高代码的清晰度和可维护性,尤其是在可以接受轻度性能开销的情况下。
● 异常处理:将异常保留给意外或不常发生的错误,因需要全面的错误信息,所以巨大的开销也算合理。
这些 C++ 错误处理基准测试强调了根据应用程序的性能需求选择正确错误处理技术的重要性。尽管 std::expected 和其他现代 C++ 特性提供了更好的可读性和可维护性,但它们对性能的影响也不容忽视,尤其是在对性能要求极高的应用程序中。
关于 LINUX 的资料浩如烟海,学习 LINUX 的途径也有很多,如何才能在比较短的时间里获得一个比较大的提升呢?《LINUX 平台高级调试与优化》将与各位 LINUX 爱好者共同探索这个问题的最佳答案。
本着生动有趣、理论与实践密切结合的原则,本研习班独辟蹊径,使用调试之剑披荆斩棘,带你闯荡纷繁复杂的 LINUX 世界。本研习班由《软件调试》、《软件简史》和《格蠹汇编》的作者张银奎主讲。