Multi-buffering improves log system performance

Source: Internet
Author: User

No matter what project must have the log system, so a high-performance log system is inevitable.
This article introduces a simple multi-buffer log system which is implemented by C++11, and compares the water for reference ^_^

Topics:
    • Log system and importance
    • Single-buffered log system model and defects
    • Multi-buffered buffer introduction and advantages
    • Multi-buffer defects
    • Design and analysis of buffer class
    • Design and analysis of logger class
log system and importance:

Log information is important for a good project, because no matter how good software can cause crashes or anomalies, the log system can play its part.
Quickly navigate to the wrong location and error content, or view recent information.
In general, a log system will rank the log, such as the info log (some of the user's actions, etc.), error log (System crash or exception), fail log (an operation failed), and so on.
Because the log system is very important, it will appear in every corner of our program, so a good log system is very important, there are many good implementations such as C + + LOG4, described below is a very simple log system according to their own ideas.

single-buffered log system model and defects

最简单的日志系统就是单缓冲或者无缓冲的。

No buffering:
Unbuffered is the simplest, in a need to output log information to the location of output information to the file and write to the disk, but note that now the program is generally concurrent execution, multi-process or multi-threaded write file we want to lock.
This is less efficient, such as you have 20 threads running, each output log must first grab the lock and then output, and output to the disk itself is very slow, so not only the output log inefficient, it is more likely to affect the operation of the program (will block the program, because the log output is ubiquitous).

Single buffering:
Single buffer is we open up a fixed size of space, each log output is first output to the buffer, wait until the buffer is full in a flush to disk, so that compared the buffer efficiency is improved a bit, not each time to output to disk files, until a certain number and then flush to disk. However, each time the thread or process that is output to the log file is locked, there is a lock-in process, and the efficiency is not high.

模型如下

As can be seen, each write buffer is the process of locking and blocking, so the efficiency is relatively low, relatively unbuffered, only reduce the number of disk IO. However, the program will still block when disk IO
磁盘IO依旧是瓶颈

Multi-Buffered buffer introduction

Since the single-block buffer does not meet our requirements, efficiency is still relatively low, then we can try to select multiple buffers to achieve. The program only focuses on the current buffer, and the rest of the buffers are handed to the background thread for processing.

模型如下

The current buffer for our program writes buffered buffer, the standby buffer buffers for us to advance the buffer, when the current curbuf buffer full swap buffer. Such as

In practice we use pointers to manipulate (the std::shared_ptr I use in the code, only with the Exchange pointer), after the exchange is complete as

At this point, we can wake up the background thread to handle the full buffer, the current buffer swap is empty, the program can continue to write the current buffer without blocking because of disk IO.

If the program writes the log very quickly, we can open a large buffer, or set the current buffer to two blocks, the standby buffer can be set to multiple blocks, when actually writing the program, because I use list<std::shared_ptr> this structure to save, when the standby buffer is not enough, will create a piece, and then the list will automatically push_back, So slowly the program will achieve the most appropriate buffer size.

The advantage is obvious, our program just write, everything is done by the background thread, does not block on the disk IO.

multi-buffer defects

Multi-buffer design is flawed, compared to the single buffer is to avoid the time-consuming operation of disk IO, but if the program write log volume is very large, each write Curbuf current buffer must first grab the lock, the visibility of the low efficiency, waiting for the time of the lock is very expensive. Multiple threads or processes operate one or two blocks of buffering, and the granularity of the lock is very large. We can try to reduce the particle size of the lock.
The solution can refer to the Java concurrenthashmap principle, Concurrenthashmap is built in a number of barrels, each hash to a different bucket, lock lock only the corresponding bucket, then equal to reduce the size of the lock, blocking in the lock frequency is greatly reduced.

Such as

The current program buffer opens up multiple, similar to a plurality of barrels, lock only the corresponding bucket can be, reduce the granularity of the lock
This is the time to change space, and then if there is no such a large demand above solutions can be resolved.

design and analysis of buffer class

The buffer class is designed to refer to buffer in the Netty, a buffer, three tags are readable position readable, writable position writable, capacity capacity.
In fact, readable and writable positions are 0,capacity for capacity size.
Write buffer when writable move, read buffer readable move, writable <= capacity.
The buffer I used vector<char> , referring to the Aboutspeaker predecessor's Muduo, used on the vector<char> one hand its internal and array is the same, and secondly we can also use the vector characteristics to manage it. The buffer class is relatively simple

classbuffer{/ * Initialize default size * /    Staticsize_t Initializesize; Public:/ * constructor, initialize buffer, and set the readable writable location * /        ExplicitBuffer (size_t buffersize = initializesize): Readable (0), Writable (0)        {/ * Open up the size in advance * /Buffer.resize (buffersize); }/ * Returns the buffer's capacity * /size_t capacity () {returnBuffer.capacity (); }/ * Returns the size of the buffer * /size_t Size () {returnwritable; }/ * Set Size * /        voidSetSize (void) {readable =0; writable =0; }/ * Add data to buffer * /        voidAppendConst Char* MESG,intLen) {strncpy(Writepoint (), MESG, Len);        writable + = Len; }/ * Return buffer Available size * /size_t avail () {returnCapacity ()-writable; }Private:/ * Returns a pointer to a readable position * /        Char* Readpoint () {return&buffer[readable]; }/ * Returns the pointer to a writable position * /        Char* Writepoint () {return&buffer[writable]; }/ * Return to readable location * /size_t readaddr () {returnreadable; }/ * Return to writable location * /size_t writeaddr () {returnwritable; }Private:STD:: vector<char>Buffer        size_t readable; size_t writable;};
design and analysis of logger class

The logger class my implementation follows the multi-buffering model just described.
Curbuf is a piece, the spare buffer is two blocks, and can be adaptively changed.

classlogger{ Public:/ * Create a log class instance * /        Static STD::shared_ptr<Logger> Setlogger ();Static STD::shared_ptr<Logger> Setlogger (size_t bufSize);/ * Get Log class instance * /        Static STD::shared_ptr<Logger> GetLogger ();/ * Output log information to the specified file in format * /        Static voidLogStream (Const Char* MESG,intLen);Private:/ * shared_ptr smart pointer Management log class * /        Static STD::shared_ptr<Logger> MyLogger;/ * Current buffer * /        Static STD::shared_ptr<Buffer> Curbuf;/ * List management fallback buffer * /        Static STD:: List<std::shared_ptr<Buffer>> buflist;/ * Returns a usable buffer in the standby buffer * /        Static STD::shared_ptr<Buffer> useful ();/ * Condition variable * /        Static STD:: Condition_variable readablebuf;/ * Number of buffer to be processed by the background thread * /        Static intReadablenum;/ * Mutex lock * /        Static STD:: Mutex mutex;/ * Background thread * /        Static STD:: Thread readthread;/ * Thread Execution function * /        Static voidThreadFunc ();Static voidFunc ();/ * Condition Variable condition * /        Static BOOLIshave ();};

It can be seen from the above code that the current buffer and the alternate buffer are managed with smart pointers, and we don't have to worry about resource release, because the current buffer and the standby buffer are exchanged very quickly for pointers.

初始化函数

std::shared_ptr<logger>Logger::Setlogger () {if(MyLogger = = nullptr) {/ * Create a log class * /MyLogger =std:: Move(std::make_shared<logger>());/ * Create current buffer * /Curbuf =std::make_shared<buffer>();/ * Create two spare buffer * /Buflist.resize (2); (*buflist.begin()) =std::make_shared<buffer>(); (* (++buflist.begin())) =std::make_shared<buffer>(); }returnMyLogger;}

are managed by smart pointers.

useful类,返回一个可用的备用Buffer

STD::shared_ptr<Buffer> logger::useful () {Autoiter = Buflist.begin ();/ * Query If there is a usable buffer * /     for(; ITER! = Buflist.end (); ++iter) {if((*iter)->size () = =0)        { Break; }    }/ * Does not exist create a new buffer and return * /    if(iter = Buflist.end ()) {STD::shared_ptr<Buffer> p =STD::make_shared<buffer> ();/ * Use right values uniformly to improve efficiency * /Buflist.push_back (STD:: Move (p));returnP }return*iter;}

This is an adaptive process, and as the program runs, it returns the size of the fit.

logStream写日志类

voidLogger::logstream (Const Char* MESG,intLen) {/* locked, use Unique_lock to combine with condition_variable condition variable */    STD::unique_lock<STD::mutex> Locker (mutex);/* Determine if the current buffer is full, full, and the standby buffer is swapped with the new */    if(Curbuf->avail () > Len)    {Curbuf->append (MESG, Len); }Else{/ * Get a backup buffer * /        AutoUsebuf = useful ();/ * Exchange pointers * /Curbuf.swap (USEBUF);/ * Number of readable buffers increased * /++readablenum;/ * Wake up blocking Background thread * /Readablebuf.notify_one (); }}

线程主要执行函数

voidLogger:: Func () {STD::unique_lock<STD::mutex> Locker (mutex); Auto iter = Buflist.begin ();/ * If the standby buffer does not have data readable, blocking waits for wake-up * /    if(Readablenum = =0) {readablebuf.wait (Locker,Logger:: Ishave); }/ * Find buffer with data not empty * /     for(; ITER! = Buflist.end (); ++iter) {if((*iter)->size ()! = 0)             Break; }/* If not found at the end, no data readable */if(iter = Buflist.end ()){return; }Else{/* Write full buffers to file */int FD=Open("1.txt", O_RDWR | O_append, 00700);if(FD < 0){perror("open error\n");Exit(1); }Write(FD, Iter->get (), (*iter)->capacity ()); /* Empty buffer */bzero(Iter->get (), (*iter)->capacity ()); /* Return to positionreadableAndwritable*/(*iter)-SetSize ();/ * Number of readable buffers minus 1 * /--readablenum; }}

Just a simple implementation, if there is a better plan or error also hope to point out, thank you ~

Finish

Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

Multi-buffering improves log system performance

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.