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 f7bbf9b6 Improve async appender performance (#360)
f7bbf9b6 is described below

commit f7bbf9b6d3361f247f8ece759c432f0401c382d8
Author: Stephen Webb <[email protected]>
AuthorDate: Mon Mar 11 13:02:11 2024 +1100

    Improve async appender performance (#360)
    
    * A read-only dispatch thread improves performance by 30%
    
    * Use a ring buffer to pass events to the dispatch thread
    
    * compare_exchange_weak is said to yield better performance on some 
platforms
---
 src/main/cpp/asyncappender.cpp         | 76 ++++++++++++++++++++++++----------
 src/test/cpp/asyncappendertestcase.cpp | 61 ++++++++++++++++++++++++++-
 2 files changed, 115 insertions(+), 22 deletions(-)

diff --git a/src/main/cpp/asyncappender.cpp b/src/main/cpp/asyncappender.cpp
index 5c540cff..332ba533 100644
--- a/src/main/cpp/asyncappender.cpp
+++ b/src/main/cpp/asyncappender.cpp
@@ -26,6 +26,7 @@
 #include <log4cxx/helpers/threadutility.h>
 #include <log4cxx/private/appenderskeleton_priv.h>
 #include <thread>
+#include <atomic>
 #include <condition_variable>
 
 #if LOG4CXX_EVENTS_AT_EXIT
@@ -96,10 +97,13 @@ typedef std::map<LogString, DiscardSummary> DiscardMap;
 }
 #endif
 
+static const int CACHE_LINE_SIZE = 128;
+
 struct AsyncAppender::AsyncAppenderPriv : public 
AppenderSkeleton::AppenderSkeletonPrivate
 {
        AsyncAppenderPriv() :
                AppenderSkeletonPrivate(),
+               buffer(DEFAULT_BUFFER_SIZE),
                bufferSize(DEFAULT_BUFFER_SIZE),
                appenders(pool),
                dispatcher(),
@@ -108,6 +112,9 @@ struct AsyncAppender::AsyncAppenderPriv : public 
AppenderSkeleton::AppenderSkele
 #if LOG4CXX_EVENTS_AT_EXIT
                , atExitRegistryRaii([this]{atExitActivated();})
 #endif
+               , eventCount(0)
+               , dispatchedCount(0)
+               , commitCount(0)
        {
        }
 
@@ -140,7 +147,7 @@ struct AsyncAppender::AsyncAppenderPriv : public 
AppenderSkeleton::AppenderSkele
        DiscardMap discardMap;
 
        /**
-        * Buffer size.
+        * The maximum number of undispatched events.
        */
        int bufferSize;
 
@@ -167,6 +174,21 @@ struct AsyncAppender::AsyncAppenderPriv : public 
AppenderSkeleton::AppenderSkele
 #if LOG4CXX_EVENTS_AT_EXIT
        helpers::AtExitRegistry::Raii atExitRegistryRaii;
 #endif
+
+       /**
+        * Used to calculate the buffer position at which to store the next 
event.
+       */
+       alignas(CACHE_LINE_SIZE) std::atomic<size_t> eventCount;
+
+       /**
+        * Used to calculate the buffer position from which to extract the next 
event.
+       */
+       alignas(CACHE_LINE_SIZE) std::atomic<size_t> dispatchedCount;
+
+       /**
+        * Used to communicate to the dispatch thread when an event is 
committed in buffer.
+       */
+       alignas(CACHE_LINE_SIZE) std::atomic<size_t> commitCount;
 };
 
 
@@ -233,31 +255,38 @@ void AsyncAppender::append(const spi::LoggingEventPtr& 
event, Pool& p)
        // Get a copy of this thread's MDC.
        event->getMDCCopy();
 
-       std::unique_lock<std::mutex> lock(priv->bufferMutex);
        if (!priv->dispatcher.joinable())
        {
-               priv->dispatcher = ThreadUtility::instance()->createThread( 
LOG4CXX_STR("AsyncAppender"), &AsyncAppender::dispatch, this );
-               priv->buffer.reserve(priv->bufferSize);
+               std::unique_lock<std::mutex> lock(priv->bufferMutex);
+               if (!priv->dispatcher.joinable())
+                       priv->dispatcher = 
ThreadUtility::instance()->createThread( LOG4CXX_STR("AsyncAppender"), 
&AsyncAppender::dispatch, this );
        }
        while (true)
        {
-               size_t previousSize = priv->buffer.size();
-
-               if (previousSize < (size_t)priv->bufferSize)
+               auto pendingCount = priv->eventCount - priv->dispatchedCount;
+               if (0 <= pendingCount && pendingCount < priv->bufferSize)
                {
-                       priv->buffer.push_back(event);
-
-                       if (previousSize == 0)
+                       // Claim a slot in the ring buffer
+                       auto oldEventCount = priv->eventCount++;
+                       auto index = oldEventCount % priv->buffer.size();
+                       // Wait for a free slot
+                       while (priv->bufferSize <= oldEventCount - 
priv->dispatchedCount)
+                               ;
+                       // Write to the ring buffer
+                       priv->buffer[index] = event;
+                       // Notify the dispatch thread that an event has been 
added
+                       auto savedEventCount = oldEventCount;
+                       while 
(!priv->commitCount.compare_exchange_weak(oldEventCount, oldEventCount + 1, 
std::memory_order_release))
                        {
-                               priv->bufferNotEmpty.notify_all();
+                                oldEventCount = savedEventCount;
                        }
-
+                       priv->bufferNotEmpty.notify_all();
                        break;
                }
-
                //
-               //   Following code is only reachable if buffer is full
+               //   Following code is only reachable if buffer is full or 
eventCount has overflowed
                //
+               std::unique_lock<std::mutex> lock(priv->bufferMutex);
                //
                //   if blocking and thread is not already interrupted
                //      and not the dispatcher then
@@ -270,7 +299,7 @@ void AsyncAppender::append(const spi::LoggingEventPtr& 
event, Pool& p)
                {
                        priv->bufferNotFull.wait(lock, [this]()
                        {
-                               return priv->buffer.empty();
+                               return priv->eventCount - priv->dispatchedCount 
< priv->bufferSize;
                        });
                        discard = false;
                }
@@ -374,6 +403,7 @@ void AsyncAppender::setBufferSize(int size)
 
        std::lock_guard<std::mutex> lock(priv->bufferMutex);
        priv->bufferSize = (size < 1) ? 1 : size;
+       priv->buffer.resize(priv->bufferSize);
        priv->bufferNotFull.notify_all();
 }
 
@@ -455,26 +485,30 @@ void AsyncAppender::dispatch()
 
        while (isActive)
        {
+               Pool p;
+               LoggingEventList events;
+               events.reserve(priv->bufferSize);
                //
                //   process events after lock on buffer is released.
                //
-               Pool p;
-               LoggingEventList events;
                {
                        std::unique_lock<std::mutex> lock(priv->bufferMutex);
                        priv->bufferNotEmpty.wait(lock, [this]() -> bool
-                               { return 0 < priv->buffer.size() || 
priv->closed; }
+                               { return priv->dispatchedCount != 
priv->commitCount || priv->closed; }
                        );
                        isActive = !priv->closed;
 
-                       events = std::move(priv->buffer);
+                       while (events.size() < priv->bufferSize && 
priv->dispatchedCount != priv->commitCount)
+                       {
+                               auto index = priv->dispatchedCount % 
priv->buffer.size();
+                               events.push_back(priv->buffer[index]);
+                               ++priv->dispatchedCount;
+                       }
                        for (auto discardItem : priv->discardMap)
                        {
                                
events.push_back(discardItem.second.createEvent(p));
                        }
 
-                       priv->buffer.clear();
-                       priv->buffer.reserve(priv->bufferSize);
                        priv->discardMap.clear();
                        priv->bufferNotFull.notify_all();
                }
diff --git a/src/test/cpp/asyncappendertestcase.cpp 
b/src/test/cpp/asyncappendertestcase.cpp
index 57610200..8a6fccc5 100644
--- a/src/test/cpp/asyncappendertestcase.cpp
+++ b/src/test/cpp/asyncappendertestcase.cpp
@@ -124,6 +124,7 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                LOGUNIT_TEST(closeTest);
                LOGUNIT_TEST(test2);
                LOGUNIT_TEST(testEventFlush);
+               LOGUNIT_TEST(testMultiThread);
                LOGUNIT_TEST(testBadAppender);
                LOGUNIT_TEST(testBufferOverflowBehavior);
 #if LOG4CXX_HAS_DOMCONFIGURATOR
@@ -215,13 +216,71 @@ class AsyncAppenderTestCase : public 
AppenderSkeletonTestCase
                        }
 
                        asyncAppender->close();
-                       root->debug(LOG4CXX_TEST_STR("m2"));
+                       root->debug(LOG4CXX_STR("m2"));
 
                        const std::vector<spi::LoggingEventPtr>& v = 
vectorAppender->getVector();
                        LOGUNIT_ASSERT_EQUAL(LEN, v.size());
+                       Pool p;
+                       for (size_t i = 0; i < LEN; i++)
+                       {
+                               LogString m(LOG4CXX_STR("message"));
+                               StringHelper::toString(i, p, m);
+                               LOGUNIT_ASSERT(v[i]->getMessage() == m);
+                       }
                        LOGUNIT_ASSERT_EQUAL(true, vectorAppender->isClosed());
                }
 
+
+               // this test checks all messages are delivered from multiple 
threads
+               void testMultiThread()
+               {
+                       size_t LEN = 2000; // Larger than default buffer size 
(128)
+                       int threadCount = 6;
+                       auto root = Logger::getRootLogger();
+                       auto vectorAppender = 
std::make_shared<VectorAppender>();
+                       auto asyncAppender = std::make_shared<AsyncAppender>();
+                       
asyncAppender->setName(LOG4CXX_STR("async-testMultiThread"));
+                       asyncAppender->addAppender(vectorAppender);
+                       root->addAppender(asyncAppender);
+
+                       std::vector<std::thread> threads;
+                       for ( int x = 0; x < threadCount; x++ )
+                       {
+                               std::thread thr([root, LEN]()
+                               {
+                                       for (size_t i = 0; i < LEN; i++)
+                                       {
+                                               LOG4CXX_DEBUG(root, "message" 
<< i);
+                                       }
+                               });
+                               threads.push_back( std::move(thr) );
+                       }
+
+                       for ( auto& thr : threads )
+                       {
+                               if ( thr.joinable() )
+                               {
+                                       thr.join();
+                               }
+                       }
+                       asyncAppender->close();
+
+                       const std::vector<spi::LoggingEventPtr>& v = 
vectorAppender->getVector();
+                       LOGUNIT_ASSERT_EQUAL(LEN*threadCount, v.size());
+                       std::vector<int> count(LEN, 0);
+                       for (auto m : v)
+                       {
+                               auto i = 
StringHelper::toInt(m->getMessage().substr(7));
+                               LOGUNIT_ASSERT(0 <= i);
+                               LOGUNIT_ASSERT(i < LEN);
+                               ++count[i];
+                       }
+                       for (size_t i = 0; i < LEN; i++)
+                       {
+                               LOGUNIT_ASSERT_EQUAL(count[i], threadCount);
+                       }
+               }
+
                /**
                 * Checks that async will switch a bad appender to another 
appender.
                 */

Reply via email to