forked from taskflow/taskflow
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchapter6.html
More file actions
166 lines (164 loc) · 31.4 KB
/
chapter6.html
File metadata and controls
166 lines (164 loc) · 31.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
<!-- HTML header for doxygen 1.8.13-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=9"/>
<meta name="generator" content="Doxygen 1.8.13"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Cpp-Taskflow</title>
<link href="tabs.css" rel="stylesheet" type="text/css"/>
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="dynsections.js"></script>
<link href="navtree.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="resize.js"></script>
<script type="text/javascript" src="navtreedata.js"></script>
<script type="text/javascript" src="navtree.js"></script>
<script type="text/javascript">
$(document).ready(initResizable);
</script>
<link href="search/search.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="search/searchdata.js"></script>
<script type="text/javascript" src="search/search.js"></script>
<script type="text/javascript">
$(document).ready(function() { init_search(); });
</script>
<link href="doxygen.css" rel="stylesheet" type="text/css" />
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr style="height: 56px;">
<td id="projectalign" style="padding-left: 0.5em;">
<div id="projectname"><a href="https://github.com/cpp-taskflow/cpp-taskflow">Cpp-Taskflow</a>
 <span id="projectnumber">2.2.0</span>
</div>
</td>
<td> <div id="MSearchBox" class="MSearchBoxInactive">
<span class="left">
<img id="MSearchSelect" src="search/mag_sel.png"
onmouseover="return searchBox.OnSearchSelectShow()"
onmouseout="return searchBox.OnSearchSelectHide()"
alt=""/>
<input type="text" id="MSearchField" value="Search" accesskey="S"
onfocus="searchBox.OnSearchFieldFocus(true)"
onblur="searchBox.OnSearchFieldFocus(false)"
onkeyup="searchBox.OnSearchFieldChange(event)"/>
</span><span class="right">
<a id="MSearchClose" href="javascript:searchBox.CloseResultsWindow()"><img id="MSearchCloseImg" border="0" src="search/close.png" alt=""/></a>
</span>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- end header part -->
<!-- Generated by Doxygen 1.8.13 -->
<script type="text/javascript">
var searchBox = new SearchBox("searchBox", "search",false,'Search');
</script>
</div><!-- top -->
<div id="side-nav" class="ui-resizable side-nav-resizable">
<div id="nav-tree">
<div id="nav-tree-contents">
<div id="nav-sync" class="sync"></div>
</div>
</div>
<div id="splitbar" style="-moz-user-select:none;"
class="ui-resizable-handle">
</div>
</div>
<script type="text/javascript">
$(document).ready(function(){initNavTree('chapter6.html','');});
</script>
<div id="doc-content">
<!-- window showing the filter options -->
<div id="MSearchSelectWindow"
onmouseover="return searchBox.OnSearchSelectShow()"
onmouseout="return searchBox.OnSearchSelectHide()"
onkeydown="return searchBox.OnSearchSelectKey(event)">
</div>
<!-- iframe showing the search results (closed by default) -->
<div id="MSearchResultsWindow">
<iframe src="javascript:void(0)" frameborder="0"
name="MSearchResults" id="MSearchResults">
</iframe>
</div>
<div class="header">
<div class="headertitle">
<div class="title">C6: Manage Threads and Executor </div> </div>
</div><!--header-->
<div class="contents">
<div class="textblock"><p>We discuss in this chapter the thread management and task execution schemes in Cpp-Taskflow. We will go through the concept of <em>thread</em>, <em>ownership</em>, and <em>executor</em> in Cpp-Taskflow.</p>
<h1><a class="anchor" id="C6_MasterWorkersAndExecutor"></a>
Master, Workers, and Executor</h1>
<p>Cpp-Taskflow defines a strict relationship between the master and workers. Master is the thread that creates the taskflow object and workers are threads that invoke the callable target of a task. Each taskflow object owns an executor instance that implements the execution of a task, for example, by a thread in a shared pool. By default, Cpp-Taskflow uses <a class="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/thread/thread/hardware_concurrency.html">std::thread::hardware_concurrency</a> to decide the number of worker threads and <a class="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/thread/thread/get_id.html">std::thread::get_id</a> to identify the ownership between the master and workers.</p>
<div class="fragment"><div class="line"><a class="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/io/basic_ostream.html">std::cout</a> << <a class="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/thread/thread/hardware_concurrency.html">std::thread::hardware_concurrency</a>() << std::endl; <span class="comment">// 8, for example</span></div><div class="line"><a class="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/io/basic_ostream.html">std::cout</a> << <a class="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/thread/thread/get_id.html">std::thread::get_id</a>() << std::endl; <span class="comment">// master thread id</span></div><div class="line"><a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> tf1; <span class="comment">// create a taskflow object with the default number of workers</span></div><div class="line"><a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> tf2{4}; <span class="comment">// create a taskflow object with four workers</span></div></div><!-- fragment --><p>In the above example, the master thread owns both taskflow objects. The first taskflow object <code>tf1</code> creates eight (default by <a class="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/thread/thread/hardware_concurrency.html">std::thread::hardware_concurrency</a>) worker threads and the second taskflow object <code>tf2</code> creates four worker threads. Including the master thread, there will be a total of 1 + 8 + 4 = 13 threads running in this program. If you create a taskflow with zero workers, the master will carry out all the tasks by itself. That is, using one worker and zero worker are conceptually equivalent to each other since they both end up using one thread to run all tasks (see the snippet below).</p>
<div class="fragment"><div class="line"><a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> tf1(0); <span class="comment">// one master, zero worker (master to run tasks)</span></div><div class="line"><a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> tf2(1); <span class="comment">// one master, one worker (one thread to run tasks)</span></div></div><!-- fragment --><p>In general, the master thread is exposed to users at programming time (main thread), while the worker threads are transparently maintained by the taskflow object. Each taskflow object owns an executor managed by <a class="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/memory/shared_ptr.html">std::shared_ptr</a>. The default executor implements a work stealing scheduler to efficiently carry out tasks. The Taskflow class defines a member type <a class="el" href="classtf_1_1BasicTaskflow.html#afa5ea834928f68f950f59889c626c2ff" title="alias of executor type ">tf::Taskflow::Executor</a> as an alias of the associated executor type. Users can acquire an ownership of the executor from a taskflow object through the method <a class="el" href="classtf_1_1BasicTaskflow.html#abe76e5288016861aaf1dafc0218d3084" title="shares ownership of the executor associated with this taskflow object ">tf::Taskflow::share_executor</a>.</p>
<div class="fragment"><div class="line"><a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> taskflow;</div><div class="line"><a class="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/memory/shared_ptr.html">std::shared_ptr<tf::Taskflow::Executor></a> ptr = taskflow.<a class="code" href="classtf_1_1BasicTaskflow.html#abe76e5288016861aaf1dafc0218d3084">share_executor</a>(); <span class="comment">// share the executor</span></div><div class="line">assert(ptr.use_count() == 2); <span class="comment">// the executor is owned by ptr and tf</span></div></div><!-- fragment --><p>The <em>shared</em> property allows users to create their own resource manager and construct a taskflow object on top. The executor has only one constructor that takes an unsigned integer indicating the number of worker threads to spawn.</p>
<div class="fragment"><div class="line"><span class="keyword">auto</span> ptr = std::make_shared<tf::Taskflow::Executor>(4); <span class="comment">// create an executor of 4 workers</span></div><div class="line"><a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> taskflow(ptr); <span class="comment">// create a taskflow object on top of the executor</span></div><div class="line">assert(ptr.use_count() == 2); <span class="comment">// the executor is owned by ptr and tf</span></div></div><!-- fragment --><h1><a class="anchor" id="C6_ShareAnExecutorAmongTaskflowObjects"></a>
Share an Executor among Taskflow Objects</h1>
<p>It is sometime useful to share one executor among multiple taskflow objects in order to avoid the thread <em>over-subscription</em> problem. In the case of over-subscription, the number of threads running in a program exceeds the number of available logical cores, resulting in additional and unnecessary context switches. Context switch has nonzero cost and is especially costly when it crosses cores. The following example mimics the over-subscription problem through a creation of 100 taskflow objects each with its own executor of four threads, assuming only four logical cores present in the machine.</p>
<div class="fragment"><div class="line"><span class="comment">// create 100 taskflow objects on top of the same executor</span></div><div class="line"><a class="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><div class="line"><span class="keyword">auto</span> executor = std::make_shared<tf::Taskflow::Executor>(4);</div><div class="line"><span class="keywordflow">for</span>(<span class="keywordtype">size_t</span> i=0; i<100; ++i) {</div><div class="line"> assert(executor.use_count() == i + 1); <span class="comment">// by the executor and each taskflow</span></div><div class="line"> tfs.emplace_back(executor); <span class="comment">// create a taskflow object from the executor</span></div><div class="line">}</div><div class="line"><span class="comment">// a total of 1 + 4 = 5 threads running in this program</span></div></div><!-- fragment --><h1><a class="anchor" id="C6CustomizeYourExecutorInterface"></a>
Customize Your Executor Interface</h1>
<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>
<div class="fragment"><div class="line"><span class="keyword">template</span> <<span class="keyword">typename</span> C></div><div class="line"><span class="keyword">class </span>MyExecutor { <span class="comment">// closure type C, callable on operator ()</span></div><div class="line"></div><div class="line"> <span class="keyword">public</span>:</div><div class="line"></div><div class="line"> MyExecutor(<span class="keywordtype">unsigned</span>); <span class="comment">// constructor on a number of worker threads (might be zero)</span></div><div class="line"></div><div class="line"> <span class="keywordtype">size_t</span> num_workers() <span class="keyword">const</span>; <span class="comment">// return the number of worker threads (might be zero)</span></div><div class="line"></div><div class="line"> <span class="keyword">template</span> <<span class="keyword">typename</span>... ArgsT></div><div class="line"> <span class="keywordtype">void</span> emplace(ArgsT&&...); <span class="comment">// arguments to construct the closure C</span></div><div class="line"></div><div class="line"> <span class="keyword">template</span> <<span class="keyword">typename</span> C></div><div class="line"> <span class="keywordtype">void</span> batch(<a class="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>&); <span class="comment">// a vector of closures for batch insertions</span></div><div class="line">};</div><div class="line"></div><div class="line"><span class="keyword">using</span> MyTaskflow = <a class="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>
<ul>
<li>a constructor on a given number of worker threads </li>
<li>a constant method num_workers to return the number of worker threads </li>
<li>a method emplace to dispatch a task to thread with arguments to forward to the constructor of the task </li>
<li>a method batch to dispatch multiple tasks in a vector to thread</li>
</ul>
<p>Cpp-Taskflow requires little requirement on the executor class. Each taskflow object has its own internal data structure to keep track of the lifetime and execution status of a task. The executor only needs to guarantee a thread to run the task given by the methods emplace and batch. We recommend users to read our built-in executor implementation <a class="el" href="classtf_1_1WorkStealingExecutor.html" title="Executor that implements an efficient work stealing algorithm. ">tf::WorkStealingExecutor</a> for more details.</p>
<h1><a class="anchor" id="C6ThreadSafety"></a>
Thread Safety</h1>
<p>The Taskflow object is <em>NOT</em> thread-safe. Touching a taskflow object from multiple threads can result in <em>undefined behavior</em>. Thread safety has nothing to do with the master nor the workers. It is completely safe to access the taskflow object as long as only one thread presents at a time. However, we strongly recommend users to acknowledge the definition of the master and the workers, and separate the program control flow accordingly. Having a clear thread ownership can greatly reduce the chance of buggy implementations and undefined behaviors.</p>
<h1><a class="anchor" id="C6Example1"></a>
Example 1: Impact of Over-subscription</h1>
<p>The example below demonstrates the impact of thread over-subscription. The workload is a task dependency graph of four tasks doing compute-intensive matrix multiplication. We benchmarked the performance between the two implementations with and without sharing an executor.</p>
<div class="fragment"><div class="line"> 1: <span class="keywordtype">void</span> create_task_dependency_graph(<a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a>& tf) {</div><div class="line"> 2: <span class="keywordflow">for</span>(<span class="keywordtype">size_t</span> i=0; i<MAX_COUNT; ++i) {</div><div class="line"> 3: <span class="keyword">auto</span> [A, B, C, D] = tf.<a class="code" href="classtf_1_1FlowBuilder.html#a4d52a7fe2814b264846a2085e931652c">emplace</a>(</div><div class="line"> 4: [&] () { matrix_multiplication(); },</div><div class="line"> 5: [&] () { matrix_multiplication(); },</div><div class="line"> 6: [&] () { matrix_multiplication(); },</div><div class="line"> 7: [&] () { matrix_multiplication(); }</div><div class="line"> 8: );</div><div class="line"> 9: A.<a class="code" href="classtf_1_1Task.html#a8c78c453295a553c1c016e4062da8588">precede</a>(B);</div><div class="line">10: A.<a class="code" href="classtf_1_1Task.html#a8c78c453295a553c1c016e4062da8588">precede</a>(C);</div><div class="line">11: C.<a class="code" href="classtf_1_1Task.html#a8c78c453295a553c1c016e4062da8588">precede</a>(D);</div><div class="line">12: B.<a class="code" href="classtf_1_1Task.html#a8c78c453295a553c1c016e4062da8588">precede</a>(D);</div><div class="line">13: }</div><div class="line">14: }</div><div class="line">15:</div><div class="line">16: <span class="comment">// create multiple executors each with its own executor</span></div><div class="line">17: <span class="keyword">auto</span> unique_executor() {</div><div class="line">18:</div><div class="line">19: <span class="keyword">auto</span> beg = <a class="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/chrono/high_resolution_clock/now.html">std::chrono::high_resolution_clock::now</a>();</div><div class="line">20:</div><div class="line">21: <a class="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><div class="line">22:</div><div class="line">23: <span class="keywordflow">for</span>(<span class="keywordtype">size_t</span> i=0; i<MAX_TASKFLOW; ++i) {</div><div class="line">24: <a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a>& tf = tfs.emplace_back(MAX_THREAD);</div><div class="line">25: create_task_dependency_graph(tf);</div><div class="line">26: assert(tf.<a class="code" href="classtf_1_1BasicTaskflow.html#abe76e5288016861aaf1dafc0218d3084">share_executor</a>().use_count() == 2);</div><div class="line">27: }</div><div class="line">28:</div><div class="line">29: <a class="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<std::shared_future<void></a>> futures;</div><div class="line">30: <span class="keywordflow">for</span>(<a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a>& tf : tfs) {</div><div class="line">31: futures.emplace_back(tf.<a class="code" href="classtf_1_1BasicTaskflow.html#a848e425f67b49a8a7ac21f6b791999c5">dispatch</a>());</div><div class="line">32: }</div><div class="line">33:</div><div class="line">34: <span class="keywordflow">for</span>(<span class="keyword">auto</span>& fu : futures) {</div><div class="line">35: fu.get();</div><div class="line">36: }</div><div class="line">37:</div><div class="line">38: <span class="keyword">auto</span> <a class="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/iterator/end.html">end</a> = <a class="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/chrono/high_resolution_clock/now.html">std::chrono::high_resolution_clock::now</a>();</div><div class="line">39: <span class="keywordflow">return</span> <a class="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/chrono/duration/duration_cast.html">std::chrono::duration_cast</a><<a class="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/chrono/duration.html">std::chrono::milliseconds</a>>(<a class="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/iterator/end.html">end</a> - beg).<a class="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/algorithm/count.html">count</a>();</div><div class="line">40: }</div><div class="line">41:</div><div class="line">42: <span class="comment">// create multiple executors on top of the same executor</span></div><div class="line">43: <span class="keyword">auto</span> shared_executor() {</div><div class="line">44:</div><div class="line">45: <span class="keyword">auto</span> beg = <a class="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/chrono/high_resolution_clock/now.html">std::chrono::high_resolution_clock::now</a>();</div><div class="line">46:</div><div class="line">47: <a class="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><div class="line">48:</div><div class="line">49: <span class="keyword">auto</span> executor = std::make_shared<tf::Taskflow::Executor>(MAX_THREAD);</div><div class="line">50:</div><div class="line">51: <span class="keywordflow">for</span>(<span class="keywordtype">size_t</span> i=0; i<MAX_TASKFLOW; ++i) {</div><div class="line">52: assert(executor.use_count() == i + 1);</div><div class="line">53: <a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a>& tf = tfs.emplace_back(executor);</div><div class="line">54: create_task_dependency_graph(tf);</div><div class="line">55: }</div><div class="line">56:</div><div class="line">57: <a class="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<std::shared_future<void></a>> futures;</div><div class="line">58: <span class="keywordflow">for</span>(<a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a>& tf : tfs) {</div><div class="line">59: futures.emplace_back(tf.<a class="code" href="classtf_1_1BasicTaskflow.html#a848e425f67b49a8a7ac21f6b791999c5">dispatch</a>());</div><div class="line">60: }</div><div class="line">61:</div><div class="line">62: <span class="keywordflow">for</span>(<span class="keyword">auto</span>& fu : futures) {</div><div class="line">63: fu.get();</div><div class="line">64: }</div><div class="line">65:</div><div class="line">66: <span class="keyword">auto</span> <a class="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/iterator/end.html">end</a> = <a class="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/chrono/high_resolution_clock/now.html">std::chrono::high_resolution_clock::now</a>();</div><div class="line">67: <span class="keywordflow">return</span> <a class="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/chrono/duration/duration_cast.html">std::chrono::duration_cast</a><<a class="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/chrono/duration.html">std::chrono::milliseconds</a>>(<a class="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/iterator/end.html">end</a> - beg).<a class="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/algorithm/count.html">count</a>();</div><div class="line">68: }</div></div><!-- fragment --><p>Debrief:</p>
<ul>
<li>Line 1-14 creates a task dependency graph composed of compute-intensive matrix operations </li>
<li>Line 16-40 creates multiple independent taskflow objects without sharing the running threads </li>
<li>Line 42-68 creates multiple taskflow objects from the same executor to run on the same set of threads</li>
</ul>
<p>Running the program on different number of taskflow objects gives the following runtime values:</p>
<div class="fragment"><div class="line"># taskflows shared (ms) unique (ms)</div><div class="line"> 1 120 114</div><div class="line"> 2 225 229</div><div class="line"> 4 451 452</div><div class="line"> 8 908 904</div><div class="line"> 16 1791 1837</div><div class="line"> 32 3581 3782</div><div class="line"> 64 7183 7636</div><div class="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>
<h1><a class="anchor" id="C6MonitorThreadActivities"></a>
Monitor Thread Activities</h1>
<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 <a class="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>
<div class="fragment"><div class="line"><a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> taskflow;</div><div class="line"><span class="keyword">auto</span> observer = taskflow.<a class="code" href="classtf_1_1BasicTaskflow.html#abe76e5288016861aaf1dafc0218d3084">share_executor</a>()->make_observer<<a class="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 <a class="el" href="classtf_1_1ExecutorObserver.html#a2cfd00ec287c7574e515a588a42b3be4" title="get the number of total tasks in the observer ">tf::ExecutorObserver::num_tasks</a>, <a class="el" href="classtf_1_1ExecutorObserver.html#a20f77a06e0f10dc67f7a5742add5b00a" title="dump the timelines in JSON format to an ostream ">tf::ExecutorObserver::dump</a> and <a class="el" href="classtf_1_1ExecutorObserver.html#adc78a004eaa25022a20fd16a35f607ce" title="clear the timeline data ">tf::ExecutorObserver::clear</a> methods.</p>
<div class="fragment"><div class="line"> 1. <a class="code" href="classtf_1_1BasicTaskflow.html">tf::Taskflow</a> taskflow;</div><div class="line"> 2. <span class="keyword">auto</span> observer = taskflow.<a class="code" href="classtf_1_1BasicTaskflow.html#abe76e5288016861aaf1dafc0218d3084">share_executor</a>()->make_observer<<a class="code" href="classtf_1_1ExecutorObserver.html">tf::ExecutorObserver</a>>();</div><div class="line"> 3. taskflow.<a class="code" href="classtf_1_1FlowBuilder.html#a4d52a7fe2814b264846a2085e931652c">emplace</a>([](){}, [](){}, [](){}, [](){});</div><div class="line"> 4. taskflow.<a class="code" href="classtf_1_1BasicTaskflow.html#a37ef86998f23ee7315be032c40fe815e">wait_for_all</a>();</div><div class="line"> 5.</div><div class="line"> 6. <span class="comment">// Query the total number of tasks (number of timestamp pairs)</span></div><div class="line"> 7. <span class="keyword">auto</span> num_tasks = observer->num_tasks();</div><div class="line"> 8.</div><div class="line"> 9. <span class="comment">// Dump the timeline data in JSON format </span></div><div class="line">10. <a class="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><div class="line">11. </div><div class="line">12. <span class="comment">// Clear the timeline data</span></div><div class="line">13. observer->clear();</div></div><!-- fragment --><p>Debrief:</p>
<ul>
<li>Line 2-4 creates an observer and a task dependency graph with four tasks and dispatch the tasks to execution. </li>
<li>Line 7 query the total number of tasks (number of timestamp pair) through observer </li>
<li>Line 10 dump the timestamps to a <a class="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>
<li>Line 13 remove all timestamps in the observer</li>
</ul>
<p>You can visualize the timeline data in a Chrome browser:</p>
<ul>
<li>Step 1: save the JSON timeline data to a file </li>
<li>Step 2: launch the Chrome browser and open a tab with the url: chrome://tracing </li>
<li>Step 3: load the JSON file</li>
</ul>
<div class="image">
<img src="timeline.png" alt="timeline.png" width="80%"/>
</div>
<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>
<p>You can derive your own observer from the base interface class <a class="el" href="classtf_1_1ExecutorObserverInterface.html" title="The interface class for creating an executor observer. ">tf::ExecutorObserverInterface</a> to customize the observing methods. </p>
</div></div><!-- contents -->
</div><!-- doc-content -->
<!-- start footer part -->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
<li class="footer">Generated by
<a href="http://www.doxygen.org/index.html">
<img class="footer" src="doxygen.png" alt="doxygen"/></a> 1.8.13 </li>
</ul>
</div>
</body>
</html>