I've made this simple benchmark to test how noticeable the gains really are.
#define MEASURE(CODE_TO_MEASURE) \
{ \
auto start = std::chrono::system_clock::now(); \
CODE_TO_MEASURE \
auto end = std::chrono::system_clock::now(); \
std::chrono::duration<double> difference = end - start; \
std::cout << difference.count(); \
}
Here are eight snippets of code. The first three have error codes as NaNs, then there are a pair of snippets for boost::optional, then there are two more for tuples, and, last but not least, an exception throwing.
Checking error codes as enum
Result_or_code sqrt_or_not(double x) {
if (std::isnan(x))
return ECode::INPUT_IS_NAN;
if (std::isinf(x))
return ECode::INPUT_IS_INFINITE;
if(x < 0.)
return ECode::INPUT_IS_NEGATIVE;
return std::sqrt(x);
}
...
MEASURE(
for(double x = -1024.; x <= 1024.; x += 1./65536.) {
auto root = sqrt_or_not(x);
if(root >= ECode::ERROR)
++errors;
else {
++results;
total += root;
}
}
);
Time: 0.2 s Return type size: 8 B
Checking error codes as NaN
Result_or_code sqrt_or_not(double x) {
if (std::isnan(x))
return ECode::INPUT_IS_NAN;
if (std::isinf(x))
return ECode::INPUT_IS_INFINITE;
if(x < 0.)
return ECode::INPUT_IS_NEGATIVE;
return std::sqrt(x);
}
...
MEASURE(
for(double x = -1024.; x <= 1024.; x += 1./65536.) {
auto root = sqrt_or_not(x);
if(std::isnan(root))
++errors;
else {
++results;
total += root;
}
}
);
Time: 0.3 s Return type size: 8 B
Storing error codes in NaN
Result_or_code sqrt_or_not(double x) {
if (std::isnan(x))
return ECode::INPUT_IS_NAN;
if (std::isinf(x))
return ECode::INPUT_IS_INFINITE;
if(x < 0.)
return ECode::INPUT_IS_NEGATIVE;
return std::sqrt(x);
}
...
MEASURE(
std::vector<Result_or_code> results_or_codes;
for(double x = -1024.; x <= 1024.; x += 1./65536.) {
results_or_codes.push_back(sqrt_or_not(x));
if(results_or_codes.back() >= ECode::ERROR)
++errors;
else {
++results;
}
}
);
Time: 1.3 s Return type size: 8 B
Checking optional result
boost::optional<double> optional_sqrt(double x) {
if (std::isnan(x) || std::isinf(x) || x < 0)
return boost::optional<double>();
return boost::optional<double>(std::sqrt(x));
}
...
MEASURE(
for(double x = -1024.; x <= 1024.; x += 1./65536.) {
auto optional_root = optional_sqrt(x);
if(!optional_root)
++errors;
else {
++results;
total += *optional_root;
}
}
);
Time: 0.2 s Return type size: 16 B
Storing optional results
boost::optional<double> optional_sqrt(double x) {
if (std::isnan(x) || std::isinf(x) || x < 0)
return boost::optional<double>();
return boost::optional<double>(std::sqrt(x));
}
...
MEASURE(
for(double x = -1024.; x <= 1024.; x += 1./65536.) {
optional_results.push_back(optional_sqrt(x));
if(!optional_results.back())
++errors;
else {
++results;
total += *optional_results.back();
}
}
);
Time: 2.9 s Return type size: 16 B
Checking tuple of code and value
std::tuple<ECode, double> code_and_sqrt(double x) {
if (std::isnan(x))
return std::make_tuple(ECode::INPUT_IS_NAN, 0.);
if (std::isinf(x))
return std::make_tuple(ECode::INPUT_IS_INFINITE, 0.);
if(x < 0)
return std::make_tuple(ECode::INPUT_IS_NEGATIVE, 0.);
return std::make_tuple(ECode::OK, std::sqrt(x));
}
...
MEASURE(
for(double x = -1024.; x <= 1024.; x += 1./65536.) {
auto code_and_root = code_and_sqrt(x);
if(std::get<0>(code_and_root) != ECode::OK)
++errors;
else {
++results;
total += std::get<1>(code_and_root);
}
}
);