可变参数增强绑定类型解析
variadic boost bind type resolution
我正在尝试编写一个异步记录器,它接受可变参数,然后使用可变纵梁将这些参数串在一起,然后推送到单个生产者单个消费者队列中。
我卡在了 Log 结构的入队函数部分,如下所示:
template <typename T>
std::string Log::stringer(T const & t){
return boost::lexical_cast<std::string>(t);
}
template<typename T, typename ... Args>
std::string Log::stringer(T const & t, Args const & ... args){
return stringer(t) + stringer(args...);
}
template<typename T, typename ... Args>
void Log::enqueue(T & t, Args & ... args){
boost::function<std::string()> f
= boost::bind(&Log::stringer<T &, Args & ...>,this,
boost::ref(t),
boost::forward<Args>(args)...);
/// the above statement fails to compile though if i use 'auto f' it works ->
/// but then it is unclear to me what the signature of f really is ?
// at this point i would like to post the functor f onto my asio::io_service,
// but not able to cause it's not clear to me what the type of f is.
// I think it should be of type boost::function<std::string()>
}
在 main() 中,我调用
Log t_log;
t_log.enqueue("hello"," world");
我对你问的功能的建议:
template <typename T, typename... Args> void enqueue(T &t, Args const&... args) {
this->io_service->post([=]{
auto s = stringer(t, args...);
//std::fprintf(stderr, "%s\n", s.c_str());
});
}
这适用于 GCC 和 Clang(GCC 4.9 或更高版本,因为捕获的可变参数包存在已知问题)。
但实际上,我会重新考虑手头的设计,并且肯定会开始很多更简单,直到您知道哪些区域值得进一步优化。
问题
这段代码有很多地方我不明白:
- 为什么参数被非常量引用采用
- 为什么您随后对它们使用
std::forward<>
(您现在已经是价值类别,并且不会改变)
为什么要将字符串化传递给 io_service
?
- 队列将引入锁定(有点反驳无锁队列)和
- 字符串化将忽略其结果...
为什么要在这里使用boost::function
?这会导致(另一个)动态分配和间接调度......只是 post f
为什么首先要通过引用绑定参数?如果你打算在不同的线程上处理参数,这会导致未定义的行为。例如。想象来电者在做
char const msg[] = "my message"; // perhaps some sprintf output
l.enqueue(cat.c_str(), msg);
c_str()
在 enqueue
返回后过时并且 msg
很快超出范围,或者被其他数据覆盖。
明明有 c++11
支持(因为你使用了 std::forward<>
和属性),为什么还要使用 bind
方法?
你为什么要使用无锁队列(预计会以最大值持续记录 CPU?在那种情况下,记录是你应用程序的核心功能,你可能应该这样想通过更严格一点(很多)(例如写入预分配的交替缓冲区并决定最大积压等)。
在所有其他情况下,您可能希望在无锁队列上最多有 1 个单线程 运行。这可能已经有点矫枉过正了(不断地旋转一个线程是昂贵的)。相反,如果 n 个周期无事可做,您可以优雅地回退到 yields/synchronization。
您可以只绑定到 shared_ptr
。这是一个 lot 比绑定到 .get()
更安全和方便
In my sample below I've just removed the need for scoped_ptr
s by not allocating everything from the heap (why was that?). (You can use boost::optional<work>
if you needed work.)
显式记忆顺序 load/stores 也让我感觉很糟糕。只有在标志中恰好涉及两个线程时,它们的编写方式才有意义,但这对我来说目前还不明显(线程是在周围创建的)。
在大多数平台上不会有任何区别,鉴于上述情况,显式内存排序的存在作为一种清晰的代码气味脱颖而出
同样的事情也适用于强制内联某些函数的尝试。您可以信任您的编译器,并且您应该避免再次猜测它,直到您知道您的瓶颈是由次优生成的代码
造成的
由于您打算赋予线程线程亲和性,确实使用线程局部变量。在 C++03 (__thread) 中使用 GCC/MSVC 扩展或使用 c++11 thread_local
,例如在 pop()
thread_local std::string s;
s.reserve(1000);
s.resize(0);
这极大地减少了分配的数量(代价是使 pop() 不可重入,这不是必需的。
I later noticed this pop()
is limited to a single thread
如果你所做的只是......手动围绕它进行自旋锁,那么拥有那个无锁队列有什么用?
void push(std::string const &s) {
while (std::atomic_flag_test_and_set_explicit(&this->lock, std::memory_order_acquire))
;
while (!this->q->push(s))
;
std::atomic_flag_clear_explicit(&this->lock, std::memory_order_release);
}
清理建议
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/atomic.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/thread/thread.hpp>
/*
* safe for use from a single thread only
*/
template <unsigned line_maxchars = 1000>
class Log {
public:
Log(std::string const &logFileName, int32_t queueSize)
: fp(stderr), // std::fopen(logFileName.c_str(),"w")
_shutdown(false),
_thread(&Log::pop, this),
_queue(queueSize)
{ }
void pop() {
std::string s;
s.reserve(line_maxchars);
struct timeval ts;
while (!_shutdown) {
while (_queue.pop(s)) {
gettimeofday(&ts, NULL);
std::fprintf(fp, "%li.%06li %s\n", ts.tv_sec, ts.tv_usec, s.c_str());
}
std::fflush(fp); // RECONSIDER HERE?
}
while (_queue.pop(s)) {
gettimeofday(&ts, NULL);
std::fprintf(fp, "%li.%06li %s\n", ts.tv_sec, ts.tv_usec, s.c_str());
}
}
template <typename S, typename T> void stringer(S& stream, T const &t) {
stream << t;
}
template <typename S, typename T, typename... Args>
void stringer(S& stream, T const &t, Args const &... args) {
stringer(stream, t);
stringer(stream, args...);
}
template <typename T, typename... Args> void enqueue(T &t, Args const&... args) {
thread_local char buffer[line_maxchars] = {};
boost::iostreams::array_sink as(buffer);
boost::iostreams::stream<boost::iostreams::array_sink> stream(as);
stringer(stream, t, args...);
auto output = as.output_sequence();
push(std::string(output.first, output.second));
}
void push(std::string const &s) {
while (!_queue.push(s));
}
~Log() {
_shutdown = true;
_thread.join();
assert(_queue.empty());
std::fflush(fp);
std::fclose(fp);
fp = NULL;
}
private:
FILE *fp;
boost::atomic_bool _shutdown;
boost::thread _thread;
boost::lockfree::spsc_queue<std::string> _queue;
};
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
auto start = high_resolution_clock::now();
{
Log<> l("/tmp/junk.log", 1024);
for (int64_t i = 0; i < 10; ++i) {
l.enqueue("hello ", i, " world");
}
}
std::cout << duration_cast<microseconds>(high_resolution_clock::now() - start).count() << "μs\n";
}
如您所见,我将代码减少了三分之一。我已经记录了这样一个事实,即它只能在单线程中安全使用。
Asio 不见了。词汇转换消失了。事物有有意义的名字。不再需要调整内存顺序。不再需要线程亲和性摆弄。不再 内联嫉妒 。不再需要繁琐的字符串分配。
您可能从中受益最多的是
- 通过引用将array_sinks/buffers汇集并存储在队列中
- 不是每个日志都刷新
我正在尝试编写一个异步记录器,它接受可变参数,然后使用可变纵梁将这些参数串在一起,然后推送到单个生产者单个消费者队列中。
我卡在了 Log 结构的入队函数部分,如下所示:
template <typename T>
std::string Log::stringer(T const & t){
return boost::lexical_cast<std::string>(t);
}
template<typename T, typename ... Args>
std::string Log::stringer(T const & t, Args const & ... args){
return stringer(t) + stringer(args...);
}
template<typename T, typename ... Args>
void Log::enqueue(T & t, Args & ... args){
boost::function<std::string()> f
= boost::bind(&Log::stringer<T &, Args & ...>,this,
boost::ref(t),
boost::forward<Args>(args)...);
/// the above statement fails to compile though if i use 'auto f' it works ->
/// but then it is unclear to me what the signature of f really is ?
// at this point i would like to post the functor f onto my asio::io_service,
// but not able to cause it's not clear to me what the type of f is.
// I think it should be of type boost::function<std::string()>
}
在 main() 中,我调用
Log t_log;
t_log.enqueue("hello"," world");
我对你问的功能的建议:
template <typename T, typename... Args> void enqueue(T &t, Args const&... args) {
this->io_service->post([=]{
auto s = stringer(t, args...);
//std::fprintf(stderr, "%s\n", s.c_str());
});
}
这适用于 GCC 和 Clang(GCC 4.9 或更高版本,因为捕获的可变参数包存在已知问题)。
但实际上,我会重新考虑手头的设计,并且肯定会开始很多更简单,直到您知道哪些区域值得进一步优化。
问题
这段代码有很多地方我不明白:
- 为什么参数被非常量引用采用
- 为什么您随后对它们使用
std::forward<>
(您现在已经是价值类别,并且不会改变) 为什么要将字符串化传递给
io_service
?- 队列将引入锁定(有点反驳无锁队列)和
- 字符串化将忽略其结果...
为什么要在这里使用
boost::function
?这会导致(另一个)动态分配和间接调度......只是 postf
为什么首先要通过引用绑定参数?如果你打算在不同的线程上处理参数,这会导致未定义的行为。例如。想象来电者在做
char const msg[] = "my message"; // perhaps some sprintf output l.enqueue(cat.c_str(), msg);
c_str()
在enqueue
返回后过时并且msg
很快超出范围,或者被其他数据覆盖。明明有
c++11
支持(因为你使用了std::forward<>
和属性),为什么还要使用bind
方法?你为什么要使用无锁队列(预计会以最大值持续记录 CPU?在那种情况下,记录是你应用程序的核心功能,你可能应该这样想通过更严格一点(很多)(例如写入预分配的交替缓冲区并决定最大积压等)。
在所有其他情况下,您可能希望在无锁队列上最多有 1 个单线程 运行。这可能已经有点矫枉过正了(不断地旋转一个线程是昂贵的)。相反,如果 n 个周期无事可做,您可以优雅地回退到 yields/synchronization。
您可以只绑定到
更安全和方便shared_ptr
。这是一个 lot 比绑定到.get()
In my sample below I've just removed the need for
scoped_ptr
s by not allocating everything from the heap (why was that?). (You can useboost::optional<work>
if you needed work.)显式记忆顺序 load/stores 也让我感觉很糟糕。只有在标志中恰好涉及两个线程时,它们的编写方式才有意义,但这对我来说目前还不明显(线程是在周围创建的)。
在大多数平台上不会有任何区别,鉴于上述情况,显式内存排序的存在作为一种清晰的代码气味脱颖而出
同样的事情也适用于强制内联某些函数的尝试。您可以信任您的编译器,并且您应该避免再次猜测它,直到您知道您的瓶颈是由次优生成的代码
造成的
由于您打算赋予线程线程亲和性,确实使用线程局部变量。在 C++03 (__thread) 中使用 GCC/MSVC 扩展或使用 c++11
thread_local
,例如在pop()
thread_local std::string s; s.reserve(1000); s.resize(0);
这极大地减少了分配的数量(代价是使 pop() 不可重入,这不是必需的。I later noticed this
pop()
is limited to a single thread如果你所做的只是......手动围绕它进行自旋锁,那么拥有那个无锁队列有什么用?
void push(std::string const &s) { while (std::atomic_flag_test_and_set_explicit(&this->lock, std::memory_order_acquire)) ; while (!this->q->push(s)) ; std::atomic_flag_clear_explicit(&this->lock, std::memory_order_release); }
清理建议
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/atomic.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/thread/thread.hpp>
/*
* safe for use from a single thread only
*/
template <unsigned line_maxchars = 1000>
class Log {
public:
Log(std::string const &logFileName, int32_t queueSize)
: fp(stderr), // std::fopen(logFileName.c_str(),"w")
_shutdown(false),
_thread(&Log::pop, this),
_queue(queueSize)
{ }
void pop() {
std::string s;
s.reserve(line_maxchars);
struct timeval ts;
while (!_shutdown) {
while (_queue.pop(s)) {
gettimeofday(&ts, NULL);
std::fprintf(fp, "%li.%06li %s\n", ts.tv_sec, ts.tv_usec, s.c_str());
}
std::fflush(fp); // RECONSIDER HERE?
}
while (_queue.pop(s)) {
gettimeofday(&ts, NULL);
std::fprintf(fp, "%li.%06li %s\n", ts.tv_sec, ts.tv_usec, s.c_str());
}
}
template <typename S, typename T> void stringer(S& stream, T const &t) {
stream << t;
}
template <typename S, typename T, typename... Args>
void stringer(S& stream, T const &t, Args const &... args) {
stringer(stream, t);
stringer(stream, args...);
}
template <typename T, typename... Args> void enqueue(T &t, Args const&... args) {
thread_local char buffer[line_maxchars] = {};
boost::iostreams::array_sink as(buffer);
boost::iostreams::stream<boost::iostreams::array_sink> stream(as);
stringer(stream, t, args...);
auto output = as.output_sequence();
push(std::string(output.first, output.second));
}
void push(std::string const &s) {
while (!_queue.push(s));
}
~Log() {
_shutdown = true;
_thread.join();
assert(_queue.empty());
std::fflush(fp);
std::fclose(fp);
fp = NULL;
}
private:
FILE *fp;
boost::atomic_bool _shutdown;
boost::thread _thread;
boost::lockfree::spsc_queue<std::string> _queue;
};
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
auto start = high_resolution_clock::now();
{
Log<> l("/tmp/junk.log", 1024);
for (int64_t i = 0; i < 10; ++i) {
l.enqueue("hello ", i, " world");
}
}
std::cout << duration_cast<microseconds>(high_resolution_clock::now() - start).count() << "μs\n";
}
如您所见,我将代码减少了三分之一。我已经记录了这样一个事实,即它只能在单线程中安全使用。
Asio 不见了。词汇转换消失了。事物有有意义的名字。不再需要调整内存顺序。不再需要线程亲和性摆弄。不再 内联嫉妒 。不再需要繁琐的字符串分配。
您可能从中受益最多的是
- 通过引用将array_sinks/buffers汇集并存储在队列中
- 不是每个日志都刷新