C++11 Variadic Printf performance

The safe_printf function by Andrei Alexandrescu is quite clever, but unfortunately has serious limitations:

  1. 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.

  2. It doesn't work with positional arguments.

There is a number of ways how you can improve on it:

  1. 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 than sprintf.

  2. 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