比较日志性能:Glog、Spdlog 和 ofstream 在不同硬件上的表现(推荐Spdlog)
日志记录是嵌入式系统中关键的调试和监控手段。在选择合适的日志库时,性能往往是重要的考量因素。本文对比了 Spdlog 和 Glog 这两个流行的日志库,以及标准的 ofstream 流在嵌入式平台上的性能表现。测试包括同步和异步模式,以全面评估它们在高频日志记录场景下的性能表现。
文章目录
比较日志性能:Glog、Spdlog 和 ofstream 在不同硬件上的表现
1. 引言
日志记录是嵌入式系统中关键的调试和监控手段。在选择合适的日志库时,性能往往是重要的考量因素。本文对比了 Spdlog 和 Glog 这两个流行的日志库,以及标准的 ofstream 流在嵌入式平台上的性能表现。测试包括同步和异步模式,以全面评估它们在高频日志记录场景下的性能表现。
2. 测试简介
为了比较 Spdlog 和 Glog 在嵌入式 ARM64 平台上的性能表现,设计了测试代码,分别测试了以下场景下的日志记录耗时:
- ofstream 同步文件写
- Spdlog 同步日志记录
- Spdlog 异步日志记录
- Glog 同步日志记录
测试使用 10 万条日志数据,每个日志记录 200 字节的消息。测试平台包括桌面电脑(Ubuntu 18.04)和树莓派 5(Ubuntu 24.04)。
3. 硬件配置
桌面电脑(Ubuntu 18.04)
- CPU:AMD Ryzen 5 3500U with Radeon Vega Mobile Gfx
- 内存:4.8G
- 操作系统:Ubuntu 18.04.4 LTS
- GCC 版本:7.5.0
- G++ 版本:7.5.0
树莓派 5(Ubuntu 24.04)
- CPU:Cortex-A76
- 内存:7.8Gi
- 操作系统:Ubuntu 24.04 LTS
- GCC 版本:13.2.0
- G++ 版本:13.2.0
4. 测试结果
桌面电脑(Ubuntu 18.04)
ofstream logging: 0.0719351 seconds
spdlog sync logging: 0.0720132 seconds
spdlog async logging: 0.160619 seconds
glog logging: 0.297419 seconds
树莓派 5(Ubuntu 24.04)
ofstream logging: 0.390023 seconds
spdlog sync logging: 0.0649371 seconds
spdlog async logging: 0.1108 seconds
glog logging: 0.953905 seconds
测试结果表明,在同步日志记录场景下,ofstream 方式的日志记录耗时最短,其次是 Spdlog 同步日志记录,Glog 同步日志记录耗时最长。Spdlog 异步日志记录耗时比 Spdlog 同步日志记录高出约 2 倍。
5. 详细分析
-
ofstream
:作为标准的文件写操作,其性能表现优异,但在嵌入式系统中,频繁的文件读写操作可能会对系统整体性能造成一定影响。 -
spdlog
:在同步场景下通过原子操作和锁机制保证线程安全,相比 Glog 的单线程模型,性能有所提升。Spdlog 的异步日志记录采用多线程和消息队列的方式,将日志记录操作与主线程分离,有效降低了对主线程的影响,但也带来了一定的性能开销。 -
glog
:同步日志记录存在性能问题,可能与其单线程模型以及日志格式化等因素有关。Glog 的异步日志记录并未进行测试,因为标准 Glog 库不直接支持异步日志记录。
6. 实现代码
#include <glog/logging.h>
#include <spdlog/async.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/spdlog.h>
#include <chrono>
#include <cstdio> // for std::remove
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
const int LOG_COUNT = 100000;
const int MESSAGE_SIZE = 200;
std::vector<std::string> generate_messages(int count, int size) {
std::vector<std::string> messages(count);
std::string sample_message(size, 'x');
for (int i = 0; i < count; ++i) {
messages[i] = "This is a test log message number " + std::to_string(i) + ": " + sample_message;
}
return messages;
}
void initialize_logging() {
FLAGS_log_dir = ".";
FLAGS_logtostderr = false;
FLAGS_alsologtostderr = false;
google::InitGoogleLogging("glog_async_worker");
spdlog::init_thread_pool(8192, 1); // Assuming 1 background thread for the example
}
void shutdown_logging() {
google::ShutdownGoogleLogging();
spdlog::shutdown();
}
void prepare_log_files() {
// Create or truncate the log files
std::ofstream("ofstream_log.txt").close();
std::ofstream("spdlog_sync.txt").close();
std::ofstream("spdlog_async.txt").close();
std::ofstream("glog_example.INFO").close();
}
void test_logging(const std::string& test_name, std::function<void(const std::vector<std::string>&)> log_function,
const std::vector<std::string>& messages) {
auto start = std::chrono::high_resolution_clock::now();
log_function(messages);
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::cout << test_name << ": " << elapsed.count() << " seconds" << std::endl;
}
void test_ofstream(const std::vector<std::string>& messages) {
std::ofstream log_file("ofstream_log.txt");
for (const auto& message : messages) {
log_file << message << "\n";
}
}
void test_spdlog_sync(const std::vector<std::string>& messages) {
auto logger = spdlog::basic_logger_mt("spdlog_sync", "spdlog_sync.txt");
for (const auto& message : messages) {
logger->info(message);
}
}
void test_spdlog_async(const std::vector<std::string>& messages) {
auto logger = spdlog::basic_logger_mt<spdlog::async_factory>("spdlog_async", "spdlog_async.txt");
for (const auto& message : messages) {
logger->info(message);
}
}
void test_glog(const std::vector<std::string>& messages) {
for (const auto& message : messages) {
LOG(INFO) << message;
}
}
int main() {
initialize_logging();
auto messages = generate_messages(LOG_COUNT, MESSAGE_SIZE);
prepare_log_files();
test_logging("ofstream logging", test_ofstream, messages);
prepare_log_files();
test_logging("spdlog sync logging", test_spdlog_sync, messages);
prepare_log_files();
test_logging("spdlog async logging", test_spdlog_async, messages);
prepare_log_files();
test_logging("glog logging", test_glog, messages);
shutdown_logging();
return 0;
}
7. 其他
7.1 Spdlog 的同步和异步支持
- Spdlog 明确支持同步和异步两种日志记录模式。
- 在异步模式下,Spdlog 使用单独的线程处理日志写入操作,减少了日志记录对主线程的影响,特别是在高并发写入日志的场景下,能显著提高性能。
7.2 Glog 的同步和异步
- Glog 原生设计为同步日志库,即日志记录操作在调用线程中执行。
- 同步模式在高日志输出频率的场景中,可能会影响应用程序的性能,尤其是对响应时间有严格要求的场景。
- 标准 Glog 库本身不直接支持异步日志记录。
- 社区中存在一些非官方的扩展,如 G2log 和 G3log,它们基于 C++11 编写,提供了异步日志功能。
7.3 Glog 的多线程和多进程写入顺序问题
- Glog 的设计没有明确保证多线程或多进程同时写入同一日志文件时,日志的顺序能严格按照写入时的时间顺序。
- 高并发写入情况下,不同线程或进程的日志条目可能因操作系统调度、磁盘 I/O 缓冲等原因交错写入,导致日志顺序混乱。
8. 结论
在嵌入式 ARM64 平台上:
- 追求极致性能:建议使用
ofstream
进行日志记录。 - 兼顾性能和线程安全:
spdlog
是一个不错的选择。 - 高性能要求场景:
glog
在性能方面表现不佳,不推荐用于对性能要求较高的嵌入式系统。
8.1 测试版本差异
需要注意的是,不同平台、不同版本的操作系统及编译器版本可能会对测试结果产生影响。本文测试结果基于特定版本的硬件和软件配置,其他配置可能会有不同的性能表现。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)