重塑微软的 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::spanScopePrinter, 但它也可以检测封装范围。

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/a31a3/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 也可以检测外跨度的主要内容,如果未能解决你的问题,请参考以下文章

微软改名部再次出手 微软旗下搜索引擎必应正式更名为微软必应

关于微软的开源项目的分享

搜索引擎Bing更名,强化微软品牌

微软 Windows 11 预览版已登陆 Azure 虚拟桌面

Windows 11 全新 4K 壁纸发布

R 中的重塑问题:我重塑的数据框将 3 个变量变为 1 个