重塑微软的 Concurrency::diagnostic::span 也可以检测外跨度
Posted
技术标签:
【中文标题】重塑微软的 Concurrency::diagnostic::span 也可以检测外跨度【英文标题】:reinvent Microsoft's Concurrency::diagnostic::span that can also detect outer span 【发布时间】:2019-12-02 13:51:02 【问题描述】:我希望创建类似于Microsoft's Concurrency::diagnostic::span
的ScopePrinter
,
但它也可以检测封装范围。
ScopePrint sc2"scope1"; // should print "[start scope1]"
createTask([&]()
ScopePrint sc2"scope2"; // should print "[start scope1/scope2]"
//do something expensive (can create more tasks & ScopePrints)
// should print "[end scope1/scope2]"
);
// should print "[end scope1]"
这是我的MCVE。
fib()
和 fibPrint()
只是一个昂贵的虚拟函数。
ScopePrinter(X)
是我在构造函数中打印[begin X]
并在其块结束时打印[end X]
的实用程序。
int fib(int n) // just something that burn some CPU cycles
if (n<2) return n;
return fib(n-1) + fib(n-2);
void fibPrint(int n) // just something that burn some CPU cycles
std::cout<<n<<" fib= "<<fib(n)<<std::endl;
struct ScopePrinter // my Utility class - useful for profiling
std::string name="";
public: ScopePrinter(std::string strP)
name=strP; std::cout<< ("[start "+name +"]\n");
//in real case, it cache current time too
public: ~ScopePrinter()
std::cout<< ("[end "+name +"]\n");
//in real case, it prints total used time too
;
这里是main()
:-
int main()
auto a1 = std::async([&]()
ScopePrinter s("a1");
fibPrint(5);
);
auto a2 = std::async([&]()
ScopePrinter s("a2");
fibPrint(6);
);
auto a3 = std::async([&]()
ScopePrinter s("a3");
fibPrint(7);
auto a31 = std::async([&]()
ScopePrinter s("a31");
fibPrint(8);
);
auto a32 = std::async([&]()
ScopePrinter s("a32");
fibPrint(9);
);
);
a1.wait();
这是一个可能的输出:-
[start a1]
[start a2]
5 fib= 6 fib= 58
[end a1]
[end a2]
[start a3]
7 fib= 13
[start a31]
8 fib= 21
[end a31]
[start a32]
9 fib= 34
[end a32]
[end a3]
如何使ScopePrinter("a31")
的构造函数和析构函数打印全范围,如[start a3/a31]
和[end a3/a31]
,而不是[start a31]
和[end a31]
?
这对于分析我的多线程程序非常有用。
我正在考虑thread_local
和 MACRO,但我认为这不会有帮助。
我已阅读Is using std::async many times for small tasks performance friendly?。
【问题讨论】:
ScopePrinter("a31")
的生命周期没有嵌套在 ScopePrinter("a3")
的生命周期内——当前者被创建时,后者已经被销毁。这两个表达式都创建了一个临时的,在最近的分号处立即被销毁。鉴于此,我不确定您所说的“全范围”是什么意思。
注意你是怎么用[start a1][end a1]5 fib= 5
而不是[start a1]5 fib= 5[end a1]
结束的
@Igor Tandetnik :: 谢谢,这是我的错误。我刚刚解决了这个问题。 (我的真实代码中不存在这样的问题)
您也许可以向ScopePrinter
添加一个构造函数,该构造函数除了名称之外还需要一个指向封闭ScopePrinter
的指针,并通过遵循这些指针来跟踪逻辑堆栈。
我想不出任何办法让在线程池中的某个后台线程上创建的ScopePrinter s("a31")
以某种方式自动发现在另一个线程上的async
调用启动了工作是在另一个 ScopePrinter
实例的范围内制作的。
【参考方案1】:
如果您希望 a3/a31
和 a3/a32
显示为子作用域,那么您只需传入指向外部作用域的指针,并使用它来构建复合名称:
struct ScopePrinter
std::string name;
public:
ScopePrinter(std::string strP, ScopePrinter* parent = nullptr)
: name((parent ? parent->name + "/" : "") + strP)
std::cout << ("[start " + name + "]\n");
public:
~ScopePrinter() std::cout << ("[end " + name + "]\n");
;
那么在嵌套调用的情况下你可以传入外部作用域:
auto a3 = std::async([&]()
ScopePrinter s("a3");
fibPrint(7);
auto a31 = std::async([&]()
ScopePrinter s1("a31", &s);
fibPrint(8);
);
auto a32 = std::async([&]()
ScopePrinter s2("a32", &s);
fibPrint(9);
);
);
这将打印出类似的内容:
[start a1]
5 fib= 5
[end a1]
[start a3]
7 fib= [start a2]
6 fib= 138
[end a2]
[start a3/a31]
8 fib= [start a3/a32]
9 fib= 34
[end a3/a32]
21
[end a3/a31]
[end a3]
【讨论】:
谢谢,但不方便。有没有更自动化的解决方案?例如如果我想分析一个游戏,我必须为 300+ 函数插入这个新参数 (ScopePrinter*
)。
你可以声明thread_local ScopePrinter* parent
,然后在构造函数中赋值给它(在使用它设置名称之后)并在析构函数中重置它。请注意,这不会影响嵌套的 async
调用,除非它们最终成为 deferred
。以上是关于重塑微软的 Concurrency::diagnostic::span 也可以检测外跨度的主要内容,如果未能解决你的问题,请参考以下文章