Mais rápido `finalmente` para C ++ [fechado]
C ++ até agora (infelizmente) não suportafinally
cláusula para umtry
declaração. Isso leva a especulações sobre como liberar recursos. Depois de estudar a questão na internet, embora tenha encontrado algumas soluções, não fiquei claro sobre o desempenho deles (e usaria Java se o desempenho não importasse tanto). Então eu tive que comparar.
As opções são:
Baseado em Functorfinally
classe proposta emCodeProject. É poderoso, mas lento. E a desmontagem sugere que as variáveis locais da função externa são capturadas de maneira muito ineficiente: empurradas para a pilha uma a uma, em vez de passar apenas o ponteiro do quadro para a função interna (lambda).
RAII: Objeto de limpeza manual na pilha: a desvantagem é a digitação manual e a adaptação para cada local usado. Outra desvantagem é a necessidade de copiar para ele todas as variáveis necessárias para a liberação do recurso.
Específico para MSVC ++__try
/ __finally
declaração. A desvantagem é que obviamente não é portátil.
Criei esse pequeno benchmark para comparar o desempenho em tempo de execução dessas abordagens:
#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;
}
Os resultados da minha Ryzen 1800X a 3.9Ghz com DDR4 a 2.6Ghz CL13 foram:
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
Aparentemente, todas as opções, exceto functor-base (# 1), são tão rápidas quanto um loop vazio.
Existe uma alternativa C ++ rápida e poderosa parafinally
, que é portátil e requer cópia mínima da pilha da função externa?
ATUALIZAÇÃO: Comparei a solução @ Jarod42, então aqui na pergunta há código e saída atualizados. Embora como mencionado por @Sopel, ele pode quebrar se a cópia não for executada.
UPDATE2: para esclarecer o que estou pedindo, é uma maneira rápida e conveniente em C ++ de executar um bloco de código, mesmo que uma exceção seja lançada. Pelas razões mencionadas na pergunta, algumas maneiras são lentas ou inconvenientes.