libjmmcg  release_579_6_g8cffd
A C++ library containing an eclectic mix of useful, advanced components.
parallel_algorithms_impl.hpp
Go to the documentation of this file.
1 /******************************************************************************
2 ** Copyright © 2004 by J.M.McGuiness, coder@hussar.me.uk
3 **
4 ** This library is free software; you can redistribute it and/or
5 ** modify it under the terms of the GNU Lesser General Public
6 ** License as published by the Free Software Foundation; either
7 ** version 2.1 of the License, or (at your option) any later version.
8 **
9 ** This library is distributed in the hope that it will be useful,
10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ** Lesser General Public License for more details.
13 **
14 ** You should have received a copy of the GNU Lesser General Public
15 ** License along with this library; if not, write to the Free Software
16 ** Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 */
18 
19 #include "../../core/thread_pool_aspects.hpp"
20 
21 #include <algorithm>
22 #include <memory>
23 #include <numeric>
24 
25 namespace jmmcg { namespace LIBJMMCG_VER_NAMESPACE { namespace ppd { namespace private_ {
26 
27 namespace alg_wk_wrap {
28 
29  template<class Conts, typename CtrPred> inline void
30  count_if_reduce<Conts, CtrPred>::process() {
31  const typename operation_type::result_type::value_type intermediate=std::count_if(beg, end, fn.input().pred);
32  fn.get_results()+=intermediate;
33  }
34 
35  template<class Conts, typename Fn> inline void
36  accumulate_reduce<Conts, Fn>::process() {
37  const typename operation_type::result_type::value_type intermediate=std::accumulate(beg, end, fn.input().init.get(), fn.input().binop);
38  fn.get_results().apply(intermediate, fn.input().binop);
39  }
40 
41  template<class Conts, typename CtrPred> inline constexpr
42  find_if_reduce<Conts, CtrPred>::find_if_reduce(in_iterator const &b, in_iterator const &e, operation_type &w)
43  : beg(b), end(e), fn(w) {
44  }
45 
46  template<class Conts, typename CtrPred> inline void
47  find_if_reduce<Conts, CtrPred>::process() {
48  if (!fn.get_results()) {
49  const typename operation_type::result_type::value_type intermediate=std::find_if(beg, end, fn.input().pred)!=end;
50  if (intermediate) {
51  fn.get_results()=true;
52  }
53  }
54  }
55 
56  template<class Conts, typename Fn>
57  class max_element_reduce<Conts, Fn>::max : public std::binary_function<typename container_type::value_type, typename container_type::value_type, typename container_type::value_type> {
58  public:
59  typedef std::binary_function<typename container_type::value_type, typename container_type::value_type, typename container_type::value_type> base_t;
62  typedef typename base_t::result_type result_type;
63 
64  explicit constexpr max(operation_type &w) noexcept(true) FORCE_INLINE
65  : fn(w) {}
66 
68  return std::max(lhs, rhs, fn.input().binop);
69  }
70 
71  private:
72  operation_type &fn;
73  };
74 
75  template<class Conts, typename Fn> inline constexpr
77  : beg(b), end(e), fn(w) {
78  }
79 
80  template<class Conts, typename Fn> inline void
81  max_element_reduce<Conts, Fn>::process() {
82  const in_iterator max_in_subrange(std::max_element(beg, end, fn.input().binop));
83  assert(max_in_subrange!=end);
84  const typename operation_type::result_type::value_type intermediate=*max_in_subrange;
85  fn.get_results().apply(intermediate, max(fn));
86  }
87 
88  template<class Conts, typename Fn>
89  class min_element_reduce<Conts, Fn>::min : public std::binary_function<typename container_type::value_type, typename container_type::value_type, typename container_type::value_type> {
90  public:
91  typedef std::binary_function<typename container_type::value_type, typename container_type::value_type, typename container_type::value_type> base_t;
94  typedef typename base_t::result_type result_type;
95 
96  explicit constexpr min(operation_type &w) noexcept(true) FORCE_INLINE
97  : fn(w) {}
98 
100  return std::min(lhs, rhs, fn.input().binop);
101  }
102 
103  private:
104  operation_type &fn;
105  };
106 
107  template<class Conts, typename Fn> inline constexpr
109  : beg(b), end(e), fn(w) {
110  }
111 
112  template<class Conts, typename Fn> inline void
113  min_element_reduce<Conts, Fn>::process() {
114  const in_iterator min_in_subrange(std::min_element(beg, end, fn.input().binop));
115  assert(min_in_subrange!=end);
116  const typename operation_type::result_type::value_type intermediate=*min_in_subrange;
117  fn.get_results().apply(intermediate, min(fn));
118  }
119 
120  template<class Conts, typename Fn> inline constexpr
121  reverse_reduce<Conts, Fn>::reverse_reduce(in_iterator const &bs, in_iterator const &es, operation_type const &w)
122  : beg_subrange(bs), end_subrange(es), fn(w), cont_size(std::distance(fn.input().cont_beg(), fn.input().cont_end())) {
123  }
124 
125  template<class Conts, typename Fn> inline void
126  reverse_reduce<Conts, Fn>::process() const {
127  for (in_iterator lhs(beg_subrange); lhs!=end_subrange; ++lhs) {
128  // fn.input().cont_beg() ... beg_subrange ... lhs ... end_subrange ... middle ... rhs ... fn.input().cont_end()
129  const typename std::iterator_traits<in_iterator>::difference_type dist_from_beg=std::distance(fn.input().cont_beg(), lhs);
130  const in_iterator rhs(std::next(fn.input().cont_beg(), cont_size-dist_from_beg-1));
131  fn.input().binop(lhs, rhs);
132  }
133  }
134 
135  template<typename Conts, class UniOp> inline void
136  fill_n_reduce<Conts, UniOp>::process() const {
137  std::fill(beg, end, val.input().value);
138  }
139 
140  template<typename Conts, class UniOp> inline void
141  fill_reduce<Conts, UniOp>::process() const {
142  std::fill(beg, end, val.input().value);
143  }
144 
145  template<class Conts, typename Pred> inline void
146  swap_ranges_reduce<Conts, Pred>::process() {
147  auto const &op=fn.input().op;
148  for (; begin1!=end1; ++begin1, ++begin2) {
149  if (op.operator()(*begin1, *begin2)) {
150  std::iter_swap(begin1, begin2);
151  }
152  }
153  }
154 
155  template<class Comp, class TPB>
156  template<class CoreWk> inline void
157  merge_work_type<Comp, TPB>::resize_output(CoreWk &wk) noexcept(false) {
158  wk.resize_output(
159  wk.work_complete()->containers().input1.size()+wk.work_complete()->containers().input2.size()
160  );
161  }
162 
163  template<direction Dir, class out_iterator, class Closure> inline bool
164  swap_pred<Dir, out_iterator, Closure>::operator()(typename out_iterator::value_type const &lhs, typename out_iterator::value_type const &rhs) const noexcept(noexcept(std::declval<typename Closure::argument_type>().comp(lhs, rhs))) {
165  return (dir==direction::ascending && !arg.comp(lhs, rhs)) || (dir==direction::descending && arg.comp(lhs, rhs));
166  }
167 
168  template<class Iter, class operation_type, direction LHSDir, direction RHSDir, class Dummy> inline void merge_final_sorter<Iter, operation_type, LHSDir, RHSDir, Dummy>::process(Dummy const &, out_iterator const begin, out_iterator const end, operation_type const &fn) noexcept(false) {
169  typedef std::reverse_iterator<out_iterator> rev_in;
170 
171  const out_sz_t size_of_portion=std::distance(begin, end);
172  const out_sz_t half_size_of_portion=size_of_portion>>1;
173  const out_iterator middle(std::next(begin, half_size_of_portion));
174  std::vector<typename out_iterator::value_type> out_colln;
175  out_colln.reserve(size_of_portion);
176  // TODO testing performance... std::merge(begin, middle, rev_in(end), rev_in(middle), begin, arg3_type(swapper_t(fn)));
177  std::merge(begin, middle, rev_in(end), rev_in(middle), std::back_inserter(out_colln), arg3_type(swapper_t(fn)));
179  std::move(out_colln.begin(), out_colln.end(), begin);
180  } else {
181  std::move(rev_in(out_colln.end()), rev_in(out_colln.begin()), begin);
182  }
183  }
184 
185  template<class Conts, typename Comp>
186  template<direction LHSDir, direction RHSDir, template<class, class, direction, direction, class> class FinalSort>
187  class batchers_bitonic_merge_reduce<Conts, Comp>::merge {
188  public:
189  static constexpr direction lhs_dir=LHSDir; ///< The general direction of the comparator.
190  static constexpr direction rhs_dir=RHSDir; ///< The direction that the rhs of the sort should be flipped, because for merges the sequence is bitonic, so the rhs sub-ranges need to be flipped.
192  typedef boost::function<void (out_iterator, out_iterator, std::binary_negate<swapper_t>)> sort_fn_t;
193 
194  /**
195  To assist in allowing compile-time computation of the algorithmic order of the threading model.
196  */
197  static constexpr ppd::generic_traits::memory_access_modes memory_access_mode=thread_pool_type::template swap_ranges_t<typename containers_type::output_t::container_type, swapper_t>::memory_access_mode;
198 
199  constexpr merge(out_iterator b, out_iterator e, operation_type const &f, sort_fn_t const &sfn, cliques::element_type const cl) noexcept(true) FORCE_INLINE
200  : sorter(sfn), begin(b), end(e), fn(f), clique(cl) {
201  }
202  virtual ~merge() FORCE_INLINE {}
203 
204  /**
205  \todo JMG: this needs extending to non-even sized collections...
206  */
207  void __fastcall process() const {
208  typedef merge<lhs_dir, lhs_dir, FinalSort> lhs_sub_merge;
209  typedef merge<lhs_dir, lhs_dir==direction::ascending ? direction::descending : direction::ascending, FinalSort> rhs_sub_merge;
210 
211  const out_sz_t size_of_portion=std::distance(begin, end);
212  if (
213  size_of_portion>3
214  && (
216  || clique<fn.input().pool.pool_size()
217  )
218  ) {
219  const out_sz_t half_size_of_portion=size_of_portion>>1;
220  const out_iterator middle(std::next(begin, half_size_of_portion));
221  const out_iterator ak(middle);
222  assert(begin!=middle);
223  assert(middle!=end);
224  auto const &shuffle=fn.input().pool<<joinable(this, shuffle_str)<<cliques(clique)<<fn.input().pool.template swap_ranges<typename containers_type::output_t::container_type>(begin, middle, ak, swapper_t(fn)); // O(half_size_of_portion/p+log(p))
225  *shuffle;
226  auto const &merge_lhs=fn.input().pool<<joinable(this, lhs_merge_str)<<lhs_sub_merge(begin, middle, fn, sorter, clique<<1); // O(log(half_size_of_portion))
227  auto const &merge_rhs=fn.input().pool<<joinable(this, rhs_merge_str)<<rhs_sub_merge(middle, end, fn, sorter, clique<<1); // O(log(half_size_of_portion))
228  *merge_lhs;
229  *merge_rhs;
230  } else {
231  typedef FinalSort<out_iterator, operation_type, lhs_dir, rhs_dir, sort_fn_t> final_sort;
232  final_sort::process(sorter, begin, end, fn);
233  }
234  }
235 
236  constexpr bool __fastcall operator<(merge const &) const noexcept(true) FORCE_INLINE {
237  return true;
238  }
239 
240  private:
241  sort_fn_t const sorter;
242  out_iterator const begin;
243  out_iterator const end;
244  operation_type const &fn;
245  cliques::element_type const clique;
246  };
247 
248  template<class Conts, typename Comp> inline void
249  batchers_bitonic_merge_reduce<Conts, Comp>::combine() {
250  typedef std::reverse_iterator<typename containers_type::input2_t::iterator> rev_in2;
251 
252  assert((conts.input1.size()+conts.input2.size())==conts.output.size());
253 
254  auto const &first=fn.input().pool<<joinable(this, combine1_str)<<cliques(clique<<1)<<fn.input().pool.template copy<typename containers_type::input1_t::container_type, typename containers_type::output_t::container_type>(conts.input1.begin(), conts.input1.end(), conts.output.begin());
255  const out_iterator middle(std::next(conts.output.begin(), conts.input1.size()));
256  auto const &second=fn.input().pool<<joinable(this, combine2_str)<<cliques(clique<<1)<<fn.input().pool.template copy<typename containers_type::input2_t::container_type, typename containers_type::output_t::container_type>(rev_in2(conts.input2.end()), rev_in2(conts.input2.begin()), middle);
257  *first;
258  *second;
259  }
260 
261  template<class Conts, typename Comp> inline
263  : conts(c), fn(w), clique(cl) {
264  }
265 
266  template<class Conts, typename Comp> inline void
268  typedef typename thread_pool_type::template create_direct<init_merger_t> init_merger_creator_t;
269 
270  if (conts.input1.size()!=conts.input2.size()) {
271  info::function fun(
272  __LINE__,
273  __PRETTY_FUNCTION__,
275  info::function::argument(_T("containers_type"), exception_type::thread_traits::demangle_name(typeid(containers_type)))
276  );
277  fun.add_args(
278  info::function::argument(_T("conts.input1.size()"), conts.input1.size()),
279  info::function::argument(_T("conts.input2.size()"), conts.input2.size())
280  );
281  throw exception_type(_T("Both input collections must be the same size."), std::move(fun), info::revision(_T(LIBJMMCG_VERSION_NUMBER)));
282  }
283  if ((conts.input1.size()+conts.input2.size())%2) {
284  info::function fun(
285  __LINE__,
286  __PRETTY_FUNCTION__,
288  info::function::argument(_T("containers_type"), exception_type::thread_traits::demangle_name(typeid(containers_type)))
289  );
290  fun.add_args(
291  info::function::argument(_T("conts.input1.size()"), conts.input1.size()),
292  info::function::argument(_T("conts.input2.size()"), conts.input2.size())
293  );
294  throw exception_type(_T("Both input collections must have sizes that are a power of two."), std::move(fun), info::revision(_T(LIBJMMCG_VERSION_NUMBER)));
295  }
296  combine();
297  auto sorter=[](auto b, auto e, auto s) {
298  return std::stable_sort<typename sort_fn_t::arg1_type, typename sort_fn_t::arg3_type>(b, e, s);
299  };
300  typename init_merger_creator_t::closure_t all(init_merger_t(conts.output.begin(), conts.output.end(), fn, sorter, clique), typename pool_traits_type::thread_wk_elem_type::cfg_details_type::params(fn.input().pool.cfg(), this, "init_merger"));
301  all.update_edge(thread_pool_type::cfg_type::sequential_edge_annotation);
302  // TODO Set start & end times of processing op, so that time-order of nodes in DFG can be seen.
303  all.process();
304  }
305 
306  template<typename Conts, class Comp>
307  template<direction dir>
308  class bitonic_sort_reduce<Conts, Comp>::sort {
309  public:
310  /**
311  Make sure we use the correct algorithm in the merge operation to ensure that the sub-collection is correctly sorted.
312 
313  \see sort_final_sorter
314  */
315  typedef typename merge_t::template merge<dir, dir, sort_final_sorter> merge_in_dir_t;
317 
318  /**
319  To assist in allowing compile-time computation of the algorithmic order of the threading model.
320  */
322 
323  /**
324  Complexity: O(log^2(O(m_sfn)/p+log(p)))
325  */
326  constexpr sort(in_iterator b, in_iterator e, operation_type const &f, cliques::element_type const cl) noexcept(true) FORCE_INLINE
327  : begin(b), end(e), fn(f), clique(cl) {
328  }
329  virtual ~sort() noexcept(true) FORCE_INLINE {}
330 
331  /**
332  \todo JMG: this needs extending to non-even sized collections...
333 
334  If std::stable_sort() is used then on average this is O(n.log(n)), with enough memory.
335  */
336  void __fastcall process() const {
337  typedef typename operation_type::argument_type::thread_pool_type::joinable joinable;
338 
339  const typename in_iterator::difference_type size_of_portion=std::distance(begin, end);
340  auto merge_sorter=[](auto b, auto e, auto s) {
341  return std::stable_sort<typename merge_sort_fn_t::arg1_type, typename merge_sort_fn_t::arg3_type>(b, e, s);
342  };
343  if (
344  size_of_portion>3
345  && (
347  || clique<fn.input().pool.pool_size()
348  )
349  ) {
350  const typename in_iterator::difference_type half_size_of_portion=size_of_portion>>1;
351  const in_iterator middle(std::next(begin, half_size_of_portion));
352  auto const &sort_lhs_ascending=fn.input().pool<<joinable(this, ascending_lhs_str)<<sort<direction::ascending>(begin, middle, fn, clique<<1/* TODO , merge_sorter*/); // O(s(half_size_of_portion))
353  auto const &sort_rhs_descending=fn.input().pool<<joinable(this, descending_rhs_str)<<sort<direction::descending>(middle, end, fn, clique<<1/* TODO, merge_sorter*/); // O(s(half_size_of_portion))
354  *sort_lhs_ascending;
355  *sort_rhs_descending;
356  auto const &bitonic_merge_all=fn.input().pool<<joinable(this, merge_str)<<merge_in_dir_t(begin, end, fn, merge_sorter, clique); // O(m(half_size_of_portion))
357  *bitonic_merge_all;
358  } else {
359  merge_sorter(begin, end, typename merge_sort_fn_t::arg3_type(swap_pred<dir, in_iterator, operation_type>(fn)));
360  }
361  }
362 
363  constexpr bool __fastcall operator<(sort const &) const noexcept(true) FORCE_INLINE {
364  return true;
365  }
366 
367  private:
368  in_iterator const begin;
369  in_iterator const end;
370  operation_type const &fn;
371  cliques::element_type const clique;
372  };
373 
374  template<typename Conts, class Comp> inline constexpr
375  bitonic_sort_reduce<Conts, Comp>::bitonic_sort_reduce(containers_type &c, operation_type const &op, cliques::element_type const cl) noexcept(true)
376  : cont(c), fn(op), clique(cl) {
377  }
378 
379  template<typename Conts, class Comp> inline void
380  bitonic_sort_reduce<Conts, Comp>::process() const {
381  typedef typename thread_pool_type::template create_direct<init_sorter_t> init_sort_creator_t;
382 
383  typename init_sort_creator_t::closure_t all(init_sorter_t(cont.input1.begin(), cont.input1.end(), fn, clique), typename pool_traits_type::thread_wk_elem_type::cfg_details_type::params(fn.input().pool.cfg(), this, "init_sorter"));
384  all.update_edge(thread_pool_type::cfg_type::sequential_edge_annotation);
385  // TODO Set start & end times of processing op, so that time-order of nodes in DFG can be seen.
386  all.process();
387  }
388 
389 }
390 
391 template<class ArgT, class UniFn, class PT>
392 struct unary_fun_work_type<ArgT, UniFn, PT>::arg_int_work_type {
393  typedef typename unary_fun_work_type::result_type result_type;
394 
395  /**
396  To assist in allowing compile-time computation of the algorithmic order of the threading model.
397  */
399 
400  explicit constexpr __stdcall arg_int_work_type(argument_type &&a) FORCE_INLINE
401  : arg(std::forward<argument_type>(a)) {
402  }
403  void __fastcall process(result_type &r) FORCE_INLINE {
404  arg.process(r.result);
405  }
406 
407  bool __fastcall operator<(arg_int_work_type const &rhs) const FORCE_INLINE {
408  return arg<rhs.arg;
409  }
410  template<class Arg1> constexpr bool __fastcall FORCE_INLINE
411  operator<(Arg1 const &) const noexcept(true) {
412  return true;
413  }
414 
415 private:
416  argument_type arg;
417 };
418 
419 template<class ArgT, class UniFn, class PT>
420 struct unary_fun_work_type<ArgT, UniFn, PT>::arg_context_t : public sp_counter_type<long, typename pool_type::os_traits::lock_traits> {
421  typedef typename pool_type::joinable joinable;
422  typedef typename pool_type::template execution_context_stack<arg_int_work_type> execution_context;
423 
424  /**
425  To assist in allowing compile-time computation of the algorithmic order of the threading model.
426  */
431  );
432 
434 
436  : arg(pool<<joinable(this, arg_str)<<arg_int_work_type(std::forward<argument_type>(a))) {
437  }
438  ~arg_context_t() noexcept(true) FORCE_INLINE {}
439 };
440 
441 template<class ArgT, class UniFn, class PT> inline
442 unary_fun_work_type<ArgT, UniFn, PT>::unary_fun_work_type(argument_type &&a, operation_type const &o, pool_type &pool) noexcept(false)
443 : op(o), arg_cxt(new arg_context_t(std::forward<argument_type>(a), pool)) {
444 }
445 
446 template<class ArgT, class UniFn, class PT> inline void
447 unary_fun_work_type<ArgT, UniFn, PT>::process(result_type &r) {
448  assert(dynamic_cast<arg_context_t *>(arg_cxt.get().get()));
449  r.result=op.operator()(arg_cxt->arg->result);
450 }
451 
452 template<class ArgT, class UniFn, class PT> inline bool
453 unary_fun_work_type<ArgT, UniFn, PT>::operator<(unary_fun_work_type const &rhs) const noexcept(true) {
454  return *arg_cxt->arg.wk_queue_item()<*rhs.arg_cxt->arg.wk_queue_item();
455 }
456 
457 template<class ArgT1, class ArgT2, class BinFn, class PT>
458 template<class Arg>
459 struct binary_fun_work_type<ArgT1, ArgT2, BinFn, PT>::arg_int_work_type {
460  typedef typename binary_fun_work_type::result_type result_type;
461  typedef Arg argument_type;
462 
463  /**
464  To assist in allowing compile-time computation of the algorithmic order of the threading model.
465  */
467 
468  explicit constexpr __stdcall arg_int_work_type(argument_type &&a) FORCE_INLINE
469  : arg(std::forward<argument_type>(a)) {
470  }
471  void __fastcall process(result_type &r) FORCE_INLINE {
472  arg.process(r.result);
473  }
474 
475  bool __fastcall operator<(arg_int_work_type const &rhs) const FORCE_INLINE {
476  return arg<rhs.arg;
477  }
478  template<class Arg1> constexpr bool __fastcall FORCE_INLINE
479  operator<(Arg1 const &) const noexcept(true) {
480  return true;
481  }
482 
483 private:
484  argument_type arg;
485 };
486 
487 template<class ArgT1, class ArgT2, class BinFn, class PT>
488 struct binary_fun_work_type<ArgT1, ArgT2, BinFn, PT>::arg_contexts_t : public sp_counter_type<long, typename pool_type::os_traits::lock_traits> {
489  typedef typename pool_type::joinable first_joinable;
490  typedef typename pool_type::template execution_context_stack<arg_int_work_type<first_argument_type>> first_execution_context;
491  typedef typename pool_type::joinable second_joinable;
492  typedef typename pool_type::template execution_context_stack<arg_int_work_type<second_argument_type>> second_execution_context;
493 
494  /**
495  To assist in allowing compile-time computation of the algorithmic order of the threading model.
496  */
502  );
503 
506 
508  : first_arg(pool<<first_joinable(this, lhs_str)<<arg_int_work_type<first_argument_type>(std::forward<first_argument_type>(lhs))),
509  second_arg(pool<<second_joinable(this, rhs_str)<<arg_int_work_type<second_argument_type>(std::forward<second_argument_type>(rhs))) {
510  }
511  ~arg_contexts_t() noexcept(true) FORCE_INLINE {}
512 };
513 
514 template<class ArgT1, class ArgT2, class BinFn, class PT> inline
515 binary_fun_work_type<ArgT1, ArgT2, BinFn, PT>::binary_fun_work_type(first_argument_type &&lhs, second_argument_type &&rhs, operation_type const &o, pool_type &pool) noexcept(false)
516 : op(o), arg_cxts(new arg_contexts_t(std::forward<first_argument_type>(lhs), std::forward<second_argument_type>(rhs), pool)) {
517 }
518 
519 template<class ArgT1, class ArgT2, class BinFn, class PT> inline void
520 binary_fun_work_type<ArgT1, ArgT2, BinFn, PT>::process(result_type &r) {
521  assert(dynamic_cast<arg_contexts_t *>(arg_cxts.get().get()));
522  r.result=op.operator()(arg_cxts->first_arg->result, arg_cxts->second_arg->result);
523 }
524 
525 template<class ArgT1, class ArgT2, class BinFn, class PT> inline bool
526 binary_fun_work_type<ArgT1, ArgT2, BinFn, PT>::operator<(binary_fun_work_type const &rhs) const noexcept(true) {
527  return *arg_cxts->first_arg.wk_queue_item()<*rhs.arg_cxts->second_arg.wk_queue_item() || (*arg_cxts->first_arg.wk_queue_item()==*rhs.arg_cxts->first_arg.wk_queue_item() && *arg_cxts->second_arg.wk_queue_item()<*rhs.arg_cxts->second_arg.wk_queue_item());
528 }
529 
530 } } } }