C++11 Variadic Printf performance
The safe_printf
function by Andrei Alexandrescu is quite clever, but unfortunately has serious limitations:
Each argument is processed twice, once to check its validity and the second time to format it with
printf
. The check can be disabled in release mode to avoid overhead, but this seriously undermines safety.It doesn't work with positional arguments.
There is a number of ways how you can improve on it:
Don't always forward formatting to
printf
once the argument type is established. For example, this benchmark shows that it's possible to implement integer formatting which is up to 6.7 times faster thansprintf
.To implement positional arguments you need to store arguments in an array because they need to be addressed by an index.
Here's an example of how it can be done:
class Arg {
private:
enum Type { INT, DOUBLE };
Type type;
union {
int int_value;
double dbl_value;
} u;
public:
Arg(int value) : type(INT) { u.int_value = value; }
Arg(double value) : type(DOUBLE) { u.dbl_value = value; }
// other types
};
void do_safe_printf(const char *format, const Arg *args, size_t num_args) {
// here we can access arguments by index
}
template <typename... Args>
void safe_printf(const char *format, const Args&... args) {
Arg arg_array[] = {args...};
do_safe_printf(format, arg_array, sizeof...(Args));
}
Apart from supporting positional arguments, this approach will also minimize the code bloat as all the work is done by a single function do_safe_printf
while safe_printf
function template only places the arguments in an array.
These and other improvements have been implemented in the fmt library. According to benchmarks it is comparable or better both in speed and compiled code size to native printf
implementation
Disclaimer: I'm the author of this library.
At GoingNative2012, Andrei Alexandrescu gave an implementation of a variadic safe_printf()
. He uses a two-step approach. First, check the format specifiers; and second, normalize the arguments being passed. Because the implementation delegates to printf()
with checked formatters and arguments, there is no std::cout
in sight and hardly any runtime overhead (the exception path should not be taken often in regular code)
Code summary:
template <typename... Ts>
int safe_printf(const char * f, const Ts&... ts)
{
check_printf(f, normalizeArg(ts)...); // check format specifiers
return printf(f, normalizeArg(ts)...); // output with clean arguments
}
void check_printf(const char * f)
{
// checking is O(N) in length of format specifiers
for (; *f; ++f) {
if (*f != ’%’ || *++f == ’%’) continue;
throw Exc("Bad format");
}
}
// variadic check_print(const char*, T...) omitted, see slides
template <class T>
typename enable_if<is_integral<T>::value, long>::type
normalizeArg(T arg)
{
return arg;
}
// more overloads for float, T* and std::string omitted, see slides