欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-29 08:18:21
...

  作者: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

  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

  // 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

  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++ 慢