博客列表 >进阶指南!C++ Exception 性能测试与实现浅探

进阶指南!C++ Exception 性能测试与实现浅探

Coco
Coco原创
2021年12月13日 15:16:59394浏览

  作者:roanhe,腾讯TEG云架平台部

  | 导语 异常处理是写代码过程中无法避开的部分。正确使用异常机制,需要我们对其性能以及背后实现有一个基本的理解,本文的写作目的是对C++ Exception机制进行简单测试,并且对其实现进行简单分析,以帮助广大C++程序员更好地使用Exception。

  很多编程语言中都有 Exception 机制。利用 Exception 机制,一段代码可以绕过正常的代码执行路径去通知另一段代码,有一些意外事件或者错误情况发生。另一种常见的异常/错误处理机制是ErrorCode,熟悉 C 语言的同学应该体会很深,比如操作系统提供的接口很多都是以 ErrorCode 的形式判断是否发生异常。

  C++ 并不像 Java 一样强制程序员使用 Exception,但是在 C++ 中处理 Exception 是不可避免的,比如当内存不足时,new 操作符会抛出std::bad_alloc。同时在 C++ 中单纯使用 ErrorCode 来标记异常情况也有其他问题:

  ErrorCode 没有统一标准,没有严格标准规定到底是返回使用-1表示Error还是使用0表示Error,所以你需要额外配合使用枚举;ErrorCode 可能会被忽略,虽然C++17中有了[[nodiscard]]属性,但是你还是有可能会忘记加 nodiscard!毕竟忘记加 nodiscard 并不比忘记处理 ErrorCode 难多少。。

  因此,掌握 C++ Exception 的原理以及正确使用方式是非常必要的。同时 C++ 目前依然是在高性能编程场景下的首选编程序言,很多同学出于性能考虑不敢使用 C++ Exception,只知道 Exception 慢,但是并不知道到底是为什么慢,究竟慢在哪里。

  本文的目的是对 C++ Exception 进行简单测试与分析。首先对 Exception 的性能进行评测,探究 C++ Exception 对程序性能的影响,然后对 C++ Exception 的实现机制做一个简单探索,让大家明白 Exception 对程序运行到底产生了哪些影响,进而写出更高质量的代码。

  Benchmark

  首先我们先通过性能测试直观地感受一下添加 Exception 对程序性能的影响。

  参考Investigating the Performance Overhead of C++ Exceptions的测试思路,我们对其测试用例进行改动。简单解释一下我们的测试代码: 我们定义一个函数,该函数会根据概率决定是否调用目标函数:

  const int randomRange=2;

  const int errorInt=1;

  int getRandom() { return random() % randomRange; }

  template

  T testFunction(const std::function& fn) {

  auto num=getRandom();

  for (int i{0}; i < 5; ++i) {

  if (num==errorInt) {

  return fn();

  }

  }

  }

  执行 testFunction 时,目标函数 fn 有 50% 的概率被调用。

  void exitWithStdException() {

  testFunction([]() -> void {

  throw std::runtime_error("Exception!");

  });

  }

  void BM_exitWithStdException(benchmark::State& state) {

  for (auto _ : state) {

  try {

  exitWithStdException();

  } catch (const std::runtime_error &ex) {

  BLACKHOLE(ex);

  }

  }

  }

  BM_exitWithStdException 用于测试函数 exitWithStdException,该函数会抛出一个 Exception,然后在 BM_exitWithStdException 中立刻被 catch,catch 后我们什么也不做。

  类似的,我们设计用于测试 ErrorCode 模式的代码如下:

  void BM_exitWithErrorCode(benchmark::State& state) {

  for (auto _ : state) {

  auto err=exitWithErrorCode();

  if (err < 0) {

  // handle_error()

  BLACKHOLE(err);

  }

  }

  }

  int exitWithErrorCode() {

  testFunction([]() -> int {

  return -1;

  });

  return 0;

  }

  将 ErrorCode 测试代码放进 try{...}catch{...} 测试只进入 try 是否会对性能有影响:

  void BM_exitWithErrorCodeWithinTry(benchmark::State& state) {

  for (auto _ : state) {

  try {

  auto err=exitWithErrorCode();

  if (err < 0) {

  BLACKHOLE(err);

  }

  } catch(...) {

  }

  }

  }

  利用 gtest/banchmark 开始我们的测试:

  BENCHMARK(BM_exitWithStdException);

  BENCHMARK(BM_exitWithErrorCode);

  BENCHMARK(BM_exitWithErrorCodeWithinTry);

  BENCHMARK_MAIN();

  测试结果:

  2021-07-08 20:59:44

  Running ./benchmarkTests/benchmarkTests

  Run on (12 X 2600 MHz CPU s)

  CPU Caches:

  L1 Data 32K (x6)

  L1 Instruction 32K (x6)

  L2 Unified 262K (x6)

  L3 Unified 12582K (x1)

  Load Average: 2.06, 1.88, 1.94

  ***WARNING*** Library was built as DEBUG. Timings may be affected.

  ------------------------------------------------------------------------

  Benchmark Time CPU Iterations

  ------------------------------------------------------------------------

  BM_exitWithStdException 1449 ns 1447 ns 470424

  BM_exitWithErrorCode 126 ns 126 ns 5536967

  BM_exitWithErrorCodeWithinTry 126 ns 126 ns 5589001

  这是我在自己的 mac 上测试的结果,使用的编译器版本为gcc version 10.2.0,异常模型为DWARF2。可以看到,当 Error/Exception 发生率为 50% 时,Exception 的处理速度要比返回 ErrorCode 慢 10 多倍。同时,对一段不会抛出异常的代码添加 try{...}catch{...} 则不会对游戏账号购买平台性能有影响。我们可以再将 Error/Exception 的发生率调的更低测试下:

  const int randomRange=100;

  const int errorInt=1;

  int getRandom() { return random() % randomRange; }

  我们将异常的概率降低到了 1%,继续测试:

  2021-07-08 21:16:01

  Running ./benchmarkTests/benchmarkTests

  Run on (12 X 2600 MHz CPU s)

  CPU Caches:

  L1 Data 32K (x6)

  L1 Instruction 32K (x6)

  L2 Unified 262K (x6)

  L3 Unified 12582K (x1)

  Load Average: 2.80, 2.22, 1.93

  ***WARNING*** Library was built as DEBUG. Timings may be affected.

  ------------------------------------------------------------------------

  Benchmark Time CPU Iterations

  ------------------------------------------------------------------------

  BM_exitWithStdException 140 ns 140 ns 4717998

  BM_exitWithErrorCode 111 ns 111 ns 6209692

  BM_exitWithErrorCodeWithinTry 113 ns 113 ns 6230807

  可以看到,Exception 模式的性能大幅提高,接近了 ErrorCode 模式。

  从实验结果,我们可以得出如下的结论:

  在 throw 发生的很频繁的情况(50%)下,Exception 机制相比 ErrorCode 会慢非常多;在 throw 并不是经常发生的情况(1%)下,Exception 机制并不会比 ErrorCode 慢;

  由此结论,我们可以进而得到如下的使用建议:

  不要使用 try{throw ...}catch(){...} 来充当你的代码控制流,这会导致你的 C++ 慢

声明:本文内容转载自脚本之家,由网友自发贡献,版权归原作者所有,如您发现涉嫌抄袭侵权,请联系admin@php.cn 核实处理。
全部评论
文明上网理性发言,请遵守新闻评论服务协议