[英]Optimizing away log function calls depending on global log level
我是設計納米衛星的大學團隊的成員。 我們決定實現自己的(更精簡的)日志記錄庫,以代替Google的glog
, spdlog
, plog
和Boost::Log
。
由於明顯的限制,必須在編譯時優化全局日志級別以下的日志調用。
第一次嘗試是這樣的(單個頭文件):日志級別:
// We can set the global log level by defining one of these
#if defined LOGLEVEL_TRACE
#define LOGLEVEL Logger::trace
#elif defined LOGLEVEL_DEBUG
#define LOGLEVEL Logger::debug
#elif defined LOGLEVEL_INFO
[...]
#else
#define LOGLEVEL Logger::disabled
#endif
這些級別本身是enum
成員:
enum LogLevel {
trace = 32, // Very detailed information, useful for tracking the individual steps of an operation
debug = 64, // General debugging information
info = 96, // Noteworthy or periodical events
[...]
};
operator<<
重載以提高可讀性:
template <class T>
Logger::LogEntry& operator<<(Logger::LogEntry& entry, const T value) {
etl::to_string(value, entry.message, entry.format, true);
return entry;
}
宏constexpr
使編譯器執行我們想要的操作:
#define LOG(level)
if (Logger::isLogged(level)) \
if (Logger::LogEntry entry(level); true) \
entry
// [...]
static constexpr bool isLogged(LogLevelType level) {
return static_cast<LogLevelType>(LOGLEVEL) <= level;
}
此代碼有很多問題(有關更多信息,請參見MR討論 )。
enum LogLevel
調用運算符已添加,以返回新的static LogEntry
。 inline
d強制const
在-O1
傳播。 LogEntry
enums
。 inline
所有內容的nop
。 if constexpr
已添加if constexpr
語法。 以及更多內容(請參閱此處和下面的說明)。
那是當前代碼的(切碎)狀態:
#include <cstdint>
#include <string>
#define LOGLEVEL_EMERGENCY
#if defined LOGLEVEL_TRACE
#define LOGLEVEL Logger::trace
#elif defined LOGLEVEL_DEBUG
#define LOGLEVEL Logger::debug
#elif defined LOGLEVEL_INFO
#define LOGLEVEL Logger::info
#elif defined LOGLEVEL_NOTICE
#define LOGLEVEL Logger::notice
#elif defined LOGLEVEL_WARNING
#define LOGLEVEL Logger::warning
#elif defined LOGLEVEL_ERROR
#define LOGLEVEL Logger::error
#elif defined LOGLEVEL_EMERGENCY
#define LOGLEVEL Logger::emergency
#else
#define LOGLEVEL Logger::disabled
#endif
#define LOG_TRACE (LOG<Logger::trace>())
#define LOG_DEBUG (LOG<Logger::debug>())
#define LOG_INFO (LOG<Logger::info>())
#define LOG_NOTICE (LOG<Logger::notice>())
#define LOG_WARNING (LOG<Logger::warning>())
#define LOG_ERROR (LOG<Logger::error>())
#define LOG_EMERGENCY (LOG<Logger::emergency>())
class Logger {
public:
Logger() = delete;
typedef uint8_t LogLevelType;
enum LogLevel : LogLevelType {
trace = 32,
debug = 64,
info = 96,
notice = 128,
warning = 160,
error = 192,
emergency = 254,
disabled = 255,
};
enum class NoLogEntry {};
struct LogEntry {
std::string message = "";
LogLevel level;
explicit LogEntry(LogLevel level);
~LogEntry();
LogEntry(LogEntry const&) = delete;
template <class T>
Logger::LogEntry& operator<<(const T value) noexcept {
message.append(value);
return *this;
}
Logger::LogEntry& operator<<(const std::string& value);
};
static constexpr bool isLogged(LogLevelType level) {
return static_cast<LogLevelType>(LOGLEVEL) <= level;
}
static void log(LogLevel level, std::string & message);
};
template <Logger::LogLevel level>
constexpr inline auto LOG() {
if constexpr (Logger::isLogged(level)) {
return Logger::LogEntry(level);
} else {
return Logger::NoLogEntry();
}
};
template <typename T>
[[maybe_unused]] constexpr Logger::NoLogEntry operator<<(const Logger::NoLogEntry noLogEntry, T value) {
return noLogEntry;
}
int main() {
LOG_NOTICE << "I am getting optimized away!";
LOG_EMERGENCY << "I am not getting optimized away, and rightfully so";
return 0;
}
正如你可以看到如編譯器資源管理器中, LOG_NOTICE
是越來越在優化掉-O1
。
你有什么建議嗎?
我遇到了這個SO問題 ,但是由於我選擇了自定義的,從零開始的日志記錄庫實現,因此它無關緊要。
老實說,我不相信全局模式,例如全局記錄器或單例模式。 我也不認為強烈需要這樣的編譯時間記錄器優化。 只要不需要編寫消息(在不需要時將其轉換("The ratio is {}:{}", 20, 50)
"The ratio is 20:50"
("The ratio is {}:{}", 20, 50)
轉換為"The ratio is 20:50"
),您就不會受到性能的影響由於記錄問題。
實際上,我想說的是您不應在編譯時對其進行優化,否則您將無法重新配置就無法配置記錄器。 如果您必須重新編譯整個程序只是為了調整記錄器配置,這將是一個調試地獄,因為它可能會由於重新編譯而改變行為,並且在您想要在沒有經過用戶設計的用戶設備上測試程序的情況下,情況尤其糟糕有開發者環境。
我的建議是使用一個可配置的記錄器類,該類的實例傳遞給項目的所有主要組件。 並為其提供簡單的消息功能來處理日志消息。 我寫了一個示例(只需將消息級別變成枚舉之類)。
#include <iostream>
#include <mutex>
#include <memory>
#include <sstream>
using namespace std;
class CGlobalLogger
{
public:
void Print(const std::string& str)
{
std::lock_guard g(m_mutex);
std::cout << str;
}
int GetMessageLevel()
{
return m_messageLevel;
}
private:
int m_messageLevel = 5;
std::mutex m_mutex;
};
void Print(std::stringstream& ss){};
template<typename... Args, typename Arg>
void Print(std::stringstream& ss, Arg&& arg, Args&&... args)
{
ss << arg;
Print(ss, std::forward<Args>(args)...);
}
class CLocalLogger
{
public:
CLocalLogger() = default;
CLocalLogger(std::shared_ptr<CGlobalLogger> global_logger, std::string local_name)
{
m_log = std::move(global_logger);
m_local_name = std::move(local_name);
}
template<typename... Args>
void Log(int messageLevel, Args&&... args)
{
if(messageLevel < m_log->GetMessageLevel())
{
return;
}
std::stringstream ss;
Print(ss, m_local_name, std::forward<Args>(args)...);
m_log->Print(ss.str());
}
private:
std::string m_local_name;
std::shared_ptr<CGlobalLogger> m_log;
};
int main()
{
CLocalLogger logger(std::make_shared<CGlobalLogger>(), "main: ");
logger.Log(7, "Hello World! ", 5, " ", 7.);
logger.Log(3, "I won't be printed... ", 5, "abs");
return 0;
}
另外,您可以操縱本地記錄器,以便確定誰打印了消息,這在某些情況下對於全局記錄器來說可能很難。 假設您有一個普通的class A
並且里面有一個印刷品。 所以,你給你的每個實例class A
本地記錄儀具有獨特的本地名字,那么你可以告訴他們的情況下class A
在日志中打印消息,而不是剛才說的class A
打印出來。
PS有一些低級別的日志記錄,在任何發行版本中都不應出現,但通常您可以將其注釋掉,除非您處理的是大型項目,在這種情況下,您會將它們隱藏在定義或其他內容之后。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.