You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Understanding thread activities is very important for performance analysis. Cpp-Taskflow provides a default *observer* of type `tf::ExecutorObserver` to let users observe when a thread starts or stops participating in task scheduling.
638
+
639
+
```cpp
640
+
tf::Taskflow taskflow;
641
+
// Create an observer
642
+
auto observer = taskflow.share_executor()->make_observer<tf::ExecutorObserver>();
643
+
```
644
+
645
+
When you dispatch a task dependency graph,
646
+
the observer will automatically record the start and end timestamps of each executed task.
647
+
You can dump the entire execution timelines into a JSON file.
648
+
649
+
```cpp
650
+
tf::Taskflow taskflow;
651
+
// Create an observer
652
+
auto observer = taskflow.share_executor()->make_observer<tf::ExecutorObserver>();
653
+
654
+
// Add tasks ....
655
+
656
+
// Dispatch the tasks to execution
657
+
taskflow.wait_for_all();
658
+
659
+
// Dump the timestamps to a JSON file
660
+
std::ofstream ofs("timestamps.json");
661
+
observer->dump(ofs);
662
+
```
663
+
664
+
You can open the chrome browser to visualize the execution timelines through the chrome://tracing developer tool. In the tracing view, click the `Load` button to read the JSON file.
665
+
You shall see the tracing graph.
666
+
667
+

668
+
669
+
Each task is given a name of `i_j` where `i` is the thread id and `j` is the task number.
670
+
You can pan or zoom in/out the timeline to get into a detailed view.
671
+
636
672
# API Reference
637
673
638
674
The official [documentation][wiki] explains the complete list of
@@ -1042,5 +1078,5 @@ Cpp-Taskflow is licensed under the [MIT License](./LICENSE).
<divclass="fragment"><divclass="line"><spanclass="comment">// create 100 taskflow objects on top of the same executor</span></div><divclass="line"><aclass="codeRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/docs/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/container/list.html">std::list<tf::Taskflow></a> tfs;</div><divclass="line"><spanclass="keyword">auto</span> executor = std::make_shared<tf::Taskflow::Executor>(4);</div><divclass="line"><spanclass="keywordflow">for</span>(<spanclass="keywordtype">size_t</span> i=0; i<100; ++i) {</div><divclass="line"> assert(executor.use_count() == i + 1); <spanclass="comment">// by the executor and each taskflow</span></div><divclass="line"> tfs.emplace_back(executor); <spanclass="comment">// create a taskflow object from the executor</span></div><divclass="line">}</div><divclass="line"><spanclass="comment">// a total of 1 + 4 = 5 threads running in this program</span></div></div><!-- fragment --><h1><aclass="anchor" id="C6CustomizeYourExecutorInterface"></a>
109
109
Customize Your Executor Interface</h1>
110
110
<p>Cpp-Taskflow permits users to define their own executor interface and integrate it into the taskflow object being built. In most cases, the executor is implemented as a thread pool to run given tasks. Your executor class must obey the following concepts in order to work with Cpp-Taskflow:</p>
111
-
<divclass="fragment"><divclass="line"><spanclass="keyword">template</span> <<spanclass="keyword">typename</span> C></div><divclass="line"><spanclass="keyword">class </span>MyExecutor { <spanclass="comment">// closure type C, callable on operator ()</span></div><divclass="line"></div><divclass="line"><spanclass="keyword">public</span>:</div><divclass="line"></div><divclass="line"> MyExecutor(<spanclass="keywordtype">unsigned</span>); <spanclass="comment">// constructor on a number of worker threads (might be zero)</span></div><divclass="line"><spanclass="keywordtype">size_t</span> num_workers() <spanclass="keyword">const</span>; <spanclass="comment">// return the number of worker threads (might be zero)</span></div><divclass="line"><spanclass="keyword">template</span> <<spanclass="keyword">typename</span>... ArgsT></div><divclass="line"><spanclass="keywordtype">void</span> emplace(ArgsT&&...); <spanclass="comment">// arguments to construct the closure C</span></div><divclass="line"><spanclass="keyword">template</span> <<spanclass="keyword">typename</span> C></div><divclass="line"><spanclass="keywordtype">void</span> batch(<aclass="codeRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/docs/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/container/vector.html">std::vector<C></a>&); <spanclass="comment">// a vector of closures for batch insertions</span></div><divclass="line">};</div><divclass="line"><spanclass="keyword">using</span> MyTaskflow = <aclass="code" href="classtf_1_1BasicTaskflow.html">tf::BasicTaskflow<MyExecutor></a>;</div></div><!-- fragment --><p>The executor class template with one parameter on the task type. The task type can be a generic polymorphic function wrapper, for instance, <code>std::function<void()></code>, or a callable class with fixed memory layout. It is completely up to users to define how to invoke the task. Your executor class must meet the following concepts:</p>
111
+
<divclass="fragment"><divclass="line"><spanclass="keyword">template</span> <<spanclass="keyword">typename</span> C></div><divclass="line"><spanclass="keyword">class </span>MyExecutor { <spanclass="comment">// closure type C, callable on operator ()</span></div><divclass="line"></div><divclass="line"><spanclass="keyword">public</span>:</div><divclass="line"></div><divclass="line"> MyExecutor(<spanclass="keywordtype">unsigned</span>); <spanclass="comment">// constructor on a number of worker threads (might be zero)</span></div><divclass="line"></div><divclass="line"><spanclass="keywordtype">size_t</span> num_workers() <spanclass="keyword">const</span>; <spanclass="comment">// return the number of worker threads (might be zero)</span></div><divclass="line"></div><divclass="line"><spanclass="keyword">template</span> <<spanclass="keyword">typename</span>... ArgsT></div><divclass="line"><spanclass="keywordtype">void</span> emplace(ArgsT&&...); <spanclass="comment">// arguments to construct the closure C</span></div><divclass="line"></div><divclass="line"><spanclass="keyword">template</span> <<spanclass="keyword">typename</span> C></div><divclass="line"><spanclass="keywordtype">void</span> batch(<aclass="codeRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/docs/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/container/vector.html">std::vector<C></a>&); <spanclass="comment">// a vector of closures for batch insertions</span></div><divclass="line">};</div><divclass="line"></div><divclass="line"><spanclass="keyword">using</span> MyTaskflow = <aclass="code" href="classtf_1_1BasicTaskflow.html">tf::BasicTaskflow<MyExecutor></a>;</div></div><!-- fragment --><p>The executor class template with one parameter on the task type. The task type can be a generic polymorphic function wrapper, for instance, <code>std::function<void()></code>, or a callable class with fixed memory layout. It is completely up to users to define how to invoke the task. Your executor class must meet the following concepts:</p>
112
112
<ul>
113
113
<li>a constructor on a given number of worker threads </li>
114
114
<li>a constant method num_workers to return the number of worker threads </li>
<li>Line 42-68 creates multiple taskflow objects from the same executor to run on the same set of threads</li>
130
130
</ul>
131
131
<p>Running the program on different number of taskflow objects gives the following runtime values:</p>
132
-
<divclass="fragment"><divclass="line"># taskflows shared (ms) unique (ms)</div><divclass="line"> 1 120 114</div><divclass="line"> 2 225 229</div><divclass="line"> 4 451 452</div><divclass="line"> 8 908 904</div><divclass="line"> 16 1791 1837</div><divclass="line"> 32 3581 3782</div><divclass="line"> 64 7183 7636</div><divclass="line"> 128 14341 15482</div></div><!-- fragment --><p>As we increase the number of taskflow objects, the implementation without sharing the executor encounters more context switches among threads. This overhead reflected on the slower runtime (15482 vs 14341 on 128 taskflow objects). </p>
132
+
<divclass="fragment"><divclass="line"># taskflows shared (ms) unique (ms)</div><divclass="line"> 1 120 114</div><divclass="line"> 2 225 229</div><divclass="line"> 4 451 452</div><divclass="line"> 8 908 904</div><divclass="line"> 16 1791 1837</div><divclass="line"> 32 3581 3782</div><divclass="line"> 64 7183 7636</div><divclass="line"> 128 14341 15482</div></div><!-- fragment --><p>As we increase the number of taskflow objects, the implementation without sharing the executor encounters more context switches among threads. This overhead reflected on the slower runtime (15482 vs 14341 on 128 taskflow objects).</p>
<p>Inspecting the thread activities is very important for performance analysis. It allows you to know when each task starts and ends participating in the task scheduling. Cpp-Taskflow provides a default observer class <aclass="el" href="classtf_1_1ExecutorObserver.html" title="A default executor observer to dump the execution timelines. ">tf::ExecutorObserver</a> for this purpose. The following example shows how to create an observer from a taskflow.</p>
136
+
<divclass="fragment"><divclass="line"><aclass="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> taskflow;</div><divclass="line"><spanclass="keyword">auto</span> observer = taskflow.<aclass="code" href="classtf_1_1BasicTaskflow.html#abe76e5288016861aaf1dafc0218d3084">share_executor</a>()->make_observer<<aclass="code" href="classtf_1_1ExecutorObserver.html">tf::ExecutorObserver</a>>();</div></div><!-- fragment --><p>Note that each executor can only have an observer at a time. An observer will automatically record the start and end timestamps of each executed task. Users can query, dump or remove the timestamps through the <aclass="el" href="classtf_1_1ExecutorObserver.html#a2cfd00ec287c7574e515a588a42b3be4" title="get the number of total tasks in the observer ">tf::ExecutorObserver::num_tasks</a>, <aclass="el" href="classtf_1_1ExecutorObserver.html#a20f77a06e0f10dc67f7a5742add5b00a" title="dump the timelines in JSON format to an ostream ">tf::ExecutorObserver::dump</a> and <aclass="el" href="classtf_1_1ExecutorObserver.html#adc78a004eaa25022a20fd16a35f607ce" title="clear the timeline data ">tf::ExecutorObserver::clear</a> methods.</p>
137
+
<divclass="fragment"><divclass="line"> 1. <aclass="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> taskflow;</div><divclass="line"> 2. <spanclass="keyword">auto</span> observer = taskflow.<aclass="code" href="classtf_1_1BasicTaskflow.html#abe76e5288016861aaf1dafc0218d3084">share_executor</a>()->make_observer<<aclass="code" href="classtf_1_1ExecutorObserver.html">tf::ExecutorObserver</a>>();</div><divclass="line"> 3. taskflow.<aclass="code" href="classtf_1_1FlowBuilder.html#a4d52a7fe2814b264846a2085e931652c">emplace</a>([](){}, [](){}, [](){}, [](){});</div><divclass="line"> 4. taskflow.<aclass="code" href="classtf_1_1BasicTaskflow.html#a37ef86998f23ee7315be032c40fe815e">wait_for_all</a>();</div><divclass="line"> 5.</div><divclass="line"> 6. <spanclass="comment">// Query the total number of tasks (number of timestamp pairs)</span></div><divclass="line"> 7. <spanclass="keyword">auto</span> num_tasks = observer->num_tasks();</div><divclass="line"> 8.</div><divclass="line"> 9. <spanclass="comment">// Dump the timeline data in JSON format </span></div><divclass="line">10. <aclass="codeRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/docs/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/string/basic_string.html">std::string</a> timelines = observer->dump();</div><divclass="line">11. </div><divclass="line">12. <spanclass="comment">// Clear the timeline data</span></div><divclass="line">13. observer->clear();</div></div><!-- fragment --><p>Debrief:</p>
138
+
<ul>
139
+
<li>Line 2-4 creates an observer and a task dependency graph with four tasks and dispatch the tasks to execution. </li>
140
+
<li>Line 7 query the total number of tasks (number of timestamp pair) through observer </li>
141
+
<li>Line 10 dump the timestamps to a <aclass="elRef" doxygen="/home/twhuang/PhD/Code/cpp-taskflow/docs/cppreference-doxygen-web.tag.xml:http://en.cppreference.com/w/" href="http://en.cppreference.com/w/cpp/string/basic_string.html">std::string</a> in JSON format </li>
142
+
<li>Line 13 remove all timestamps in the observer</li>
143
+
</ul>
144
+
<p>You can visualize the timeline data in a Chrome browser:</p>
145
+
<ul>
146
+
<li>Step 1: save the JSON timeline data to a file </li>
147
+
<li>Step 2: launch the Chrome browser and open a tab with the url: chrome://tracing </li>
<p>Tasks will be categorized by the executing thread and each task is named with <em>i_j</em> where <em>i</em> is the thread id and <em>j</em> is the task number. You can pan or zoom in/out the timeline to get a detailed view.</p>
154
+
<p>You can derive your own observer from the base interface class <aclass="el" href="classtf_1_1ExecutorObserverInterface.html" title="The interface class for creating an executor observer. ">tf::ExecutorObserverInterface</a> to customize the observing methods. </p>
0 commit comments