Ser*_*tch 5 c++ performance finally raii try-catch-finally
到目前为止,C++(不幸的是)不支持finally语句的子句try.这导致了对如何释放资源的猜测.在互联网上研究了这个问题之后,虽然我找到了一些解决方案,但我并没有明白他们的表现(如果表现并不重要,我会使用Java).所以我不得不做基准测试.
选项是:
CodeProjectfinally提出的基于Functor的类.它很强大,但很慢.并且反汇编表明外部函数局部变量被非常低效地捕获:逐个推送到堆栈,而不是仅仅将帧指针传递给内部(lambda)函数.
RAII:堆栈上的手动清洁器对象:缺点是手动键入并为每个使用的位置定制它.另一个缺点是需要将资源释放所需的所有变量复制到其中.
MSVC++特定__try/ __finally声明.缺点是它显然不便携.
我创建了这个小基准来比较这些方法的运行时性能:
#include <chrono>
#include <functional>
#include <cstdio>
class Finally1 {
std::function<void(void)> _functor;
public:
Finally1(const std::function<void(void)> &functor) : _functor(functor) {}
~Finally1() {
_functor();
}
};
void BenchmarkFunctor() {
volatile int64_t var = 0;
const int64_t nIterations = 234567890;
auto start = std::chrono::high_resolution_clock::now();
for (int64_t i = 0; i < nIterations; i++) {
Finally1 doFinally([&] {
var++;
});
}
auto elapsed = std::chrono::high_resolution_clock::now() - start;
double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
printf("Functor: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}
void BenchmarkObject() {
volatile int64_t var = 0;
const int64_t nIterations = 234567890;
auto start = std::chrono::high_resolution_clock::now();
for (int64_t i = 0; i < nIterations; i++) {
class Cleaner {
volatile int64_t* _pVar;
public:
Cleaner(volatile int64_t& var) : _pVar(&var) { }
~Cleaner() { (*_pVar)++; }
} c(var);
}
auto elapsed = std::chrono::high_resolution_clock::now() - start;
double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
printf("Object: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}
void BenchmarkMSVCpp() {
volatile int64_t var = 0;
const int64_t nIterations = 234567890;
auto start = std::chrono::high_resolution_clock::now();
for (int64_t i = 0; i < nIterations; i++) {
__try {
}
__finally {
var++;
}
}
auto elapsed = std::chrono::high_resolution_clock::now() - start;
double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
printf("__finally: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}
template <typename Func> class Finally4 {
Func f;
public:
Finally4(Func&& func) : f(std::forward<Func>(func)) {}
~Finally4() { f(); }
};
template <typename F> Finally4<F> MakeFinally4(F&& f) {
return Finally4<F>(std::forward<F>(f));
}
void BenchmarkTemplate() {
volatile int64_t var = 0;
const int64_t nIterations = 234567890;
auto start = std::chrono::high_resolution_clock::now();
for (int64_t i = 0; i < nIterations; i++) {
auto doFinally = MakeFinally4([&] { var++; });
//Finally4 doFinally{ [&] { var++; } };
}
auto elapsed = std::chrono::high_resolution_clock::now() - start;
double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
printf("Template: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}
void BenchmarkEmpty() {
volatile int64_t var = 0;
const int64_t nIterations = 234567890;
auto start = std::chrono::high_resolution_clock::now();
for (int64_t i = 0; i < nIterations; i++) {
var++;
}
auto elapsed = std::chrono::high_resolution_clock::now() - start;
double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
printf("Empty: %.3lf Ops/sec, var=%lld\n", nIterations / nSec, (long long)var);
}
int __cdecl main() {
BenchmarkFunctor();
BenchmarkObject();
BenchmarkMSVCpp();
BenchmarkTemplate();
BenchmarkEmpty();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我的Ryzen 1800X @ 3.9Ghz与DDR4 @ 2.6Ghz CL13的结果如下:
Functor: 175148825.946 Ops/sec, var=234567890
Object: 553446751.181 Ops/sec, var=234567890
__finally: 553832236.221 Ops/sec, var=234567890
Template: 554964345.876 Ops/sec, var=234567890
Empty: 554468478.903 Ops/sec, var=234567890
Run Code Online (Sandbox Code Playgroud)
显然,除了functor-base(#1)之外的所有选项都和空循环一样快.
那么有一个快速而强大的C++替代品finally,它是可移植的,并且需要从外部函数的堆栈中进行最少的复制吗?
更新:我已经对@ Jarod42解决方案进行了基准测试,所以这里的问题是更新代码和输出.尽管如@Sopel所述,如果不执行复制省略,它可能会中断.
UPDATE2:澄清我要求的是一种方便快捷的C++方法,即使抛出异常也可以执行代码块.由于问题中提到的原因,某些方法很慢或不方便.
Jar*_*d42 11
您可以实现Finally没有类型擦除和开销std::function:
template <typename F>
class Finally {
F f;
public:
template <typename Func>
Finally(Func&& func) : f(std::forward<Func>(func)) {}
~Finally() { f(); }
Finally(const Finally&) = delete;
Finally(Finally&&) = delete;
Finally& operator =(const Finally&) = delete;
Finally& operator =(Finally&&) = delete;
};
template <typename F>
Finally<F> make_finally(F&& f)
{
return { std::forward<F>(f) };
}
Run Code Online (Sandbox Code Playgroud)
并使用它像:
auto&& doFinally = make_finally([&] { var++; });
Run Code Online (Sandbox Code Playgroud)