/
executor_traits.hpp
576 lines (520 loc) · 21.6 KB
/
executor_traits.hpp
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
// Copyright (c) 2007-2015 Hartmut Kaiser
// Copyright (c) 2015 Daniel Bourgeois
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
/// \file parallel/executors/executor_traits.hpp
#if !defined(HPX_PARALLEL_EXECUTOR_TRAITS_MAY_10_2015_1128AM)
#define HPX_PARALLEL_EXECUTOR_TRAITS_MAY_10_2015_1128AM
#include <hpx/hpx_fwd.hpp>
#include <hpx/exception.hpp>
#include <hpx/async.hpp>
#include <hpx/traits/is_executor.hpp>
#include <hpx/lcos/when_all.hpp>
#include <hpx/util/decay.hpp>
#include <hpx/util/always_void.hpp>
#include <hpx/util/result_of.hpp>
#include <hpx/util/deferred_call.hpp>
#include <hpx/util/unwrapped.hpp>
#include <hpx/traits/is_callable.hpp>
#include <hpx/parallel/config/inline_namespace.hpp>
#include <type_traits>
#include <utility>
#include <boost/range/functions.hpp>
#include <boost/range/irange.hpp>
namespace hpx { namespace parallel { HPX_INLINE_NAMESPACE(v3)
{
///////////////////////////////////////////////////////////////////////////
/// Function invocations executed by a group of sequential execution agents
/// execute in sequential order.
struct sequential_execution_tag {};
/// Function invocations executed by a group of parallel execution agents
/// execute in unordered fashion. Any such invocations executing in the
/// same thread are indeterminately sequenced with respect to each other.
///
/// \note \a parallel_execution_tag is weaker than
/// \a sequential_execution_tag.
struct parallel_execution_tag {};
/// Function invocations executed by a group of vector execution agents are
/// permitted to execute in unordered fashion when executed in different
/// threads, and un-sequenced with respect to one another when executed in
/// the same thread.
///
/// \note \a vector_execution_tag is weaker than
/// \a parallel_execution_tag.
struct vector_execution_tag {};
namespace detail
{
/// \cond NOINTERNAL
template <typename Category1, typename Category2>
struct is_not_weaker
: std::false_type
{};
template <typename Category>
struct is_not_weaker<Category, Category>
: std::true_type
{};
template <>
struct is_not_weaker<parallel_execution_tag, vector_execution_tag>
: std::true_type
{};
template <>
struct is_not_weaker<sequential_execution_tag, vector_execution_tag>
: std::true_type
{};
template <>
struct is_not_weaker<sequential_execution_tag, parallel_execution_tag>
: std::true_type
{};
/// \endcond
}
///////////////////////////////////////////////////////////////////////////
namespace detail
{
/// \cond NOINTERNAL
// wraps int so that int argument is favored over wrap_int
struct wrap_int
{
wrap_int(int) {}
};
///////////////////////////////////////////////////////////////////////
template <typename Executor, typename Enable = void>
struct execution_category
{
typedef parallel_execution_tag type;
};
template <typename Executor>
struct execution_category<Executor,
typename hpx::util::always_void<
typename Executor::execution_category
>::type>
{
typedef typename Executor::execution_category type;
};
///////////////////////////////////////////////////////////////////////
template <typename Executor, typename T, typename Enable = void>
struct future_type
{
typedef hpx::future<T> type;
};
template <typename Executor, typename T>
struct future_type<Executor, T,
typename hpx::util::always_void<
typename Executor::future_type>::type
>
{
typedef typename Executor::future_type type;
};
///////////////////////////////////////////////////////////////////////
struct apply_helper
{
template <typename Executor, typename F>
static auto call(wrap_int, Executor& exec, F && f) -> void
{
exec.async_execute(std::forward<F>(f));
}
template <typename Executor, typename F>
static auto call(int, Executor& exec, F && f)
-> decltype(exec.apply_execute(std::forward<F>(f)))
{
exec.apply_execute(std::forward<F>(f));
}
};
template <typename Executor, typename F>
void call_apply_execute(Executor& exec, F && f)
{
return apply_helper::call(0, exec, std::forward<F>(f));
}
///////////////////////////////////////////////////////////////////////
struct execute_helper
{
template <typename Executor, typename F>
static typename hpx::util::result_of<
typename hpx::util::decay<F>::type()
>::type
call(wrap_int, Executor& exec, F && f)
{
return exec.async_execute(std::forward<F>(f)).get();
}
template <typename Executor, typename F>
static auto call(int, Executor& exec, F && f)
-> decltype(exec.execute(std::forward<F>(f)))
{
return exec.execute(std::forward<F>(f));
}
};
template <typename Executor, typename F>
typename hpx::util::result_of<
typename hpx::util::decay<F>::type()
>::type
call_execute(Executor& exec, F && f)
{
return execute_helper::call(0, exec, std::forward<F>(f));
}
///////////////////////////////////////////////////////////////////////
template <typename F, typename Shape>
struct bulk_async_execute_result
{
typedef typename
boost::range_const_iterator<Shape>::type
iterator_type;
typedef typename
std::iterator_traits<iterator_type>::value_type
value_type;
typedef typename hpx::util::result_of<
typename hpx::util::decay<F>::type(value_type)
>::type type;
};
struct bulk_async_execute_helper
{
template <typename Executor, typename F, typename S>
static std::vector<typename future_type<
Executor, typename bulk_async_execute_result<F, S>::type
>::type>
call(wrap_int, Executor& exec, F && f, S const& shape)
{
std::vector<typename future_type<
Executor,
typename bulk_async_execute_result<F, S>::type
>::type> results;
for (auto const& elem: shape)
{
results.push_back(
exec.async_execute(hpx::util::deferred_call(f, elem))
);
}
return results;
}
template <typename Executor, typename F, typename S>
static auto call(int, Executor& exec, F && f, S const& shape)
-> decltype(exec.bulk_async_execute(std::forward<F>(f), shape))
{
return exec.bulk_async_execute(std::forward<F>(f), shape);
}
};
template <typename Executor, typename F, typename S>
std::vector<typename future_type<
Executor, typename bulk_async_execute_result<F, S>::type
>::type>
call_bulk_async_execute(Executor& exec, F && f, S const& shape)
{
return bulk_async_execute_helper::call(
0, exec, std::forward<F>(f), shape);
}
///////////////////////////////////////////////////////////////////////
template <typename F, typename Shape, typename Enable = void>
struct bulk_execute_result
{
typedef hpx::util::detail::unwrap_impl<
typename detail::bulk_async_execute_result<F, Shape>::type
> type;
};
template <typename F, typename Shape>
struct bulk_execute_result<F, Shape,
typename boost::enable_if_c<
boost::is_void<
typename detail::bulk_async_execute_result<F, Shape>::type
>::value
>::type>
{
typedef void type;
};
struct bulk_execute_helper
{
// returns void if F returns void
template <typename Executor, typename F, typename S>
static typename detail::bulk_execute_result<F, S>::type
call(wrap_int, Executor& exec, F && f, S const& shape)
{
std::vector<typename future_type<
Executor,
typename bulk_async_execute_result<F, S>::type
>::type> results;
for (auto const& elem: shape)
{
results.push_back(
exec.async_execute(hpx::util::deferred_call(f, elem))
);
}
return hpx::util::unwrapped(results);
}
template <typename Executor, typename F, typename S>
static auto call(int, Executor& exec, F && f, S const& shape)
-> decltype(exec.bulk_execute(std::forward<F>(f), shape))
{
return exec.bulk_execute(std::forward<F>(f), shape);
}
};
template <typename Executor, typename F, typename S>
typename detail::bulk_execute_result<F, S>::type
call_bulk_execute(Executor& exec, F && f, S const& shape)
{
return bulk_execute_helper::call(0, exec, std::forward<F>(f), shape);
}
///////////////////////////////////////////////////////////////////////
struct os_thread_count_helper
{
template <typename Executor>
static auto call(wrap_int, Executor& exec) -> std::size_t
{
return hpx::get_os_thread_count();
}
template <typename Executor>
static auto call(int, Executor& exec)
-> decltype(exec.os_thread_count())
{
return exec.os_thread_count();
}
};
template <typename Executor>
std::size_t call_os_thread_count(Executor& exec)
{
return os_thread_count_helper::call(0, exec);
}
///////////////////////////////////////////////////////////////////////
struct has_pending_closures_helper
{
template <typename Executor>
static auto call(wrap_int, Executor& exec) -> bool
{
return false; // assume stateless scheduling
}
template <typename Executor>
static auto call(int, Executor& exec)
-> decltype(exec.has_pending_closures())
{
return exec.has_pending_closures();
}
};
template <typename Executor>
bool call_has_pending_closures(Executor& exec)
{
return has_pending_closures_helper::call(0, exec);
}
/// \endcond
}
///////////////////////////////////////////////////////////////////////////
/// The executor_traits type is used to request execution agents from an
/// executor. It is analogous to the interaction between containers and
/// allocator_traits.
///
/// \note For maximum implementation flexibility, executor_traits does not
/// require executors to implement a particular exception reporting
/// mechanism. Executors may choose whether or not to report
/// exceptions, and if so, in what manner they are communicated back
/// to the caller. However, we expect many executors to report
/// exceptions in a manner consistent with the behavior of execution
/// policies described by the Parallelism TS, where multiple exceptions
/// are collected into an exception_list. This list would be reported
/// through async_execute()'s returned future, or thrown directly by
/// execute().
///
template <typename Executor, typename Enable>
class executor_traits
{
public:
/// The type of the executor associated with this instance of
/// \a executor_traits
typedef Executor executor_type;
/// The category of agents created by the bulk-form execute() and
/// async_execute()
///
/// \note This evaluates to executor_type::execution_category if it
/// exists; otherwise it evaluates to \a parallel_execution_tag.
///
typedef typename detail::execution_category<executor_type>::type
execution_category;
/// The type of future returned by async_execute()
///
/// \note This evaluates to executor_type::future_type<T> if it exists;
/// otherwise it evaluates to \a hpx::future<T>
///
template <typename T>
struct future
{
/// The future type returned from async_execute
typedef typename detail::future_type<executor_type, T>::type type;
};
/// \brief Singleton form of asynchronous fire & forget execution agent
/// creation.
///
/// This asynchronously (fire & forget) creates a single function
/// invocation f() using the associated executor.
///
/// \param exec [in] The executor object to use for scheduling of the
/// function \a f.
/// \param f [in] The function which will be scheduled using the
/// given executor.
///
/// \note This calls exec.apply_execute(f), if available, otherwise
/// it calls exec.async_execute() while discarding the returned
/// future
///
template <typename F>
static void apply_execute(executor_type& exec, F && f)
{
detail::call_apply_execute(exec, std::forward<F>(f));
}
/// \brief Singleton form of asynchronous execution agent creation.
///
/// This asynchronously creates a single function invocation f() using
/// the associated executor.
///
/// \param exec [in] The executor object to use for scheduling of the
/// function \a f.
/// \param f [in] The function which will be scheduled using the
/// given executor.
///
/// \note Executors have to implement only `async_execute()`. All other
/// functions will be emulated by this `executor_traits` in terms
/// of this single basic primitive. However, some executors will
/// naturally specialize all four operations for maximum
/// efficiency.
///
/// \note This calls exec.async_execute(f)
///
/// \returns f()'s result through a future
///
template <typename F>
static typename future<
typename hpx::util::result_of<
typename hpx::util::decay<F>::type()
>::type
>::type
async_execute(executor_type& exec, F && f)
{
return exec.async_execute(std::forward<F>(f));
}
/// \brief Singleton form of synchronous execution agent creation.
///
/// This synchronously creates a single function invocation f() using
/// the associated executor. The execution of the supplied function
/// synchronizes with the caller
///
/// \param exec [in] The executor object to use for scheduling of the
/// function \a f.
/// \param f [in] The function which will be scheduled using the
/// given executor.
///
/// \returns f()'s result through a future
///
/// \note This calls exec.execute(f) if it exists;
/// otherwise hpx::async(f).get()
///
template <typename F>
static typename hpx::util::result_of<
typename hpx::util::decay<F>::type()
>::type
execute(executor_type& exec, F && f)
{
return detail::call_execute(exec, std::forward<F>(f));
}
/// \brief Bulk form of asynchronous execution agent creation
///
/// This asynchronously creates a group of function invocations f(i)
/// whose ordering is given by the execution_category associated with
/// the executor.
///
/// Here \a i takes on all values in the index space implied by shape.
/// All exceptions thrown by invocations of f(i) are reported in a
/// manner consistent with parallel algorithm execution through the
/// returned future.
///
/// \param exec [in] The executor object to use for scheduling of the
/// function \a f.
/// \param f [in] The function which will be scheduled using the
/// given executor.
/// \param shape [in] The shape objects which defines the iteration
/// boundaries for the arguments to be passed to \a f.
///
/// \returns The return type of \a executor_type::async_execute if
/// defined by \a executor_type. Otherwise a vector
/// of futures holding the returned value of each invocation
/// of \a f.
///
/// \note This calls exec.async_execute(f, shape) if it exists;
/// otherwise it executes hpx::async(f, i) as often as needed.
///
template <typename F, typename Shape>
static std::vector<typename future<
typename detail::bulk_async_execute_result<F, Shape>::type
>::type>
async_execute(executor_type& exec, F && f, Shape const& shape)
{
return detail::call_bulk_async_execute(
exec, std::forward<F>(f), shape);
}
/// \brief Bulk form of synchronous execution agent creation
///
/// This synchronously creates a group of function invocations f(i)
/// whose ordering is given by the execution_category associated with
/// the executor. The function synchronizes the execution of all
/// scheduled functions with the caller.
///
/// Here \a i takes on all values in the index space implied by shape.
/// All exceptions thrown by invocations of f(i) are reported in a
/// manner consistent with parallel algorithm execution through the
/// returned future.
///
/// \param exec [in] The executor object to use for scheduling of the
/// function \a f.
/// \param f [in] The function which will be scheduled using the
/// given executor.
/// \param shape [in] The shape objects which defines the iteration
/// boundaries for the arguments to be passed to \a f.
///
/// \returns The return type of \a executor_type::execute if defined
/// by \a executor_type. Otherwise a vector holding the
/// returned value of each invocation of \a f except when
/// \a f returns void, which case void is returned.
///
/// \note This calls exec.execute(f, shape) if it exists;
/// otherwise it executes hpx::async(f, i) as often as needed.
///
template <typename F, typename Shape>
static typename detail::bulk_execute_result<F, Shape>::type
execute(executor_type& exec, F && f, Shape const& shape)
{
return detail::call_bulk_execute(exec, std::forward<F>(f), shape);
}
/// Retrieve the number of (kernel-)threads used by the associated
/// executor.
///
/// \param exec [in] The executor object to use for scheduling of the
/// function \a f.
///
/// \note This calls exec.os_thread_count() if it exists;
/// otherwise it executes hpx::get_os_thread_count().
///
static std::size_t os_thread_count(executor_type const& exec)
{
return detail::call_os_thread_count(exec);
}
/// Retrieve whether this executor has operations pending or not.
///
/// \param exec [in] The executor object to use for scheduling of the
/// function \a f.
///
/// \note If the executor does not expose this information, this call
/// will always return \a false
///
static bool has_pending_closures(executor_type& exec)
{
return detail::call_has_pending_closures(exec);
}
};
///////////////////////////////////////////////////////////////////////////
/// 1. The type is_executor can be used to detect executor types
/// for the purpose of excluding function signatures
/// from otherwise ambiguous overload resolution participation.
/// 2. If T is the type of a standard or implementation-defined executor,
/// is_executor<T> shall be publicly derived from
/// integral_constant<bool, true>, otherwise from
/// integral_constant<bool, false>.
/// 3. The behavior of a program that adds specializations for
/// is_executor is undefined.
///
template <typename T>
struct is_executor; // defined in hpx/traits/is_executor.hpp
}}}
#endif