This is an automated email from the ASF dual-hosted git repository.

swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git


The following commit(s) were added to refs/heads/master by this push:
     new 0389045b Include sample performance measurement data in web-site 
information (#539)
0389045b is described below

commit 0389045bac82e04b70810155143838f1964dc161
Author: Stephen Webb <[email protected]>
AuthorDate: Thu Sep 18 12:11:54 2025 +1000

    Include sample performance measurement data in web-site information (#539)
    
    * Quantify the significance of using buffered output
    
    * Add an explaination for lock contention when logging from multiple threads
---
 src/main/cpp/appenderskeleton.cpp           | 19 ++++----
 src/main/include/log4cxx/appenderskeleton.h | 23 ++++++---
 src/site/markdown/performance.md            | 75 ++++++++++++++++++++++++-----
 3 files changed, 90 insertions(+), 27 deletions(-)

diff --git a/src/main/cpp/appenderskeleton.cpp 
b/src/main/cpp/appenderskeleton.cpp
index 76eb3dfd..f44b8ced 100644
--- a/src/main/cpp/appenderskeleton.cpp
+++ b/src/main/cpp/appenderskeleton.cpp
@@ -95,40 +95,39 @@ void AppenderSkeleton::doAppend(const spi::LoggingEventPtr& 
event, Pool& pool1)
        doAppendImpl(event, pool1);
 }
 
-void AppenderSkeleton::doAppendImpl(const spi::LoggingEventPtr& event, Pool& 
pool1)
+void AppenderSkeleton::doAppendImpl(const spi::LoggingEventPtr& event, Pool& 
pool)
 {
        if (m_priv->closed)
        {
                LogLog::warn(LOG4CXX_STR("Attempted to append to closed 
appender named [")
                        + m_priv->name + LOG4CXX_STR("]."));
-               return;
        }
-
-       if (!isAsSevereAsThreshold(event->getLevel()))
+       else if (isAsSevereAsThreshold(event->getLevel()) && isAccepted(event))
        {
-               return;
+               append(event, pool);
        }
+}
 
+bool AppenderSkeleton::isAccepted(const spi::LoggingEventPtr& event) const
+{
        FilterPtr f = m_priv->headFilter;
-
-
        while (f != 0)
        {
                switch (f->decide(event))
                {
                        case Filter::DENY:
-                               return;
+                               return false;
 
                        case Filter::ACCEPT:
                                f = nullptr;
                                break;
 
+                       default:
                        case Filter::NEUTRAL:
                                f = f->getNext();
                }
        }
-
-       append(event, pool1);
+       return true;
 }
 
 bool AppenderSkeleton::AppenderSkeletonPrivate::checkNotClosed()
diff --git a/src/main/include/log4cxx/appenderskeleton.h 
b/src/main/include/log4cxx/appenderskeleton.h
index 5679a3e8..3a47214d 100644
--- a/src/main/include/log4cxx/appenderskeleton.h
+++ b/src/main/include/log4cxx/appenderskeleton.h
@@ -44,13 +44,23 @@ class LOG4CXX_EXPORT AppenderSkeleton :
                AppenderSkeleton(LOG4CXX_PRIVATE_PTR(AppenderSkeletonPrivate) 
priv);
 
                /**
-               Subclasses of <code>AppenderSkeleton</code> should implement 
this
+               Subclasses of <code>AppenderSkeleton</code> must implement this
                method to perform actual logging. See also 
AppenderSkeleton::doAppend
                method.
                */
-               virtual void append(const spi::LoggingEventPtr& event, 
LOG4CXX_NS::helpers::Pool& p) = 0;
+               virtual void append(const spi::LoggingEventPtr& event, 
helpers::Pool& p) = 0;
 
-               void doAppendImpl(const spi::LoggingEventPtr& event, 
LOG4CXX_NS::helpers::Pool& pool);
+               /**
+               * Compare \c event level against the appender threshold and 
check that \c event is accepted.
+               * If \c event is accepted, delegate log output to the subclass 
implementation of
+               * the AppenderSkeleton#append method.
+               * */
+               void doAppendImpl(const spi::LoggingEventPtr& event, 
helpers::Pool& pool);
+
+               /**
+               * Does no attached filter deny \c event or does an attached 
filter accept \c event?
+               */
+               bool isAccepted(const spi::LoggingEventPtr& event) const;
 
        public:
                DECLARE_ABSTRACT_LOG4CXX_OBJECT(AppenderSkeleton)
@@ -141,9 +151,10 @@ class LOG4CXX_EXPORT AppenderSkeleton :
 
 
                /**
-               * This method performs threshold checks and invokes filters 
before
-               * delegating actual logging to the subclasses specific
-               * AppenderSkeleton#append method.
+               * Call AppenderSkeleton#doAppendImpl after acquiring a lock
+               * that prevents other threads from concurrently executing 
AppenderSkeleton#doAppendImpl.
+               *
+               * Reimplement this method in your appender if you use a 
different concurrency control technique.
                * */
                void doAppend(const spi::LoggingEventPtr& event, helpers::Pool& 
pool) override;
 
diff --git a/src/site/markdown/performance.md b/src/site/markdown/performance.md
index f64655c4..424876c1 100644
--- a/src/site/markdown/performance.md
+++ b/src/site/markdown/performance.md
@@ -21,23 +21,17 @@ Log4cxx Performance {#performance}
  limitations under the License.
 -->
 
-One important question with a logging library is: is it fast enough?  While
-Log4cxx may not be the fastest logging implementation, it is more than fast
+One important question with a logging library is: is it fast enough?
+While Log4cxx may not be the fastest logging implementation, it is more than 
fast
 enough for the vast majority of cases.
 
-In performance testing done on a developer's system(utilising an Intel
-i5-8400 processor and a virtual machine), it is possible for Log4cxx to handle
-over 2,000,000 messages/second in a single-threaded application.  Since
-Log4cxx is designed to be multithread-safe, logging from multiple threads makes
-this throughput much lower.  Delays in writing messages to disk can also
-greatly decrease performance, depending on how much data is being logged.
-
-If the logging of a particular level is disabled, performance can also be
-a lot better.  While Log4cxx can generate 2,000,000 log messages/second,
+While Log4cxx can generate 2,000,000 log messages/second,
 skipping a disabled logging request requires only a few nano-seconds,
 so application performance is not affected when
 logging requests are not removed from the release build.
 
+Benchmark testing shows MessageBuffer (i.e. std::stringstream) filling
+consumes the majority of CPU time when the logging request is not disabled.
 For the best performance, use the `LOG4CXX_[level]_FMT` series of macros,
 as the [{fmt}](https://fmt.dev/latest/index.html) library
 they use is significantly faster
@@ -65,3 +59,62 @@ The throughput tests may be built by
 specifying `BUILD_THROUGHPUT` with CMake when building Log4cxx.
 The benckmark tests require Google's 
[Benchmark](https://github.com/google/benchmark) library
 and may be built by specifying `BUILD_BENCHMARK_CHECKS` with CMake when 
building Log4cxx.
+
+The following table shows some timing tests using Google's benchmarking 
library.
+The "Time" column shows the average elapsed time, i.e real_accumulated_time / 
iteration_count.
+The "CPU" column is also an average, the cpu_accumulated_time / 
iteration_count.
+The "Iterations" column derivation is explained in [Google Benchmark 
documentation](https://google.github.io/benchmark/user_guide.html#runtime-and-reporting-considerations).
+
+       g++ (Ubuntu 11.4.0-1ubuntu1~22.04.2) 11.4.0
+       Run on (8 X 2328.61 MHz CPU s)
+       CPU Caches:
+         L1 Data 32 KiB (x4)
+         L1 Instruction 32 KiB (x4)
+         L2 Unified 256 KiB (x4)
+         L3 Unified 6144 KiB (x1)
+       Load Average: 0.07, 0.03, 0.01
+
+| Benchmark    | Time         | CPU          |   Iterations |
+| :----------- | -----------: | -----------: | -----------: |
+| Testing disabled logging request | 0.473 ns | 0.473 ns | 1000000000 |
+| Testing disabled logging request/threads:6 | 0.130 ns | 0.777 ns | 816416202 
|
+| Appending 5 char string using MessageBuffer, pattern: \%m\%n | 346 ns | 346 
ns | 2014476 |
+| Appending 5 char string using MessageBuffer, pattern: \%m\%n/threads:6 | 450 
ns | 2522 ns | 287886 |
+| Appending 49 char string using MessageBuffer, pattern: \%m\%n | 387 ns | 387 
ns | 1805660 |
+| Appending 49 char string using MessageBuffer, pattern: \%m\%n/threads:6 | 
490 ns | 2691 ns | 235698 |
+| Appending int value using MessageBuffer, pattern: \%m\%n | 538 ns | 538 ns | 
1301436 |
+| Appending int value using MessageBuffer, pattern: \%m\%n/threads:6 | 496 ns 
| 2775 ns | 238962 |
+| Appending int+float using MessageBuffer, pattern: \%m\%n | 921 ns | 921 ns | 
762785 |
+| Appending int+float using MessageBuffer, pattern: \%m\%n/threads:6 | 576 ns 
| 3324 ns | 201900 |
+| Appending int value using MessageBuffer, pattern: [\%d] \%m\%n | 566 ns | 
566 ns | 1233924 |
+| Appending int value using MessageBuffer, pattern: [\%d] [\%c] [\%p] \%m\%n | 
624 ns | 624 ns | 1122040 |
+| Appending 49 char string using FMT, pattern: \%m\%n | 360 ns | 360 ns | 
1945236 |
+| Appending 49 char string using FMT, pattern: \%m\%n/threads:6 | 489 ns | 
2666 ns | 248046 |
+| Appending int value using FMT, pattern: \%m\%n | 388 ns | 388 ns | 1804423 |
+| Appending int value using FMT, pattern: \%m\%n/threads:6 | 496 ns | 2720 ns 
| 253938 |
+| Appending int+float using FMT, pattern: \%m\%n | 519 ns | 519 ns | 1352503 |
+| Appending int+float using FMT, pattern: \%m\%n/threads:6 | 515 ns | 2900 ns 
| 229374 |
+| Async, Sending int+float using MessageBuffer | 1113 ns | 1113 ns | 633889 |
+| Async, Sending int+float using MessageBuffer/threads:6 | 545 ns | 3254 ns | 
211344 |
+| Logging int+float using MessageBuffer, pattern: \%d \%m\%n | 1079 ns | 1078 
ns | 641626 |
+| Logging int+float using MessageBuffer, pattern: \%d \%m\%n/threads:6 | 1036 
ns | 4715 ns | 144528 |
+| Logging int+float using MessageBuffer, JSON | 1446 ns | 1446 ns | 487967 |
+| Logging int+float using MessageBuffer, JSON/threads:6 | 2181 ns | 7102 ns | 
85848 |
+| Multiprocess logging int+float using MessageBuffer, pattern: \%d \%m\%n | 
3456 ns | 3456 ns | 203235 |
+
+-# The "Appending" benchmarks just format the message (using PatternLayout) 
then discard the result.
+-# The "Async" benchmarks test AsyncAppender throughput, with logging events 
discarded in the background thread.
+-# The "Logging" benchmarks write to a file using buffered output. Overhead is 
2-3 times more when not using buffered output.
+
+The above table shows that the overhead of an enabled logging request
+varies greatly with the message content.
+A single operations-per-second number is not meaningful.
+Most importantly note that [using buffered output](@ref 
log4cxx::FileAppender::setOption)
+reduces overhead more than any other detail.
+
+Note also that logging from multiple threads concurrently
+to a common appender does not increase throughput due to lock contention.
+To simplify the work of an appender implementator,
+the [doAppend method](@ref log4cxx::AppenderSkeleton::doAppend) currently 
prevents multiple threads
+concurrently entering [the append method](@ref 
log4cxx::AppenderSkeleton::append),
+which is the method required to be implemented by a concrete appender class.

Reply via email to