Cheap, Simple, and Safe Logging Using Expression Templates - Marc Eaddy
Cheap, Simple, and Safe Logging Using Expression Templates - Marc Eaddy
void foo() {
string file = "blah.txt";
int error = 123;
...
LOG("Read failed: " << file << " (" << error << ")");
}
// Outputs: Convenient
// test.cpp(5): Read failed: blah.txt (123) and type safe
streaming interface
Marc Eaddy, Intel 9/12/2014
3 /15
AFTER PRE-PROCESSING
void foo() {
string file = "blah.txt";
int error = 123;
...
if (s_bLoggingEnabled)
std::cout << "foo.cpp" << "(" << 53 << "): "
<< "Read failed: " << file << " (" << error << ")")
<< std::endl;
}
ASSEMBLER
void foo() {
string file = "blah.txt"; % icc --std=c++11 -O3 -g -fcode-asm -S test.cpp
int error = 123;
...
movb g_bLogging(%rip), %al
testb %al, %al
33 instructions
je ..B2.14
movl $_ZSt4cout, %edi
10 function calls
movl $.L_2__STRING.3, %esi
call ostream& operator<<(ostream&, char const*)
movq %rax, %rdi
movl $.L_2__STRING.0, %esi
call ostream& operator<<(ostream&, char const*)
movq %rax, %rdi
movl $19, %esi
call ostream::operator<<(int)
...
movq %rax, %rdi
movl $ostream& endl(ostream&), %esi
call ostream::operator<<(ostream& (*)(ostream&))
}
PROBLEM
• Log-related instructions...
• may prevent compiler optimizations
• may hurt icache performance
A SOLUTION
• How to retain streaming interface without op<< function calls
at call site?
• Evaluate expressions at compile-time instead of runtime
• "Expression templates" use operator overloading to pack an
expression into a type
• Matrix D = A + B * C;
• polygons.Find(VERTICES == 4 && WIDTH >= 20);
• LOG("Read failed: " << file << " (" << error << ")");
LOGDATA<>
#define LOG(msg) \
if (s_bLoggingEnabled) \
(Log(__FILE__, __LINE__, LogData<None>() << msg))
template<typename List>
struct LogData {
typedef List type;
List list;
};
struct None { };
Marc Eaddy, Intel 9/12/2014
8 /15
LOGDATA<>
LOGDATA<>
LOG("Read failed: " << file << " (" << error << ")");
LogData<
pair<
pair<
pair<
pair<
pair<
None,
char const*>, "Read failed: "
string const&>, file
char const*>, " ("
int const&>, error
char const*> ")"
Marc Eaddy, Intel
>
10 /15
LOG()
template<typename TLogData>
void Log(const char* file, int line, TLogData&& data) noexcept __attribute__((__noinline__)) {
std::cout << file << "(" << line << "): ";
Log_Recursive(std::cout, std::forward<typename TLogData::type>(data.list));
cout << endl;
}
template<typename TLogDataPair>
void Log_Recursive(std::ostream& os, TLogDataPair&& data) noexcept {
Log_Recursive(os, std::forward<typename TLogDataPair::first_type>(data.first));
os << std::forward<typename TLogDataPair::second_type>(data.second);
}
template<typename Begin>
LogData<pair<Begin&&, PfnManipulator>> operator<<(LogData<Begin>&& begin,
PfnManipulator pfn)
noexcept {
return {{ std::forward<Begin>(begin.list), pfn }};
}
ASSEMBLER
movb g_bLogging(%rip), %al
testb %al, %al
je ..B6.7
movb $0, (%rsp)
movl $.L_2__STRING.4, %ecx
movl $.L_2__STRING.3, %edi 9 instructions
movl $40, %esi 1 pimp’d function call
lea 128(%rsp), %r9
call void Log<pair<pair<pair<pair<pair<None, char const*>,
string const&>, char const*>, int const&>, char const*> >(char
const*, int, LogData<pair<pair<pair<pair<pair<None, char
const*>, string const&>, char const*>, int const&>, char const*>
> const&)
SUMMARY
• Expression templates solution
• Reduced instructions at call site by 73% (33 9)
• Mo' args, mo' savings
THANK YOU!
template<typename... Args>
void Log_Variadic(const char* file, int line, const Args&... args) {
std::cout << file << "(" << line << "): ";
Log_Recursive(file, line, std::cout, args...);
std::cout << std::endl;
}
inline void Log_Recursive(const char* file, int line, std::ostream& os) { /* Empty */ }
Marc Eaddy, Intel 9/12/2014
17 /15
LOG("Read failed: " << file << " (" << error << ")");