7

I'm writing a library in C++ which needs to be as fast as reasonably possible. However, I'd also like to be able to provide logging in case a user (or me) needs to debug possible problems.

This library needs to be built, but also contains some header-only templated classes.

As I understand, usually logging in libraries is done by declaring, but not defining, a specific logging function, which is used to log messages inside the library. This function is then defined inside the user's program, which logs with whatever mechanism the user wants (or doesn't log at all).

As some logs are in very performance-sensitive places, I'm wondering whether I should be concerned that this approach would impact the library performances even when no logging is desired by the user at all. The library is usually linked statically.

An alternative idea I had would be to define logging as a macro, and define it both during the library build step and during the final program build step as an either empty macro or the desired build function. This would ensure that in case of no logging, nothing would remain in the final program. However, it would be more cumbersome for the user, and possibly more complicated as one would need to modify a library header file before building the library in order to correctly define the macro.

What are the best practices for this type of problem?

  • 1
    Hardly a complete answer but a useful compromise I find for some performance-critical areas is the idea of LOG_ONCE. That is, logging information is captured upon the first invocation of a piece of code, but not subsequent invocations, with the assumption that if it was able to execute properly one time, it can a second time, and a third time, and so on. That assumption is far from sound in all cases, but it can help in some cases (ex: making absolute certain that hardware instructions work on the user's hardware -- typically if it works the first time in those cases, it's fine). – user204677 Jan 28 '18 at 12:38
  • LOG_ONCE ends up storing a static boolean in the body of the function which gets set to true after first invocation. In that case you can avoid repeatedly logging on very performance-critical loop iterations, e.g., if you can make this kind of assumption that what works the first time will most likely work the second, and third, and so on. It also avoids spamming your log with a billion entries. – user204677 Jan 28 '18 at 12:41
4

When it comes to performance, there is only one best practice: measure! Don't guess around about how the performance might behave, those guesses very often wrong.

For the given case, you could find the most performance critical part of your lib, measure its performance when calling an "empty" logging function (maybe through a function pointer), then remove that function call and measure again. If that really shows up to result in a noteable performance difference, then the next step is to think about optimizations.

For example, the "logging function" should typically be implemented as an abstract class. Instead of just providing an empty logging function in case logging is disabled, the logger could also provide a public boolean function allowing a test if logging is enabled, so you can use it to optimize it this way:

   class MyLibraryClass
   {
      AbstractLogger *logger;

      void myLibraryFunction()
      {
          double x = doSomeQuickCalculation();
          // relies on "log" to do nothing when logging is disabled
          loggger->log("doSomeQuickCalculation result=" + formatDouble(x));
      }

      void myLibraryFunction_Optimized()
      {
          double x = doSomeQuickCalculation();

          // optimization: avoid string formatting for logging if not required, 
          // (in case the string formatting is really the bottleneck)
          if(loggger->isLoggingEnabled())
          {
              loggger->log("doSomeQuickCalculation result=" + formatDouble(x));
          } 
      }
  }

Only if it shows up - by measurement - that this is still too slow, it is time to think about a compile-time solution, which may utilize macros or templates.

Note that compile-time solutions make it harder to switch logging on and off at run time. In case that's not a problem, the standard way in modern C++ is probably to make the Logger a template parameter of the classes which needs logging, with an empty Logger as default, and rely one the optimizer that it will eliminate all calls to empty functions.

  • Since my project is a library, I don't have much control on who will compile and use the project, and on what machine they will do so. Thus I was looking for a general "idealish" approach that tries to be as platform independent as possible. If this was a one-off for myself I would definitely follow your approach though =) – Svalorzen Jan 28 '18 at 17:11
  • I though about your template suggestion, and it's definitely more refined than the macro idea. At the same time it would still require recompilation of the whole library and an eventual executable in order to turn on/off logging. However, I'm starting to believe that's the only reasonable option, since any other one which would be simpler for the user would require LTO, and relying on that is still kind of flaky... – Svalorzen Jan 28 '18 at 17:13
  • 1
    @Svalorzen: the only reasonable way is IMHO to follow what I wrote in my first paragraph. Measure on your machine, maybe with different optimization options, when it turns out deactivated logging will slow things down by less than 0,1% compared to "no logging at all", chances are high on other machines users will observe a similar ratio. – Doc Brown Jan 28 '18 at 18:21
  • @Svalorzen: see my edit, I added a simple optimization example for the case where logging needs to be switched at runtime. – Doc Brown Jan 29 '18 at 9:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.