【同步 / 异步 日志系统】--- 全局接口 性能测试
Welcome to 9ilk's Code World

(๑•́ ₃ •̀๑) 个人主页: 9ilk
(๑•́ ₃ •̀๑) 文章专栏: 项目
本篇博客主要是对日志系统的收尾,将日志器更好封装供用户使用,同时测试其性能。
全局接口
到目前为止,用户可以日志器建造者来创建不同的日志器,必要时,还可以使用全局日志器建造者,添加到管理器中管理,方便全局使用。但是在调用等级日志输出接口的时候,需要用户自己输入文件名__FILE__和行号__LINE__,这对用户来说有点不友好,最好封装成跟使用printf一样就好了,同时通过单例对象获取日志器是比较麻烦的。
提供全局接口&宏函数,对日志系统进行使用便捷性优化:
- 提供获取指定日志器的全局接口(避免用户自己操作单例对象)
- 使用宏函数对日志器的接口进行代理(代理模式)
- 提供宏函数直接提供默认日志器进行日志的标准输出打印(不用获取日志器了)
获取指定日志器的全局接口:
Logger::ptr getLogger(const string& name){return LoggerManager::getInstance().getLogger(name);}Logger::ptr getrootLogger(){return LoggerManager::getInstance().getrootLogger();}
使用宏函数对日志器的接口进行代理(代理模式):到时日志器调用等级输出接口时就会进行宏替换
//2.使用宏函数对日志器的接口进行代理#define debug(fmt,...) debug(__FILE__,__LINE__,fmt,##__VA__ARGS__)#define info(fmt,...) info(__FILE__,__LINE__,fmt,##__VA__ARGS__)#define warn(fmt,...) warn(__FILE__,__LINE__,fmt,##__VA__ARGS__)#define error(fmt,...) error(__FILE__,__LINE__,fmt,##__VA__ARGS__)#define fatal(fmt,...) fatal(__FILE__,__LINE__,fmt,##__VA__ARGS__)
此时可以简化使用成这样
Logger::ptr logger = getLogger("async_logger");logger->debug("测试日志"); --> 替换为 logger->debug(__FILE__,__LINE__,"测试日志")logger->info("测试日志");logger->warn("测试日志");logger->error("测试日志");logger->fatal("测试日志");
提供宏函数, 直接通过默认日志器进行日志的标准输出打印(复用上面的即可):
#define DEBUG(fmt,...) getrootLogger()->debug(fmt,##__VA_ARGS__)#define INFO(fmt,...) getrootLogger()->info(fmt,##__VA_ARGS__)#define WARN(fmt,...) getrootLogger()->warn(fmt,##__VA_ARGS__)#define ERROR(fmt,...) getrootLogger()->error(fmt,##__VA_ARGS__)#define FATAL(fmt,...) getrootLogger()->fatal(fmt,##__VA_ARGS__)
日志系统性能测试
下面对日志系统做一个性能测试,测试一下平均每秒能打印多少条日志消息到文件。
测试环境:
- CPU:Intel Xeon Platinum 8255C @2.50 GHz 2核心 / 2线程
- RAM: 2 GB
- ROM:50 GB SSD
- OS:Ubuntu 22.04.5 LTS (云服务器 2核 2GB内存)
主要测试方法:
- 耗时 = 结束时间 - 开始时间
- 每秒能打印日志数 = 打印日志条数 / 总的打印日志所消耗时间
- 每秒输出大小 = (日志数量*单条日志大小)/ 总耗时
主要测试要素:同步/异步 & 单线程/多线程
同步日志器 单/多线程测试:
void bench(const string& logger_name,size_t thread_count,size_t msg_count,size_t msg_len)
{//1.获取日志器Logger::ptr logger = getLogger(logger_name);if(logger.get() == nullptr)return;cout << "测试日志:" << msg_count << "条 " << "总大小:" << (msg_count*msg_len)/1024 << "KB" << endl;//2.组织指定长度的日志消息string msg(msg_len-1,'A'); //少一个字节是为了给末尾到时添加换行//3.创建指定数量的线程vector<thread> threads;vector<double> cost_array(thread_count);size_t msg_per_thread = msg_count / thread_count;//总日志数量 / 线程数量 = 每个线程要输出的日志数量for(int i = 0 ; i < thread_count ; i++){threads.emplace_back([&,i](){//4.线程内部开始计时auto start = std::chrono::high_resolution_clock::now();//5.开始循环写日志for(int j = 0 ; j < msg_per_thread ; j++)logger->fatal("%s",msg.c_str());//6.线程函数内部结束计时auto end = std::chrono::high_resolution_clock::now();std::chrono::duration<double> cost = end - start;cost_array[i] = cost.count();cout << "线程" << i << ": " << "\t输出数量: " << msg_per_thread << ",耗时: " << cost.count() << "s" << endl; });}for(int i = 0 ; i < thread_count ; i++)threads[i].join();//7.计算总耗时:在多线程中每个线程都会耗时,但线程是并发处理的,因此耗时最高的那个才是总时间double max_cost = cost_array[0];for(int i = 0 ; i < thread_count ; i ++)max_cost = max_cost < cost_array[i] ? cost_array[i] : max_cost;size_t msg_per_sec = msg_count / max_cost; //每秒输出日志数量size_t size_per_sec = (msg_count*msg_len) / (max_cost*1024); //每秒输出日志大小,单位KB//8.输出打印cout << "总耗时: " << max_cost << endl;cout << "每秒输出日志数量:" << msg_per_sec <<"条" << endl;cout << "每秒输出日志大小:" << size_per_sec << "KB" << endl;
}void sync_bench()
{unique_ptr<LoggerBuilder> builder(new GlobalLoggerBuilder());builder->bulidLoggerName("sync_logger");builder->buildFormatter("[%d{%H:%M:%S}][%p]%T%m%n");builder->bulidLoggerType(LoggerType::TYPE_SYNC);builder->buildSink<FileLogSink>("./logfile/sync.log");builder->build();std::cout << "------------------- 同步日志器 单线程 ------------------- " << std::endl;bench("sync_logger",1,1000000,100);std::cout << "------------------- 同步日志器 多线程 ------------------- " << std::endl;bench("sync_logger",3,1000000,100);
}
测试结果如下:

我们可以看到多线程的性能反而比单线程低,这是因为同步日志器多线程锁冲突严重,同时我们这里落地方向是写磁盘,磁盘性能已经达到上限了。
异步日志器下安全模式 vs 非安全模式:
void async_bench()
{unique_ptr<LoggerBuilder> builder(new GlobalLoggerBuilder());builder->bulidLoggerName("async_logger");builder->buildFormatter("[%d{%H:%M:%S}][%p]%T%m%n");builder->bulidLoggerType(LoggerType::TYPE_ASYNC);builder->buildSink<FileLogSink>("./logfile/async.log");// builder->buildEnableUnSafeAsync(); 安全模式builder->buildEnableUnSafeAsync(); //非安全模式builder->build();bench("async_logger",1,3000000,100);
}
测试结果如下:

我们可以看到非安全模式性能比安全模式要高,这是因为非安全模式减少了线程阻塞和同步开销,它不需要等待缓冲区的空间,不需要等待业务线程处理完交换有空闲时间再放数据,减少了I/O操作频率,但是会有丢数据的风险。
异步日志器非安全模式下单线程 vs 多线程:
void async_bench()
{unique_ptr<LoggerBuilder> builder(new GlobalLoggerBuilder());builder->bulidLoggerName("async_logger");builder->buildFormatter("[%d{%H:%M:%S}][%p]%T%m%n");builder->bulidLoggerType(LoggerType::TYPE_ASYNC);builder->buildSink<FileLogSink>("./logfile/async.log");builder->buildEnableUnSafeAsync();builder->build();std::cout << "----------------异步日志非安全模式单线程--------------------"<< std::endl;bench("async_logger",1,1000000,100);std::cout << "----------------异步日志非安全模式多线程--------------------"<< std::endl;bench("async_logger",3,1000000,100);
}
测试结果如下:

我们可以看到非安全模式下,没有同步和线程阻塞的开销,不会因为异步工作线程没处理完而阻塞,多线程异步只管多个线程往内存放,虽然锁冲突比单线程严重些,但是多线程所以单位时间处理的数据量要大写,因此锁冲突显得没那么严重了,此时更看重CPU和内存性能的上限。
