-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Performance issue: Boost.Log does a huge work when filtering records #202
Comments
The library will have overhead compared to the raw That said, I'm interested in optimizing the library. If you have suggestions to this end, feel free to post a comment or PR. I don't think dynamic memory allocations can be completely eliminated, but may be reduced with pooling. Although if memory allocations are your bottleneck then a better solution would be to use a faster memory allocator library, such as tcmalloc. |
I have no doubts that Boost.Log is a great library with lots of features. We use only a small subset of them and of cause we realize what we pay for. But when I take the Surely I don't fully understand the architecture of the library but the first thing comes to my mind is applying filtres somewhere on an upper layer. Suppose the most usages look like this:
that is on a call side the severity is usually a compile-time constant. Given the logger itself is a singleton and configured only once it seems that it could take a severity requirement as a template parameter and apply filtering at comple-time. As a result, log writing operation could be omitted by the compiler which would make sense for cases with strong performance requirements. To sum up, I would say 80% users work with only 20% features. And it would be really cool to have a logger optimized for basic usages. |
Severity level is an attribute like any other, so in this regard you are using attributes. The problem is that attributes, and consequently the log record, is not quite lightweight and requires dynamic memory allocations to construct. Furthermore, the severity level type is erased in the process, so the filter has to extract the value from the record when it needs it. Type erasure is necessary to keep the library API stable across all its users (libraries, the application itself, different parts of the code base, etc.) regardless of the attributes they use. It is probably possible to optimize the cost of this machinery, but it cannot be eliminated completely. I'm not sure how significantly it can be reduced, since the only thing that comes to mind is to pool memory and apply small object optimizations.
Adding filtering in the logger is an interesting possibility, but you have to understand it has its own downsides. Obviously, the filter will only be applied to log records made through that logger. If you have multiple loggers, you will have to configure the filter in every one of them. Updating the filter during run time also becomes more difficult. Also, if you want to optimize away log record construction, the logger-specific filter cannot be a normal filter in the current library's terms, because a filter accepts an attribute value set, which is essentially a log record under construction. Which means this logger-specific filter would have to be fairly specialized (e.g. specifically a severity level threshold rather than an arbitrary function).
First, loggers are not singletons. They can be made as such, but from logger's perspective it doesn't matter whether it is a singleton or not. And actually it is preferred to not use logger singletons when possible as it has negative performance implications. Second, filters are rarely defined at compile time. Yes, there are use cases when one wants to build a binary stripped off logging, completely or in part, but this is not achieved though filtering. In fact, the library leaves the implementation of such use case to the user, as the most efficient way to do this is through preprocessor programming. Normally, filters are configured in run time, based on user's settings. |
That's why I suggest to think about making an exclusion for severity. Maybe there is a simple way to do it at least optionally for basic usages.
I agree that there can be more advanced cases when logging plays a key role. However, our usage is as simple as possible: we have only one logger and the only cause makes us dealing with attributes is a need to filter by severity. And it has always been enough for our needs for years. Probably we chose the wrong library and try shooting a mosquito with a bazooka 😄
I meant creating global loggers via
It's just an idea of optimization for some specific cases when the severity level is known at compile time. But I agree that it could be done on user side as a wrapper around the logger. BTW, as a hot-fix for now I've added a simple filter by adding a condition like this:
where |
I worked with Boost.Log library and encountered a huge performance impact in case when all content passed to the logger supposed to be filtered.
I reduced the logger to the simplest one with a console backend and the result remained the same.
Here is the sample code:
This simple program works for ~14 seconds.
But if I change the loop like this:
the program completes instantly as it supposed to. And it's what I expect the log library should do.
I profiled the program with dotTrace and here is the result
As you can see
filter
function takes only 95ms and the rest time goes to another stuff including memory allocations which I wouldn't expect to see.This problem makes Boost.Log unusable in cases when performance is a priority.
As a hot-fix one can filter records manually by introducing a special macro, for example.
But is it worth expecting this issue will be fixed?
The text was updated successfully, but these errors were encountered: