adaptive_merge.hpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. //////////////////////////////////////////////////////////////////////////////
  2. //
  3. // (C) Copyright Ion Gaztanaga 2015-2016.
  4. // Distributed under the Boost Software License, Version 1.0.
  5. // (See accompanying file LICENSE_1_0.txt or copy at
  6. // http://www.boost.org/LICENSE_1_0.txt)
  7. //
  8. // See http://www.boost.org/libs/move for documentation.
  9. //
  10. //////////////////////////////////////////////////////////////////////////////
  11. #ifndef BHO_MOVE_ADAPTIVE_MERGE_HPP
  12. #define BHO_MOVE_ADAPTIVE_MERGE_HPP
  13. #include <asio2/bho/move/detail/config_begin.hpp>
  14. #include <asio2/bho/move/algo/detail/adaptive_sort_merge.hpp>
  15. #include <cassert>
  16. #if defined(BHO_CLANG) || (defined(BHO_GCC) && (BHO_GCC >= 40600))
  17. #pragma GCC diagnostic push
  18. #pragma GCC diagnostic ignored "-Wsign-conversion"
  19. #endif
  20. namespace bho {
  21. namespace movelib {
  22. ///@cond
  23. namespace detail_adaptive {
  24. template<class RandIt, class Compare, class XBuf>
  25. inline void adaptive_merge_combine_blocks( RandIt first
  26. , typename iter_size<RandIt>::type len1
  27. , typename iter_size<RandIt>::type len2
  28. , typename iter_size<RandIt>::type collected
  29. , typename iter_size<RandIt>::type n_keys
  30. , typename iter_size<RandIt>::type l_block
  31. , bool use_internal_buf
  32. , bool xbuf_used
  33. , Compare comp
  34. , XBuf & xbuf
  35. )
  36. {
  37. typedef typename iter_size<RandIt>::type size_type;
  38. size_type const len = size_type(len1+len2);
  39. size_type const l_combine = size_type(len-collected);
  40. size_type const l_combine1 = size_type(len1-collected);
  41. if(n_keys){
  42. RandIt const first_data = first+collected;
  43. RandIt const keys = first;
  44. BHO_MOVE_ADAPTIVE_SORT_PRINT_L2(" A combine: ", len);
  45. if(xbuf_used){
  46. if(xbuf.size() < l_block){
  47. xbuf.initialize_until(l_block, *first);
  48. }
  49. assert(xbuf.size() >= l_block);
  50. size_type n_block_a, n_block_b, l_irreg1, l_irreg2;
  51. combine_params( keys, comp, l_combine
  52. , l_combine1, l_block, xbuf
  53. , n_block_a, n_block_b, l_irreg1, l_irreg2); //Outputs
  54. op_merge_blocks_with_buf
  55. (keys, comp, first_data, l_block, l_irreg1, n_block_a, n_block_b, l_irreg2, comp, move_op(), xbuf.data());
  56. BHO_MOVE_ADAPTIVE_SORT_PRINT_L1(" A mrg xbf: ", len);
  57. }
  58. else{
  59. size_type n_block_a, n_block_b, l_irreg1, l_irreg2;
  60. combine_params( keys, comp, l_combine
  61. , l_combine1, l_block, xbuf
  62. , n_block_a, n_block_b, l_irreg1, l_irreg2); //Outputs
  63. if(use_internal_buf){
  64. op_merge_blocks_with_buf
  65. ( keys, comp, first_data, l_block, l_irreg1, n_block_a, n_block_b
  66. , l_irreg2, comp, swap_op(), first_data-l_block);
  67. BHO_MOVE_ADAPTIVE_SORT_PRINT_L2(" A mrg buf: ", len);
  68. }
  69. else{
  70. merge_blocks_bufferless
  71. (keys, comp, first_data, l_block, l_irreg1, n_block_a, n_block_b, l_irreg2, comp);
  72. BHO_MOVE_ADAPTIVE_SORT_PRINT_L1(" A mrg nbf: ", len);
  73. }
  74. }
  75. }
  76. else{
  77. xbuf.shrink_to_fit(l_block);
  78. if(xbuf.size() < l_block){
  79. xbuf.initialize_until(l_block, *first);
  80. }
  81. size_type *const uint_keys = xbuf.template aligned_trailing<size_type>(l_block);
  82. size_type n_block_a, n_block_b, l_irreg1, l_irreg2;
  83. combine_params( uint_keys, less(), l_combine
  84. , l_combine1, l_block, xbuf
  85. , n_block_a, n_block_b, l_irreg1, l_irreg2, true); //Outputs
  86. BHO_MOVE_ADAPTIVE_SORT_PRINT_L2(" A combine: ", len);
  87. assert(xbuf.size() >= l_block);
  88. op_merge_blocks_with_buf
  89. (uint_keys, less(), first, l_block, l_irreg1, n_block_a, n_block_b, l_irreg2, comp, move_op(), xbuf.data());
  90. xbuf.clear();
  91. BHO_MOVE_ADAPTIVE_SORT_PRINT_L1(" A mrg buf: ", len);
  92. }
  93. }
  94. template<class RandIt, class Compare, class XBuf>
  95. inline void adaptive_merge_final_merge( RandIt first
  96. , typename iter_size<RandIt>::type len1
  97. , typename iter_size<RandIt>::type len2
  98. , typename iter_size<RandIt>::type collected
  99. , typename iter_size<RandIt>::type l_intbuf
  100. , typename iter_size<RandIt>::type //l_block
  101. , bool //use_internal_buf
  102. , bool xbuf_used
  103. , Compare comp
  104. , XBuf & xbuf
  105. )
  106. {
  107. typedef typename iter_size<RandIt>::type size_type;
  108. size_type n_keys = size_type(collected-l_intbuf);
  109. size_type len = size_type(len1+len2);
  110. if (!xbuf_used || n_keys) {
  111. xbuf.clear();
  112. const size_type middle = xbuf_used && n_keys ? n_keys: collected;
  113. unstable_sort(first, first + middle, comp, xbuf);
  114. BHO_MOVE_ADAPTIVE_SORT_PRINT_L2(" A k/b srt: ", len);
  115. stable_merge(first, first + middle, first + len, comp, xbuf);
  116. }
  117. BHO_MOVE_ADAPTIVE_SORT_PRINT_L1(" A fin mrg: ", len);
  118. }
  119. template<class SizeType>
  120. inline static SizeType adaptive_merge_n_keys_without_external_keys(SizeType l_block, SizeType len1, SizeType len2, SizeType l_intbuf)
  121. {
  122. typedef SizeType size_type;
  123. //This is the minimum number of keys to implement the ideal algorithm
  124. size_type n_keys = size_type(len1/l_block + len2/l_block);
  125. const size_type second_half_blocks = size_type(len2/l_block);
  126. const size_type first_half_aux = size_type(len1 - l_intbuf);
  127. while(n_keys >= ((first_half_aux-n_keys)/l_block + second_half_blocks)){
  128. --n_keys;
  129. }
  130. ++n_keys;
  131. return n_keys;
  132. }
  133. template<class SizeType>
  134. inline static SizeType adaptive_merge_n_keys_with_external_keys(SizeType l_block, SizeType len1, SizeType len2, SizeType l_intbuf)
  135. {
  136. typedef SizeType size_type;
  137. //This is the minimum number of keys to implement the ideal algorithm
  138. size_type n_keys = size_type((len1-l_intbuf)/l_block + len2/l_block);
  139. return n_keys;
  140. }
  141. template<class SizeType, class Xbuf>
  142. inline SizeType adaptive_merge_n_keys_intbuf(SizeType &rl_block, SizeType len1, SizeType len2, Xbuf & xbuf, SizeType &l_intbuf_inout)
  143. {
  144. typedef SizeType size_type;
  145. size_type l_block = rl_block;
  146. size_type l_intbuf = xbuf.capacity() >= l_block ? 0u : l_block;
  147. if (xbuf.capacity() > l_block){
  148. l_block = xbuf.capacity();
  149. }
  150. //This is the minimum number of keys to implement the ideal algorithm
  151. size_type n_keys = adaptive_merge_n_keys_without_external_keys(l_block, len1, len2, l_intbuf);
  152. assert(n_keys >= ((len1-l_intbuf-n_keys)/l_block + len2/l_block));
  153. if(xbuf.template supports_aligned_trailing<size_type>
  154. ( l_block
  155. , adaptive_merge_n_keys_with_external_keys(l_block, len1, len2, l_intbuf)))
  156. {
  157. n_keys = 0u;
  158. }
  159. l_intbuf_inout = l_intbuf;
  160. rl_block = l_block;
  161. return n_keys;
  162. }
  163. // Main explanation of the merge algorithm.
  164. //
  165. // csqrtlen = ceil(sqrt(len));
  166. //
  167. // * First, csqrtlen [to be used as buffer] + (len/csqrtlen - 1) [to be used as keys] => to_collect
  168. // unique elements are extracted from elements to be sorted and placed in the beginning of the range.
  169. //
  170. // * Step "combine_blocks": the leading (len1-to_collect) elements plus trailing len2 elements
  171. // are merged with a non-trivial ("smart") algorithm to form an ordered range trailing "len-to_collect" elements.
  172. //
  173. // Explanation of the "combine_blocks" step:
  174. //
  175. // * Trailing [first+to_collect, first+len1) elements are divided in groups of cqrtlen elements.
  176. // Remaining elements that can't form a group are grouped in front of those elements.
  177. // * Trailing [first+len1, first+len1+len2) elements are divided in groups of cqrtlen elements.
  178. // Remaining elements that can't form a group are grouped in the back of those elements.
  179. // * In parallel the following two steps are performed:
  180. // * Groups are selection-sorted by first or last element (depending whether they are going
  181. // to be merged to left or right) and keys are reordered accordingly as an imitation-buffer.
  182. // * Elements of each block pair are merged using the csqrtlen buffer taking into account
  183. // if they belong to the first half or second half (marked by the key).
  184. //
  185. // * In the final merge step leading "to_collect" elements are merged with rotations
  186. // with the rest of merged elements in the "combine_blocks" step.
  187. //
  188. // Corner cases:
  189. //
  190. // * If no "to_collect" elements can be extracted:
  191. //
  192. // * If more than a minimum number of elements is extracted
  193. // then reduces the number of elements used as buffer and keys in the
  194. // and "combine_blocks" steps. If "combine_blocks" has no enough keys due to this reduction
  195. // then uses a rotation based smart merge.
  196. //
  197. // * If the minimum number of keys can't be extracted, a rotation-based merge is performed.
  198. //
  199. // * If auxiliary memory is more or equal than min(len1, len2), a buffered merge is performed.
  200. //
  201. // * If the len1 or len2 are less than 2*csqrtlen then a rotation-based merge is performed.
  202. //
  203. // * If auxiliary memory is more than csqrtlen+n_keys*sizeof(std::size_t),
  204. // then no csqrtlen need to be extracted and "combine_blocks" will use integral
  205. // keys to combine blocks.
  206. template<class RandIt, class Compare, class XBuf>
  207. void adaptive_merge_impl
  208. ( RandIt first
  209. , typename iter_size<RandIt>::type len1
  210. , typename iter_size<RandIt>::type len2
  211. , Compare comp
  212. , XBuf & xbuf
  213. )
  214. {
  215. typedef typename iter_size<RandIt>::type size_type;
  216. if(xbuf.capacity() >= min_value<size_type>(len1, len2)){
  217. buffered_merge( first, first+len1
  218. , first + len1+len2, comp, xbuf);
  219. }
  220. else{
  221. const size_type len = size_type(len1+len2);
  222. //Calculate ideal parameters and try to collect needed unique keys
  223. size_type l_block = size_type(ceil_sqrt(len));
  224. //One range is not big enough to extract keys and the internal buffer so a
  225. //rotation-based based merge will do just fine
  226. if(len1 <= l_block*2 || len2 <= l_block*2){
  227. merge_bufferless(first, first+len1, first+len1+len2, comp);
  228. return;
  229. }
  230. //Detail the number of keys and internal buffer. If xbuf has enough memory, no
  231. //internal buffer is needed so l_intbuf will remain 0.
  232. size_type l_intbuf = 0;
  233. size_type n_keys = adaptive_merge_n_keys_intbuf(l_block, len1, len2, xbuf, l_intbuf);
  234. size_type const to_collect = size_type(l_intbuf+n_keys);
  235. //Try to extract needed unique values from the first range
  236. size_type const collected = collect_unique(first, first+len1, to_collect, comp, xbuf);
  237. BHO_MOVE_ADAPTIVE_SORT_PRINT_L1("\n A collect: ", len);
  238. //Not the minimum number of keys is not available on the first range, so fallback to rotations
  239. if(collected != to_collect && collected < 4){
  240. merge_bufferless(first, first+collected, first+len1, comp);
  241. merge_bufferless(first, first + len1, first + len1 + len2, comp);
  242. return;
  243. }
  244. //If not enough keys but more than minimum, adjust the internal buffer and key count
  245. bool use_internal_buf = collected == to_collect;
  246. if (!use_internal_buf){
  247. l_intbuf = 0u;
  248. n_keys = collected;
  249. l_block = lblock_for_combine(l_intbuf, n_keys, len, use_internal_buf);
  250. //If use_internal_buf is false, then then internal buffer will be zero and rotation-based combination will be used
  251. l_intbuf = use_internal_buf ? l_block : 0u;
  252. }
  253. bool const xbuf_used = collected == to_collect && xbuf.capacity() >= l_block;
  254. //Merge trailing elements using smart merges
  255. adaptive_merge_combine_blocks(first, len1, len2, collected, n_keys, l_block, use_internal_buf, xbuf_used, comp, xbuf);
  256. //Merge buffer and keys with the rest of the values
  257. adaptive_merge_final_merge (first, len1, len2, collected, l_intbuf, l_block, use_internal_buf, xbuf_used, comp, xbuf);
  258. }
  259. }
  260. } //namespace detail_adaptive {
  261. ///@endcond
  262. //! <b>Effects</b>: Merges two consecutive sorted ranges [first, middle) and [middle, last)
  263. //! into one sorted range [first, last) according to the given comparison function comp.
  264. //! The algorithm is stable (if there are equivalent elements in the original two ranges,
  265. //! the elements from the first range (preserving their original order) precede the elements
  266. //! from the second range (preserving their original order).
  267. //!
  268. //! <b>Requires</b>:
  269. //! - RandIt must meet the requirements of ValueSwappable and RandomAccessIterator.
  270. //! - The type of dereferenced RandIt must meet the requirements of MoveAssignable and MoveConstructible.
  271. //!
  272. //! <b>Parameters</b>:
  273. //! - first: the beginning of the first sorted range.
  274. //! - middle: the end of the first sorted range and the beginning of the second
  275. //! - last: the end of the second sorted range
  276. //! - comp: comparison function object which returns true if the first argument is is ordered before the second.
  277. //! - uninitialized, uninitialized_len: raw storage starting on "uninitialized", able to hold "uninitialized_len"
  278. //! elements of type iterator_traits<RandIt>::value_type. Maximum performance is achieved when uninitialized_len
  279. //! is min(std::distance(first, middle), std::distance(middle, last)).
  280. //!
  281. //! <b>Throws</b>: If comp throws or the move constructor, move assignment or swap of the type
  282. //! of dereferenced RandIt throws.
  283. //!
  284. //! <b>Complexity</b>: Always K x O(N) comparisons and move assignments/constructors/swaps.
  285. //! Constant factor for comparisons and data movement is minimized when uninitialized_len
  286. //! is min(std::distance(first, middle), std::distance(middle, last)).
  287. //! Pretty good enough performance is achieved when uninitialized_len is
  288. //! ceil(sqrt(std::distance(first, last)))*2.
  289. //!
  290. //! <b>Caution</b>: Experimental implementation, not production-ready.
  291. template<class RandIt, class Compare>
  292. void adaptive_merge( RandIt first, RandIt middle, RandIt last, Compare comp
  293. , typename iterator_traits<RandIt>::value_type* uninitialized = 0
  294. , typename iter_size<RandIt>::type uninitialized_len = 0)
  295. {
  296. typedef typename iter_size<RandIt>::type size_type;
  297. typedef typename iterator_traits<RandIt>::value_type value_type;
  298. if (first == middle || middle == last){
  299. return;
  300. }
  301. //Reduce ranges to merge if possible
  302. do {
  303. if (comp(*middle, *first)){
  304. break;
  305. }
  306. ++first;
  307. if (first == middle)
  308. return;
  309. } while(1);
  310. RandIt first_high(middle);
  311. --first_high;
  312. do {
  313. --last;
  314. if (comp(*last, *first_high)){
  315. ++last;
  316. break;
  317. }
  318. if (last == middle)
  319. return;
  320. } while(1);
  321. ::bho::movelib::adaptive_xbuf<value_type, value_type*, size_type> xbuf(uninitialized, size_type(uninitialized_len));
  322. ::bho::movelib::detail_adaptive::adaptive_merge_impl(first, size_type(middle - first), size_type(last - middle), comp, xbuf);
  323. }
  324. } //namespace movelib {
  325. } //namespace bho {
  326. #if defined(BHO_CLANG) || (defined(BHO_GCC) && (BHO_GCC >= 40600))
  327. #pragma GCC diagnostic pop
  328. #endif
  329. #include <asio2/bho/move/detail/config_end.hpp>
  330. #endif //#define BHO_MOVE_ADAPTIVE_MERGE_HPP