0

I wish to create ScopePrinter that works similar as Microsoft's Concurrency::diagnostic::span,
but it can also detect encapsulating scope.

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]"

Here is my MCVE.

fib() and fibPrint() are just a dummy expensive function.

ScopePrinter(X) is my utility for printing [begin X] at the constructor and print [end X] when its block is ended.

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
    }
};

Here is the 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();
}

Here is a possible output :-

[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]

How to make ScopePrinter("a31")'s constructor and destructor print full scope like [start a3/a31] and [end a3/a31] instead of [start a31] and [end a31]?

This will be very useful for profiling my multithreading program.
I am considering thread_local and MACRO, but I don't think it would help.

I have read Is using std::async many times for small tasks performance friendly?.

cppBeginner
  • 1,114
  • 9
  • 27
  • The lifetime of `ScopePrinter("a31")` is not nested within the lifetime of `ScopePrinter("a3")` - by the time the former is created, the latter has already been destroyed. Both expressions create a temporary that is destroyed immediately, at the nearest semicolon. In light of this, I'm not sure what you mean by "full scope". – Igor Tandetnik Dec 02 '19 at 14:30
  • Note how you end up with `[start a1][end a1]5 fib= 5` and not `[start a1]5 fib= 5[end a1]` – Igor Tandetnik Dec 02 '19 at 14:32
  • @Igor Tandetnik :: Thank, it is my mistake. I just fixed the question. (such issue does not exist in my real code) – cppBeginner Dec 02 '19 at 14:44
  • You could perhaps add a constructor to `ScopePrinter` that takes a pointer to enclosing `ScopePrinter` in addition to the name, and trace the logical stack by following those pointers. – Igor Tandetnik Dec 02 '19 at 14:46
  • @Igor Tandetnik :: It would be an inconvenient solution. ... I have to pass this debugging pointer as additional parameter to various functions. – cppBeginner Dec 03 '19 at 01:14
  • 1
    I can't think of any way for `ScopePrinter s("a31")` created on some background thread from a thread pool to somehow automatically discover that the `async` call, on a different thread, that kicked off the job was made within the scope of another `ScopePrinter` instance. – Igor Tandetnik Dec 03 '19 at 02:33
  • @Igor Tandetnik Thank. It is a valuable comment. I will try to look elsewhere. – cppBeginner Dec 03 '19 at 03:16

1 Answers1

1

If you want a3/a31 and a3/a32 to show up as sub-scopes, then you can just pass in the pointer to the outer scope, and use this to build a composite name:

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"); }
};

Then in the nested-call case you can pass in the outer scope:

  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);
      });
    }
  });

This will then print something like:

[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]
Anthony Williams
  • 66,628
  • 14
  • 133
  • 155
  • Thank, but it is inconvenient. Is there a more automatic solution? e.g. If I want to profile a game, I will have to insert this new parameter (`ScopePrinter*`) for 300+ function. – cppBeginner Dec 03 '19 at 01:20
  • You could declare `thread_local ScopePrinter* parent`, and then assign to it in the constructor (after using it to set the name) and reset it in the destructor. Note that this wouldn't affect the nested `async` calls unless they ended up being `deferred`. – Anthony Williams Dec 03 '19 at 08:43