自学内容网 自学内容网

[C++]——同步异步日志系统(8)

一、项目目录结构

example:如何使用项目的具体样例
extend:扩展代码
logs:项目的各个模块,项目源码
pratice:练习代码,项目前置学习的代码
bench:用来进行性能测试
整理完成后,目录结构如下(注意:扩展代码和具体样例代码一定要进行测试)

在这里插入图片描述

一、功能测试

// 测试代码
#include "../logs/logslearn.h"

//进行功能测试
void test_log(const std::string &name){
   INFO( "%s", "测试开始");
    logslearn::Logger::ptr logger=logslearn::LoggerManager::getInstance().getLogger(name);
    //测试日志打印
    logger->debug( "%s", "测试日志");
    logger->info( "%s", "测试日志");
    logger->warn( "%s", "测试日志");
    logger->error( "%s", "测试日志");
    logger->fatal("%s", "测试日志");
    INFO( "%s", "测试结束");
}
int main()
{
    std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::GlobalLoggerBuilder());
    //建造者构建零部件
    builder->buildLoggerName("async_logger");
    builder->buildLoggerLevel(logslearn::loglevel::value::DEBUG);
    builder->buildLoggerFormatter("[%d{%H:%M:%S}][%t][%c][%p]%m%n");
    builder->buildLoggerType(logslearn::LoggerType::LOGGER_SYNC);
    builder->buildSink<logslearn::StdoutSink>();                                 // 标准输出落地
    builder->buildSink<logslearn::FileSink>("./logfile/async.log"); // 文件落地方式
    builder->buildSink<logslearn::RoolBySizeSink>("./logfile/roll-", 1024 * 1024); // 滚动文件落地方式
    builder->build();
    
    test_log("async_logger");

    return 0;
}

打印结果:异步方式
在这里插入图片描述
打印结果:同步方式
在这里插入图片描述

测试⼀个⽇志器中包含有所有的落地⽅向,观察是否每个⽅向都正常落地,分别测试同步⽅式和异步⽅式落地后数据是否正常。(我们的代码进行功能测试都很正常)

二、性能测试

下⾯对⽇志系统做⼀个性能测试,测试⼀下平均每秒能打印多少条⽇志消息到⽂件。

测试三要素:

1.测试环境
2.测试方法
3.测试结果

测试工具的编写:

1.可以控制写日志线程数量
2.可以控制写日志的总数量
分别对于同步日志器 & 异步日志器进行各自的性能测试,
需要测试单写日志线程的性能 需要测试多写日志线程的性能

实现:

封装一个接口,传入日志器名称,线程数量,日志数量,单条日志大小 在接口内,创建指定数量的线程,各自负责一部分日志的输出,在输出之前计时开始,在输出完毕后计时结束。
所耗时间=结束时间-起始时间
每秒输出量 =日志数量/总耗时
每秒输出大小 =日志数量*单条日志大小/总耗时
注意:异步日志输出这里,我们启动非安全式,纯内存写入(不去考虑实际落地的时间)

测试环境:

CPU:Intel® Core™ i5-9300HF CPU @ 2.40GHz
RAM:只读存储器 16.0 GB
ROM:随机存取存储器 327GB—SSD
OS:ubuntu-20.04

2.1 项目性能测试工具实现

  1. 在对项目测试之前,需要编写测试工具,测试工具的具体实现放到bench里。
#include "../logs/logslearn.h"
// c++11提供的时间特性
#include <chrono>
// 设计性能测试功能
// logger_name日志器的名字,thr_count线程数的个数,msg_counr日志消息的总条数,len日志消息的长度
void bench(const std::string &logger_name, size_t thr_count, size_t msg_count, size_t msg_len)
{
    // 1.获取日志器
    logslearn::Logger::ptr logger = logslearn::getLogger(logger_name);
    // 如果没找到日志器就返回空
    if (logger.get() == nullptr)
    {
        return;
    }
    // 2.组织指定长度的日志消息
    // 留一个字符,放换行符
    std::string msg(msg_len - 1, 'A');
    // 3.创建指定数量的线程
    // 创建一个存放线程的数组
    std::vector<std::thread> threads;
    // 存放每个线程打印日志需要消耗的时间
    std::vector<double> cost_arry(thr_count);
    // 每个线程需要打印的日志数=总日志数/线程数
    size_t msg_per_thr = msg_count / thr_count;
    // 创建指定数量的线程,push_back()构造然后拷贝,插入元素到末尾,emplace_back()构造并插入元素到末尾
    // 打印测试日志总条数,总大小
    std::cout << "\t测试日志:" << msg_count << "条,\t总大小:" << (msg_count * msg_len) / 1024 << "KB\n";
    for (int i = 0; i < thr_count; i++)
    {
        // 插入元素时用lambad表达式
        threads.emplace_back([&, i]()
                             {
            // 4.线程函数内部开始计时,高精度获得当前的系统时间
            auto start=std::chrono::high_resolution_clock::now();
            // 5.开始循环写日志
            for(int i=0;i<msg_per_thr;i++){
            //打印日志
            logger->fatal("%s",msg.c_str());
            }
            // 6.线程函数内部结束计时,高精度获得当前的系统时间
            auto end=std::chrono::high_resolution_clock::now();
            //每个线程需要的时间
            std::chrono::duration<double> cost=end-start;
            cost_arry[i]=cost.count();
            std::cout<<"\t线程"<<i<<":"<<"\t输出数量日志:"<<msg_per_thr<<",\t耗时:"<<cost.count()<<"s"<<std::endl; });
    }
    // 要记住,创建线程那么就要等待线程退出
    for (int i = 0; i < thr_count; i++)
    {
        threads[i].join();
    }
    // 7.计算总耗时:在多线程中,每个线程都会耗时间,但是线程是并发运行处理的,因此耗时最高的线程就是总时间。
    // 创建的子线程已经全部退出了
    double max_cost = cost_arry[0];
    for (int i = 0; i < thr_count; i++)
    {
        max_cost = max_cost < cost_arry[i] ? cost_arry[i] : max_cost;
    }
    // 每秒输出日志数量=总日志数/总消耗时间
    size_t msg_per_sec = msg_count / max_cost;
    // 每秒输出日志大小=总日志的长度/总消耗时间
    size_t size_per_sec = (msg_count * msg_len) / (max_cost*1024);
    // 8.进行输出打印
    std::cout << "\t总耗时:" << max_cost << "s\n";
    std::cout << "\t每秒输出日志数量:" << msg_per_sec << "条\n";
    std::cout << "\t每秒输出日志大小:" << size_per_sec << "KB\n";
}

2. 项目性能测试

因为我们做的是性能测试,所以每一次只测试一种结果。
同步日志器性能受磁盘影响、

1. 同步日志器单线程

// 测试同步日志器
void sync_bench()
{
    // 创建一个同步日志器建造者
    std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::GlobalLoggerBuilder());
    // 建造者构建零部件
    builder->buildLoggerName("sync_logger");
    builder->buildLoggerFormatter("%m%n");
    builder->buildLoggerType(logslearn::LoggerType::LOGGER_SYNC);
    builder->buildSink<logslearn::FileSink>("./logfile/sync.log"); // 文件落地方式
    // builder->buildSink<logslearn::StdoutSink>();                                   // 标准输出落地
    // builder->buildSink<logslearn::RoolBySizeSink>("./logfile/roll-sync-", 1024 * 1024); // 滚动文件落地方式
    builder->build();
    //  测试单线程情况
    bench("sync_logger", 1, 1000000, 100);
}
int main()
{// 同步日志器单线程
    sync_bench();
    return 0;
}

测试结果:
在这里插入图片描述
2. 同步日志器多线程

// 测试同步日志器
void sync_bench()
{
    // 创建一个同步日志器建造者
    std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::GlobalLoggerBuilder());
    // 建造者构建零部件
    builder->buildLoggerName("sync_logger");
    builder->buildLoggerFormatter("%m%n");
    builder->buildLoggerType(logslearn::LoggerType::LOGGER_SYNC);
    builder->buildSink<logslearn::FileSink>("./logfile/sync.log"); // 文件落地方式
    // builder->buildSink<logslearn::StdoutSink>();                                   // 标准输出落地
    // builder->buildSink<logslearn::RoolBySizeSink>("./logfile/roll-sync-", 1024 * 1024); // 滚动文件落地方式
    builder->build();
     bench("sync_logger",3,1000000,100);
}
int main()
{
    sync_bench();
    return 0;
}

在这里插入图片描述
为什么多线程比单线程耗时慢?
单线程没有锁冲突,他是一个加锁写日志,加锁写日志过程,是一个串行接口
因为多线程写日志是多个线程加锁写日志的过程,会存在锁冲突问题。
我的电脑磁盘性能比较好,如果是更大量的日志,就是单线程耗时慢
比如日志达到2000000条,结果如下:
在这里插入图片描述
3. 异步日志器单线程

本质:多线程和单线程都是向内存写数据要考虑cpu和内存的性能,不去考虑磁盘落地的情况。

// 测试异步日志器
void async_bench()
{
// 创建一个同步日志器建造者
    std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::GlobalLoggerBuilder());
    // 建造者构建零部件
    builder->buildLoggerName("async_logger");
    builder->buildLoggerFormatter("%m%n");
    builder->buildLoggerType(logslearn::LoggerType::LOGGER_ASYNC);
    builder->buildEnabeUnSafeAsync();//开启非安全模式————主要是为了将实际落地时间排除在外
    builder->buildSink<logslearn::FileSink>("./logfile/async.log"); // 文件落地方式
    // builder->buildSink<logslearn::StdoutSink>();                                   // 标准输出落地
    // builder->buildSink<logslearn::RoolBySizeSink>("./logfile/roll-sync-", 1024 * 1024); // 滚动文件落地方式
    builder->build();
    //  测试单线程情况
    bench("async_logger", 1, 1000000, 100);

}
int main()
{
    async_bench();
    return 0;
}

测试:
在这里插入图片描述

4. 异步日志器多线程

// 测试异步日志器
void async_bench()
{
// 创建一个同步日志器建造者
    std::unique_ptr<logslearn::LoggerBuilder> builder(new logslearn::GlobalLoggerBuilder());
    // 建造者构建零部件
    builder->buildLoggerName("async_logger");
    builder->buildLoggerFormatter("%m%n");
    builder->buildLoggerType(logslearn::LoggerType::LOGGER_ASYNC);
    builder->buildEnabeUnSafeAsync();//开启非安全模式————主要是为了将实际落地时间排除在外
    builder->buildSink<logslearn::FileSink>("./logfile/async.log"); // 文件落地方式
    // builder->buildSink<logslearn::StdoutSink>();                                   // 标准输出落地
    // builder->buildSink<logslearn::RoolBySizeSink>("./logfile/roll-sync-", 1024 * 1024); // 滚动文件落地方式
    builder->build();
     // 测试多线程情况
    bench("async_logger",3,1000000,100);
}
int main()
{
    async_bench();
    return 0;
}

测试:
在这里插入图片描述
我们把日志的数量提高到2000000条
结果如下:
在这里插入图片描述
cpu和内存的性能越好打印日志越快,日志的多少和线程数无关(不会因为落地而阻塞)。

总结:

能够通过上边的测试看出来,⼀些情况:
在单线程情况下,异步效率看起来还没有同步⾼,这是因为现在的IO操作,用户态都是从缓冲区进入到缓冲区,因此我们当前测试⽤例看起来的同步其实⼤多时候也是在操作内存,只有在缓冲区满了才会涉及到阻塞写磁盘操作,⽽异步单线程效率看起来低,也有⼀个很重要的原因就是单线程同步操作中不存在锁冲突,⽽单线程异步⽇志操作存在⼤量的锁冲突,因此性能也会有⼀定的降低。 但是,我们也要看到限制同步⽇志效率的最⼤原因是磁盘性能,打⽇志的线程多少并⽆明显区别,线程多了反⽽会降低,因为增加了磁盘的读写争抢,⽽对于异步⽇志的限制,并⾮磁盘的性能,⽽是cpu和内存的处理性能,打⽇志并不会因为落地⽽阻塞,因此在多线程打⽇志的情况下性能有了显著的提⾼。


原文地址:https://blog.csdn.net/plj521/article/details/140293338

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!