架构设计理念
spdlog的高性能源于其零分配、零拷贝、编译期优化的核心设计思想,下面详细分析各项技术实现。
1. 格式化系统优化
编译期格式字符串解析
// 编译期格式字符串检查与优化
template<typename... Args>
void log(level::level_enum lvl, format_string_t<Args...> fmt, Args&&... args) {
if (should_log(lvl)) {
details::log_msg log_msg(&name_, lvl,
fmt::format(fmt, std::forward<Args>(args)...));
sink_it_(log_msg);
}
}
// 关键:format_string_t在编译期验证格式字符串
底层原理:
- 利用C++20的
consteval或自定义编译期字符串处理 - 格式错误在编译期捕获,避免运行时开销
- 类型安全的参数传递,避免运行时类型检查
快速整数格式化
namespace details {
// 快速整数转字符串 - 避免使用std::to_string
template<typename T>
inline void format_int(T value, int width, fmt::basic_memory_buffer<char>& buffer) {
auto ptr = buffer.end();
const bool negative = value < 0;
// 手动处理整数转换,避免locale开销
do {
*--ptr = static_cast<char>('0' + std::abs(value % 10));
value /= 10;
} while (value != 0);
if (negative) {
*--ptr = '-';
}
// 处理宽度填充
// ...
}
}
2. 内存管理技术
栈上内存预分配
class log_msg {
private:
// 小字符串优化:栈上预分配内存
static constexpr size_t SMALL_BUF_SIZE = 256;
mutable fmt::basic_memory_buffer<char, SMALL_BUF_SIZE> formatted_;
string_view_t payload_;
public:
// 避免动态内存分配
template<typename... Args>
log_msg(source_loc loc, string_view_t logger_name,
level::level_enum lvl, string_view_t fmt, Args&&... args) {
// 使用栈缓冲区进行格式化
fmt::format_to(std::back_inserter(formatted_), fmt, std::forward<Args>(args)...);
payload_ = string_view_t(formatted_.data(), formatted_.size());
}
};
对象池技术
// 异步日志器的对象池
template<typename T>
class object_pool {
std::queue<std::unique_ptr<T>> pool_;
std::mutex pool_mutex_;
public:
std::unique_ptr<T> borrow() {
std::lock_guard<std::mutex> lock(pool_mutex_);
if (!pool_.empty()) {
auto obj = std::move(pool_.front());
pool_.pop();
return obj;
}
return std::unique_ptr<T>(new T());
}
void return_object(std::unique_ptr<T> obj) {
if (obj) {
obj->reset(); // 重置对象状态
std::lock_guard<std::mutex> lock(pool_mutex_);
pool_.push(std::move(obj));
}
}
};
3. 异步日志机制
无锁MPSC队列
class mpsc_queue {
struct node {
std::atomic<node*> next;
log_msg msg;
};
std::atomic<node*> head_;
std::atomic<node*> tail_;
public:
bool enqueue(log_msg&& msg) {
auto new_node = new node{nullptr, std::move(msg)};
auto prev_head = head_.exchange(new_node, std::memory_order_acq_rel);
prev_head->next.store(new_node, std::memory_order_release);
return true;
}
bool dequeue(log_msg& msg) {
auto tail = tail_.load(std::memory_order_acquire);
auto next = tail->next.load(std::memory_order_acquire);
if (next != nullptr) {
msg = std::move(next->msg);
tail_.store(next, std::memory_order_release);
delete tail;
return true;
}
return false;
}
};
批量写入优化
class async_logger : public logger {
private:
std::unique_ptr<thread_pool> tp_;
mpsc_queue queue_;
void worker_loop() {
std::vector<log_msg> batch;
batch.reserve(BATCH_SIZE);
while (active_ || !queue_.empty()) {
// 批量收集消息
log_msg msg;
while (batch.size() < BATCH_SIZE && queue_.dequeue(msg)) {
batch.push_back(std::move(msg));
}
if (!batch.empty()) {
// 批量写入,减少I/O调用
for (auto& msg : batch) {
sink_it_(msg);
}
batch.clear();
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
}
};
4. 编译期优化技术
条件编译消除
// 编译期根据日志级别消除代码
template<typename... Args>
void trace(format_string_t<Args...> fmt, Args&&... args) {
log(level::trace, fmt, std::forward<Args>(args)...);
}
// 编译器优化:在低优化级别下,以下代码会被完全消除
#define SPDLOG_LOGGER_TRACE(logger, ...) \
if ((logger)->should_log(spdlog::level::trace)) { \
(logger)->trace(__VA_ARGS__); \
}
// 或者使用C++17的if constexpr
template<level::level_enum Level, typename... Args>
void log_if_enabled(format_string_t<Args...> fmt, Args&&... args) {
if constexpr (Level >= SPDLOG_ACTIVE_LEVEL) {
if (should_log(Level)) {
// 实际日志处理
}
}
// 否则不生成任何代码
}
模板元编程优化
// 编译期字符串处理
template<size_t Size>
struct compile_time_string {
char data[Size]{};
constexpr compile_time_string(const char (&str)[Size]) {
std::copy_n(str, Size, data);
}
};
// 利用模板特化优化不同类型处理
template<typename T>
struct formatter {
static void format(const T& value, memory_buf& buf) {
fmt::format_to(std::back_inserter(buf), "{}", value);
}
};
// 对常见类型的特化优化
template<>
struct formatter<int> {
static void format(int value, memory_buf& buf) {
details::format_int(value, 0, buf); // 使用快速整数格式化
}
};
5. I/O优化技术
写入缓冲与批量刷新
class file_sink : public base_sink<std::mutex> {
private:
FILE* file_;
std::unique_ptr<char[]> buffer_;
size_t buffer_size_;
size_t buffer_pos_;
protected:
void sink_it_(const details::log_msg& msg) override {
// 缓冲写入
if (buffer_pos_ + msg.payload.size() >= buffer_size_) {
flush_();
}
std::memcpy(buffer_.get() + buffer_pos_, msg.payload.data(), msg.payload.size());
buffer_pos_ += msg.payload.size();
}
void flush_() override {
if (buffer_pos_ > 0) {
fwrite(buffer_.get(), 1, buffer_pos_, file_);
buffer_pos_ = 0;
}
fflush(file_);
}
};
时间戳缓存
class cached_strftime {
private:
std::string cached_time_;
std::chrono::seconds last_update_{0};
std::mutex mutex_;
public:
const std::string& get_time(const std::string& format) {
auto now = std::chrono::system_clock::now();
auto now_seconds = std::chrono::time_point_cast<std::chrono::seconds>(now);
if (now_seconds != last_update_) {
std::lock_guard<std::mutex> lock(mutex_);
if (now_seconds != last_update_) {
auto time_t = std::chrono::system_clock::to_time_t(now);
std::tm tm;
localtime_r(&time_t, &tm);
char buffer[64];
std::strftime(buffer, sizeof(buffer), format.c_str(), &tm);
cached_time_ = buffer;
last_update_ = now_seconds;
}
}
return cached_time_;
}
};
6. 性能基准测试对比
以下是spdlog与其它日志库的性能对比(消息数/秒):
| 场景 | spdlog | glog | log4cxx | 提升倍数 |
|---|---|---|---|---|
| 同步文件写入 | 1,200,000 | 800,000 | 450,000 | 1.5-2.7x |
| 异步文件写入 | 4,500,000 | 2,100,000 | 1,800,000 | 2.1-2.5x |
| 控制台输出 | 850,000 | 600,000 | 350,000 | 1.4-2.4x |
| 空调用(编译期消除) | ~0开销 | 中等开销 | 高开销 | 极大优势 |
7. 关键技术原理总结
零分配原理
// 通过以下技术实现零动态内存分配:
// 1. 小字符串优化(SSO)
class small_buf {
char* data_;
size_t size_;
union {
char stack_buf[64]; // 栈上缓冲区
char* heap_buf; // 堆上缓冲区(大字符串时)
};
};
// 2. 内存预分配
static thread_local fmt::basic_memory_buffer<char, 512> thread_local_buf;
零拷贝原理
// 使用string_view避免字符串拷贝
void process_log(string_view_t message) {
// 不拷贝字符串,只传递指针和长度
sink_->log(message);
}
// 移动语义优化
log_msg(log_msg&& other) noexcept
: payload_(std::move(other.payload_))
, formatted_(std::move(other.formatted_)) {
}
编译期计算原理
// 利用constexpr进行计算
constexpr size_t count_placeholders(const char* fmt) {
size_t count = 0;
for (; *fmt; ++fmt) {
if (*fmt == '{' && *(fmt + 1) != '{') {
++count;
}
}
return count;
}
// 编译期格式验证
static_assert(count_placeholders("Hello {}") == 1,
"Format string placeholder count mismatch");
8. 实际应用示例
// 高性能使用模式
#include <spdlog/spdlog.h>
#include <spdlog/async.h>
#include <spdlog/sinks/rotating_file_sink.h>
void setup_high_performance_logging() {
// 1. 配置异步日志器
auto async_file = spdlog::rotating_logger_mt<spdlog::async_factory>(
"async_file", "logs/app.log", 1024 * 1024 * 100, 5);
// 2. 设置高性能参数
spdlog::init_thread_pool(8192, 1); // 大队列,单后台线程
async_file->set_level(spdlog::level::info);
// 3. 使用模式优化
for (int i = 0; i < 1000000; ++i) {
// 编译期格式字符串,避免运行时解析
SPDLOG_LOGGER_INFO(async_file, "Processing item: {}, value: {}", i, i * 2);
}
}
总结
spdlog的高性能源于多重技术的协同作用:
- 编译期优化:格式字符串验证、条件代码消除
- 内存管理:栈分配、对象池、小字符串优化
- 并发模型:无锁队列、异步批量处理
- 算法优化:快速格式化、缓存策略
- I/O优化:缓冲写入、批量刷新
这些技术使得spdlog在保持简洁API的同时,提供了接近极限的日志性能,特别适合高性能要求的应用场景。