optimize performance of easylog (#343)

This commit is contained in:
qicosmos 2023-07-01 21:18:39 +08:00 committed by GitHub
parent f1b9722210
commit 72b2ef8340
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 235 additions and 131 deletions

View File

@ -19,7 +19,7 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <mutex>
#include <shared_mutex>
#include <string>
#include <string_view>
@ -32,12 +32,55 @@ struct QueueTraits : public moodycamel::ConcurrentQueueDefaultTraits {
static const size_t BLOCK_SIZE = 256;
};
constexpr char digits[10] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
template <size_t N, char c>
inline void to_int(int num, char *p, int &size) {
for (int i = 0; i < N; i++) {
p[--size] = digits[num % 10];
num = num / 10;
}
if constexpr (N != 4)
p[--size] = c;
}
inline char *get_time_str(const auto &now) {
static thread_local char buf[33];
static thread_local std::chrono::seconds last_sec_{};
std::chrono::system_clock::duration d = now.time_since_epoch();
std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(d);
auto mill_sec =
std::chrono::duration_cast<std::chrono::milliseconds>(d - s).count();
int size = 23;
if (last_sec_ == s) {
to_int<3, '.'>(mill_sec, buf, size);
return buf;
}
last_sec_ = s;
auto tm = std::chrono::system_clock::to_time_t(now);
auto gmt = localtime(&tm);
to_int<3, '.'>(mill_sec, buf, size);
to_int<2, ':'>(gmt->tm_sec, buf, size);
to_int<2, ':'>(gmt->tm_min, buf, size);
to_int<2, ' '>(gmt->tm_hour, buf, size);
to_int<2, '-'>(gmt->tm_mday, buf, size);
to_int<2, '-'>(gmt->tm_mon + 1, buf, size);
to_int<4, ' '>(gmt->tm_year + 1900, buf, size);
return buf;
}
class appender {
public:
appender(const std::string &filename, bool async, bool enable_console,
size_t max_file_size, size_t max_files, bool flush_every_time)
: flush_every_time_(flush_every_time),
enable_console_(enable_console),
appender() = default;
appender(const std::string &filename, bool async, size_t max_file_size,
size_t max_files, bool flush_every_time)
: has_init_(true),
flush_every_time_(flush_every_time),
max_file_size_(max_file_size) {
filename_ = filename;
max_files_ = (std::min)(max_files, static_cast<size_t>(1000));
@ -56,7 +99,7 @@ class appender {
}
if (queue_.size_approx() == 0) {
std::unique_lock<std::mutex> lock(mtx_);
std::unique_lock lock(que_mtx_);
cnd_.wait(lock, [&]() {
return queue_.size_approx() > 0 || stop_;
});
@ -74,45 +117,86 @@ class appender {
}
}
std::string_view get_tid_buf(unsigned int tid) {
static thread_local char buf[24];
static thread_local unsigned int last_tid;
static thread_local size_t last_len;
if (tid == last_tid) {
return {buf, last_len};
}
buf[0] = '[';
auto [ptr, ec] = std::to_chars(buf + 1, buf + 21, tid);
buf[22] = ']';
buf[23] = ' ';
last_len = ptr - buf;
buf[last_len++] = ']';
buf[last_len++] = ' ';
return {buf, last_len};
}
template <bool sync = false, bool enable_console = false>
void write_record(record_t &record) {
char buf[32];
size_t len = get_time_str(buf, record.get_time_point());
if constexpr (sync == true) {
std::shared_lock guard(mtx_);
if (max_files_ > 0 && file_size_ > max_file_size_ &&
static_cast<size_t>(-1) != file_size_) {
guard.unlock();
std::lock_guard lock{mtx_};
roll_log_files();
}
}
write_str({buf, len});
write_str({" ", 1});
write_str(severity_str(record.get_severity()));
write_str({" ", 1});
auto buf = get_time_str(record.get_time_point());
auto [ptr, ec] = std::to_chars(buf, buf + 32, record.get_tid());
buf[23] = ' ';
memcpy(buf + 24, severity_str(record.get_severity()).data(), 8);
buf[32] = ' ';
write_str({"[", 1});
write_str(std::string_view(buf, ptr - buf));
write_str({"] ", 2});
write_str(record.get_file_str());
write_str(record.get_message());
write_str({"\n", 1});
if constexpr (enable_console) {
add_color(record.get_severity());
}
if (enable_console_) {
write_str<enable_console>(std::string_view(buf, 33));
if constexpr (enable_console) {
clean_color(record.get_severity());
}
write_str<enable_console>(get_tid_buf(record.get_tid()));
write_str<enable_console>(record.get_file_str());
write_str<enable_console>(record.get_message());
if constexpr (enable_console) {
std::cout << std::flush;
}
}
void add_color(Severity severity) {
#if defined(_WIN32)
#else
if (severity == Severity::WARN)
std::cout << "\x1B[93m";
if (severity == Severity::ERROR)
std::cout << "\x1B[91m";
if (severity == Severity::CRITICAL)
std::cout << "\x1B[97m\x1B[41m";
#endif
}
void clean_color(Severity severity) {
#if defined(_WIN32)
#else
if (severity >= Severity::WARN)
std::cout << "\x1B[0m\x1B[0K";
#endif
}
void write(record_t &&r) {
queue_.enqueue(std::move(r));
cnd_.notify_all();
}
template <typename String>
void write(const String &str) {
std::lock_guard guard(mtx_);
if (max_files_ > 0 && file_size_ > max_file_size_ &&
static_cast<size_t>(-1) != file_size_) {
roll_log_files();
}
write_str(str);
}
void flush() {
std::lock_guard guard(mtx_);
if (file_.is_open()) {
@ -140,20 +224,6 @@ class appender {
write_thd_.join();
}
template <size_t N>
size_t get_time_str(char (&buf)[N], const auto &now) {
const auto nowAsTimeT = std::chrono::system_clock::to_time_t(now);
const auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch())
.count() %
1000;
size_t endpos =
std::strftime(buf, N, "%Y-%m-%d %H:%M:%S", std::localtime(&nowAsTimeT));
snprintf(buf + endpos, N - endpos, ".%03d", (int)nowMs);
return endpos + 4;
}
private:
void open_log_file() {
std::string filename = build_filename();
@ -214,22 +284,25 @@ class appender {
is_first_write_ = false;
}
template <bool enable_console>
void write_str(std::string_view str) {
if (file_.write(str.data(), str.size())) {
if (flush_every_time_) {
file_.flush();
}
if (has_init_) {
if (file_.write(str.data(), str.size())) {
if (flush_every_time_) {
file_.flush();
}
file_size_ += str.size();
file_size_ += str.size();
}
}
if (enable_console_) {
if constexpr (enable_console) {
std::cout << str;
}
}
bool has_init_ = false;
std::string filename_;
bool enable_console_ = false;
bool flush_every_time_;
size_t file_size_ = 0;
@ -237,9 +310,11 @@ class appender {
size_t max_files_ = 0;
bool is_first_write_ = true;
std::mutex mtx_;
std::shared_mutex mtx_;
std::ofstream file_;
std::mutex que_mtx_;
moodycamel::ConcurrentQueue<record_t, QueueTraits> queue_;
std::thread write_thd_;
std::condition_variable cnd_;

View File

@ -56,8 +56,8 @@ class logger {
void init(Severity min_severity, bool async, bool enable_console,
const std::string &filename, size_t max_file_size, size_t max_files,
bool flush_every_time) {
static appender appender(filename, async, enable_console, max_file_size,
max_files, flush_every_time);
static appender appender(filename, async, max_file_size, max_files,
flush_every_time);
async_ = async;
appender_ = &appender;
min_severity_ = min_severity;
@ -73,49 +73,28 @@ class logger {
void stop_async_log() { appender_->stop(); }
private:
logger() = default;
logger() {
static appender appender{};
appender_ = &appender;
}
logger(const logger &) = default;
void append_record(record_t record) { appender_->write(std::move(record)); }
void append_format(const record_t &record) {
char buf[32];
size_t len = appender_->get_time_str(buf, record.get_time_point());
#ifdef YLT_ENABLE_PMR
#if __has_include(<memory_resource>)
char arr[1024];
std::pmr::monotonic_buffer_resource resource(arr, 1024);
std::pmr::string str{&resource};
#endif
#else
std::string str;
#endif
str.append(buf, len).append(" ");
str.append(severity_str(record.get_severity())).append(" ");
auto [ptr, ec] = std::to_chars(buf, buf + 32, record.get_tid());
str.append("[").append(std::string_view(buf, ptr - buf)).append("] ");
str.append(record.get_file_str());
str.append(record.get_message()).append("\n");
void append_format(record_t &record) {
if (appender_) {
appender_->write(str);
}
if (enable_console_) {
std::cout << str;
std::cout << std::flush;
}
for (auto &fn : appenders_) {
fn(std::string_view(str));
if (enable_console_) {
appender_->write_record<true, true>(record);
}
else {
appender_->write_record<true, false>(record);
}
}
}
Severity min_severity_;
bool async_ = true;
bool async_ = false;
bool enable_console_ = true;
appender *appender_ = nullptr;
std::vector<std::function<void(std::string_view)>> appenders_;

View File

@ -64,7 +64,6 @@ namespace easylog {
inline std::string_view severity_str(Severity severity) {
switch (severity) {
#if defined(NDEBUG) || defined(_WIN32)
case Severity::TRACE:
return "TRACE ";
case Severity::DEBUG:
@ -77,22 +76,8 @@ inline std::string_view severity_str(Severity severity) {
return "ERROR ";
case Severity::CRITICAL:
return "CRITICAL";
#else
case Severity::TRACE:
return "TRACE ";
case Severity::DEBUG:
return "DEBUG ";
case Severity::INFO:
return "INFO ";
case Severity::WARN:
return "\x1B[93mWARNING\x1B[0m\x1B[0K ";
case Severity::ERROR:
return "\x1B[91mERROR\x1B[0m\x1B[0K ";
case Severity::CRITICAL:
return "\x1B[97m\x1B[41mCRITICAL\x1B[0m\x1B[0K";
#endif
default:
return "NONE";
return "NONE ";
}
}
@ -102,14 +87,19 @@ class record_t {
record_t(auto tm_point, Severity severity, std::string_view str)
: tm_point_(tm_point),
severity_(severity),
tid_(get_tid_impl()),
file_str_(str) {}
tid_(_get_tid()),
file_str_(str) {
ss_.reserve(64);
}
record_t(record_t &&) = default;
record_t &operator=(record_t &&) = default;
Severity get_severity() const { return severity_; }
const char *get_message() const { return ss_.data(); }
const char *get_message() {
ss_.push_back('\n');
return ss_.data();
}
std::string_view get_file_str() const { return file_str_; }
@ -175,6 +165,11 @@ class record_t {
ss_.append(buf);
}
unsigned int _get_tid() {
static thread_local unsigned int tid = get_tid_impl();
return tid;
}
unsigned int get_tid_impl() {
#ifdef _WIN32
return std::hash<std::thread::id>{}(std::this_thread::get_id());

View File

@ -1,9 +1,10 @@
find_package(glog QUIET)
find_package(spdlog QUIET)
if (UNIX)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
endif()
#include_directories(spdlog/include)
# include_directories(spdlog/include)
add_executable(easylog_benchmark
main.cpp
)
@ -15,3 +16,9 @@ if (glog_FOUND)
target_compile_definitions(easylog_benchmark PRIVATE HAVE_GLOG)
target_link_libraries(easylog_benchmark PRIVATE glog::glog)
endif ()
if (spdlog_FOUND)
message(STATUS "spdlog_FOUND: ${spdlog_FOUND}")
target_compile_definitions(easylog_benchmark PRIVATE HAVE_SPDLOG)
target_link_libraries(easylog_benchmark PRIVATE spdlog::spdlog_header_only)
endif ()

View File

@ -14,12 +14,17 @@
* limitations under the License.
*/
#include <exception>
#include <system_error>
#ifdef HAVE_GLOG
#include <glog/logging.h>
#endif
// #include <spdlog/sinks/base_sink.h>
// #include <spdlog/spdlog.h>
#ifdef HAVE_SPDLOG
#include <spdlog/sinks/base_sink.h>
#include <spdlog/sinks/basic_file_sink.h>
#include <spdlog/spdlog.h>
#endif
#include <filesystem>
@ -67,36 +72,79 @@ void test_glog() {
#endif
}
void test_easylog() {
std::filesystem::remove("long_name_of_easylog.txt");
easylog::init_log(Severity::DEBUG, "long_name_of_easylog.txt", true, false,
10 * 1024 * 1024, 1);
void test_easylog(std::string filename, int count, bool async) {
std::error_code ec;
std::filesystem::remove(filename, ec);
if (ec) {
std::cout << ec.message() << "\n";
}
easylog::init_log(Severity::DEBUG, filename, async, false, -1);
for (int i = 0; i < 10; i++) {
ScopedTimer timer("easylog");
for (int i = 0; i < 50000; i++)
for (int i = 0; i < count; i++)
ELOG(INFO) << "Hello logger: msg number " << i;
}
}
// void bench(int howmany, std::shared_ptr<spdlog::logger> log) {
// spdlog::drop(log->name());
#ifdef HAVE_SPDLOG
void bench(int howmany, std::shared_ptr<spdlog::logger> log) {
spdlog::drop(log->name());
// using std::chrono::duration;
// using std::chrono::duration_cast;
// using std::chrono::high_resolution_clock;
using std::chrono::duration;
using std::chrono::duration_cast;
using std::chrono::high_resolution_clock;
// for (int i = 0; i < 10; i++) {
// ScopedTimer timer("spdlog ");
// for (auto i = 0; i < howmany; ++i) {
// log->info("Hello logger: msg number {}", i);
// }
// }
// }
for (int i = 0; i < 10; i++) {
ScopedTimer timer("spdlog ");
for (auto i = 0; i < howmany; ++i) {
SPDLOG_LOGGER_INFO(log, "Hello logger: msg number {}", i);
}
}
}
void bench_mt(int howmany, std::shared_ptr<spdlog::logger> log,
size_t thread_count) {
using std::chrono::duration;
using std::chrono::duration_cast;
using std::chrono::high_resolution_clock;
spdlog::drop(log->name());
std::vector<std::thread> threads;
threads.reserve(thread_count);
{
ScopedTimer timer("spdlog ");
for (size_t t = 0; t < thread_count; ++t) {
threads.emplace_back([&]() {
for (int j = 0; j < howmany / static_cast<int>(thread_count); j++) {
SPDLOG_LOGGER_INFO(log, "Hello logger: msg number {}", j);
}
});
}
for (auto &t : threads) {
t.join();
};
}
}
#endif
int main() {
// auto basic_st = spdlog::basic_logger_st("basic_st", "basic_st.log", true);
// bench(50000, std::move(basic_st));
int count = 500000;
#ifdef HAVE_SPDLOG
spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l [%t] [%@] %v");
std::cout << "========test sync spdlog===========\n";
auto basic_st = spdlog::basic_logger_st("basic_st", "basic_st.log", true);
bench(count, std::move(basic_st));
std::cout << "========test async spdlog===========\n";
auto basic_mt = spdlog::basic_logger_st("basic_mt", "basic_mt.log", true);
bench_mt(count, std::move(basic_mt), 4);
#endif
test_glog();
test_easylog();
std::cout << "========test sync easylog===========\n";
test_easylog("easylog.txt", count, /*async =*/false);
std::cout << "========test async easylog===========\n";
test_easylog("async_easylog.txt", count, /*async =*/true);
}