├── README.rst ├── measure.cc ├── parallel_string_radix_sort.h ├── parallel_string_radix_sort_test.cc └── sample.cc /README.rst: -------------------------------------------------------------------------------- 1 | 概要 2 | ---- 3 | MSD Radix Sort による 文字列ソートを OpenMP を用いて並列化した実装です. 4 | const char* の配列か,std::string の配列をソートできます. 5 | 6 | 参考文献の論文に書かれている高速化手法である 7 | Loop Fission や Super-Alphabet 等のテクニックや, 8 | 配列のコピーを減らす工夫等が導入されています. 9 | 10 | 使い方 11 | ------ 12 | sample.cc や measure.cc を見ると大体分かると思います. 13 | 14 | コンパイル時に -fopenmp を付けないと並列化されないので注意してください. 15 | 16 | 逆に,-fopenmp を付けないで,並列化せずに使用することもできます. 17 | そのような状況でも std::sort より有意に高速だと思います. 18 | 19 | 性能 20 | ---- 21 | measure.cc で 3 千万個のランダム文字列 (const char*) のソートの時間を測ります. 22 | 文字列の長さは 1 から 30 文字の一様分布です. 23 | 24 | 実行例:: 25 | 26 | % g++ -O3 -fopenmp measure.cc 27 | % ./a.out 28 | ParallelStringRadixSort::Sort(0): 1.072459 sec 29 | ParallelStringRadixSort::Sort(1): 1.252914 sec 30 | ParallelStringRadixSort::Sort(2): 1.249938 sec 31 | std::sort(0): 24.678047 sec 32 | std::sort(1): 25.407672 sec 33 | std::sort(2): 24.269998 sec 34 | 35 | 上の実行例は Intel Core i7 CPU 960 (3.20 GHz) 上で実行しました. 36 | 37 | 余談 38 | ---- 39 | const char* 配列のソートのほうが std::string 配列のソートより数倍高速です. 40 | 41 | 文字列ソートのアルゴリズムとして Burstsort も良く知られています. 42 | よくチューンされた変種同士でどちらが高速なのかは, 43 | 少し調べた限りでは明確には分かりませんでした. 44 | ただ,MSD Radix Sort が苦手とする強い偏りのあるデータも Burstsort は得意のようです. 45 | 46 | 課題 47 | ---- 48 | * Algorithmic Caching を試す? 49 | 50 | * Software Managed Buffer の導入を検討する? 51 | 52 | * std::vector にちゃんと対応する? 53 | 54 | + 今でも一応 std::vector::data() を使えばソートできますが... 55 | 56 | 参考文献 57 | -------- 58 | * MSD Radix Sort 59 | 60 | + Waihong Ng and Katsuhiko Kakehi. 2007. **Cache Efficient Radix Sort for String Sorting.** IEICE Trans. Fundam. Electron. Commun. Comput. Sci. E90-A, 2 (February 2007), 457-466. DOI=10.1093/ietfec/e90-a.2.457 http://dx.doi.org/10.1093/ietfec/e90-a.2.457 61 | 62 | + Juha Kärkkäinen and Tommi Rantala. 2008. **Engineering Radix Sort for Strings.** In Proceedings of the 15th International Symposium on String Processing and Information Retrieval (SPIRE '08), Amihood Amir, Andrew Turpin, and Alistair Moffat (Eds.). Springer-Verlag, Berlin, Heidelberg, 3-14. DOI=10.1007/978-3-540-89097-3_3 http://dx.doi.org/10.1007/978-3-540-89097-3_3 63 | 64 | * Burstsort 65 | 66 | + Ranjan Sinha and Justin Zobel. 2004. **Cache-conscious sorting of large sets of strings with dynamic tries.** J. Exp. Algorithmics 9, Article 1.5 (December 2004). DOI=10.1145/1005813.1041517 http://doi.acm.org/10.1145/1005813.1041517 67 | 68 | + Ranjan Sinha and Anthony Wirth. 2010. **Engineering burstsort: Toward fast in-place string sorting.** J. Exp. Algorithmics 15, Article 2.5 (March 2010), 1.14 pages. DOI=10.1145/1671973.1671978 http://doi.acm.org/10.1145/1671973.1671978 69 | -------------------------------------------------------------------------------- /measure.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "parallel_string_radix_sort.h" 11 | 12 | const int MAX_LEN = 30; 13 | const int TRIAL = 3; 14 | const size_t NUM_ELEMS = 30000000; 15 | 16 | struct __bench__ { 17 | double start; 18 | char msg[100]; 19 | __bench__(const char* format, ...) 20 | __attribute__((format(printf, 2, 3))) 21 | { 22 | va_list args; 23 | va_start(args, format); 24 | vsnprintf(msg, sizeof(msg), format, args); 25 | va_end(args); 26 | 27 | start = sec(); 28 | } 29 | ~__bench__() { 30 | if (sec() - start > 0.2) 31 | fprintf(stderr, "%s: %.6f sec\n", msg, sec() - start); 32 | } 33 | double sec() { 34 | struct timeval tv; 35 | gettimeofday(&tv, NULL); 36 | return tv.tv_sec + tv.tv_usec * 1e-6; 37 | } 38 | operator bool() { return false; } 39 | }; 40 | 41 | #define benchmark(...) if(__bench__ __b__ = __bench__(__VA_ARGS__)); else 42 | 43 | void InitRandom(const char **a, size_t n) { 44 | for (size_t i = 0; i < n; ++i) { 45 | int len = 1 + rand() % MAX_LEN; 46 | 47 | char *s = new char[len + 1]; 48 | for (int j = 0; j < len; ++j) { 49 | s[j] = 'a' + rand() % 26; 50 | } 51 | s[len] = '\0'; 52 | 53 | a[i] = s; 54 | } 55 | } 56 | 57 | void DeleteAll(const char **a, size_t n) { 58 | for (size_t i = 0; i < n; ++i) { 59 | delete [] a[i]; 60 | } 61 | } 62 | 63 | class Compare { 64 | public: 65 | bool operator()(const char* const &a, const char* const &b) { 66 | return strcmp(a, b) < 0; 67 | } 68 | }; 69 | 70 | int main() { 71 | const char **strings = new const char*[NUM_ELEMS]; 72 | parallel_string_radix_sort::ParallelStringRadixSort psrs; 73 | psrs.Init(NUM_ELEMS); 74 | 75 | for (int t = 0; t < TRIAL; ++t) { 76 | InitRandom(strings, NUM_ELEMS); 77 | benchmark("ParallelStringRadixSort::Sort(%d)", t) { 78 | psrs.Sort(strings, NUM_ELEMS); 79 | } 80 | DeleteAll(strings, NUM_ELEMS); 81 | } 82 | 83 | for (int t = 0; t < TRIAL; ++t) { 84 | InitRandom(strings, NUM_ELEMS); 85 | benchmark("std::sort(%d)", t) { 86 | std::sort(strings, strings + NUM_ELEMS, Compare()); 87 | } 88 | DeleteAll(strings, NUM_ELEMS); 89 | } 90 | 91 | delete [] strings; 92 | } 93 | -------------------------------------------------------------------------------- /parallel_string_radix_sort.h: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Takuya Akiba 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Takuya Akiba nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | #ifndef PARALLEL_STRING_RADIX_SORT_H_ 31 | #define PARALLEL_STRING_RADIX_SORT_H_ 32 | 33 | #ifdef _OPENMP 34 | #include 35 | #endif 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #define PSRS_CHECK(expr) \ 46 | if (expr) { \ 47 | } else { \ 48 | fprintf(stderr, "CHECK Failed(%s:%d): %s\n", \ 49 | __FILE__, __LINE__, #expr); \ 50 | exit(EXIT_FAILURE); \ 51 | } 52 | 53 | namespace parallel_string_radix_sort { 54 | namespace internal { 55 | /** 56 | * Comparison function class used in |ParallelStringRadixSort|. 57 | */ 58 | template class Compare; 59 | 60 | template<> class Compare { 61 | public: 62 | explicit Compare(int depth) : depth_(depth) {} 63 | 64 | inline bool operator()(const char* const a, const char* const b) { 65 | return strcmp(a + depth_, b + depth_) < 0; 66 | } 67 | private: 68 | int depth_; 69 | }; 70 | 71 | template<> class Compare { 72 | public: 73 | explicit Compare(int depth) : depth_(depth) {} 74 | 75 | inline bool operator()(const std::string &a, const std::string &b) { 76 | return a.compare(depth_, a.length() - depth_, 77 | b, depth_, b.length() - depth_) < 0; 78 | } 79 | private: 80 | int depth_; 81 | }; 82 | } // namespace internal 83 | 84 | /** 85 | * The class to perform string sorting. 86 | * 87 | * @param StringType The type of the strings to be sorted. 88 | * This should be either of |const char*| or |std::string|. 89 | */ 90 | template 91 | class ParallelStringRadixSort { 92 | public: 93 | ParallelStringRadixSort(); 94 | 95 | ~ParallelStringRadixSort(); 96 | 97 | /** 98 | * Initialize the class. 99 | * 100 | * @param max_elems the maximum number of the elements to be sorted 101 | */ 102 | void Init(size_t max_elems); 103 | 104 | /** 105 | * Sort an array of strings. 106 | * 107 | * @param strings the array to be sorted 108 | * @param num_elems the number of the elements in the given array 109 | */ 110 | void Sort(StringType *strings, size_t num_elems); 111 | 112 | private: 113 | /// The theshold of size of ranges to call |std::sort| 114 | static const size_t kThreshold = 30; 115 | /// The limit of depth to call |std::sort| 116 | static const size_t kDepthLimit = 100; 117 | 118 | size_t max_elems_; 119 | 120 | StringType *data_, *temp_; 121 | 122 | uint8_t *letters8_; 123 | uint16_t *letters16_; 124 | 125 | /** 126 | * Release all the allocated resources. 127 | */ 128 | void DeleteAll(); 129 | 130 | /** 131 | * Sort elements in range [bgn, end), 132 | * which have common prefix with |depth| characters. 133 | * 134 | * @param flip When false, read from |data_|, write to |temp_| and recurse. 135 | * When true, read from |temp_|, write to |data_| and recurse. 136 | */ 137 | void Sort8(size_t bgn, size_t end, size_t depth, bool flip); 138 | 139 | /** 140 | * Sort elements in range [bgn, end), 141 | * which have common prefix with |depth| characters. 142 | * 143 | * Treat pairs of characters as single super characters. 144 | * 145 | * @param flip When false, read from |data_|, write to |temp_| and recurse. 146 | * When true, read from |temp_|, write to |data_| and recurse. 147 | */ 148 | void Sort16(size_t bgn, size_t end, size_t depth, bool flip); 149 | 150 | /** 151 | * Sort elements in range [bgn, end), 152 | * which have common prefix with |depth| characters. 153 | * 154 | * Treat pairs of characters as single super characters. 155 | * 156 | * Builds threads and recurse in parallel. 157 | * 158 | * @param flip When false, read from |data_|, write to |temp_| and recurse. 159 | * When true, read from |temp_|, write to |data_| and recurse. 160 | */ 161 | void Sort16Parallel(size_t bgn, size_t end, size_t depth, bool flip); 162 | 163 | /** 164 | * Sort elements in range [bgn, end), 165 | * which have common prefix with |depth| characters. 166 | * 167 | * The size of the range determines the action of this function. 168 | * 169 | * If the size is sufficiently small (smaller than or equal to |kThreshold|), 170 | * we call |std::sort| complete the sorting. 171 | * In doing so, regardless of |flip|, write the result to |data_|. 172 | * 173 | * If the size is smaller than |1 << 16| then we call |Sort8|, 174 | * otherwise we call |Sort16|. 175 | * 176 | * If the |depth| is larget than or equal to |kDepthLimit|, 177 | * we also use |std::sort|. This is to avoid stack overflow. 178 | */ 179 | inline void Recurse(size_t bgn, size_t end, size_t depth, bool flip) { 180 | size_t n = end - bgn; 181 | if (depth >= kDepthLimit || n <= kThreshold) { 182 | if (flip) { 183 | for (size_t i = bgn; i < end; ++i) { 184 | std::swap(data_[i], temp_[i]); 185 | } 186 | } 187 | if (n > 1) { 188 | std::sort(data_ + bgn, data_ + end, internal::Compare(depth)); 189 | } 190 | } else if (n < (1 << 16)) { 191 | Sort8(bgn, end, depth, flip); 192 | } else { 193 | Sort16(bgn, end, depth, flip); 194 | } 195 | } 196 | }; 197 | 198 | template 199 | ParallelStringRadixSort::ParallelStringRadixSort() 200 | : max_elems_(0), temp_(NULL), letters8_(NULL), letters16_(NULL) {} 201 | 202 | template 203 | ParallelStringRadixSort::~ParallelStringRadixSort() { 204 | DeleteAll(); 205 | } 206 | 207 | template 208 | void ParallelStringRadixSort::Init(size_t max_elems) { 209 | DeleteAll(); 210 | 211 | max_elems_ = max_elems; 212 | temp_ = new StringType[max_elems]; 213 | letters8_ = new uint8_t[max_elems]; 214 | letters16_ = new uint16_t[max_elems]; 215 | PSRS_CHECK(temp_ != NULL && letters8_ != NULL && letters16_ != NULL); 216 | } 217 | 218 | template 219 | void ParallelStringRadixSort::DeleteAll() { 220 | delete [] temp_; 221 | delete [] letters8_; 222 | delete [] letters16_; 223 | max_elems_ = 0; 224 | temp_ = NULL; 225 | letters8_ = NULL; 226 | letters16_ = NULL; 227 | } 228 | 229 | template 230 | void ParallelStringRadixSort 231 | ::Sort(StringType *strings, size_t num_elems) { 232 | assert(num_elems <= max_elems_); 233 | data_ = strings; 234 | Sort16Parallel(0, num_elems, 0, false); 235 | } 236 | 237 | template 238 | void ParallelStringRadixSort 239 | ::Sort8(size_t bgn, size_t end, size_t depth, bool flip) { 240 | // Here we do not use |new| or |malloc|. This is because: 241 | // 1. these may be heavy bottle-necks under multi-thread, and 242 | // 2. the size is sufficiently small for preventing stack overflow. 243 | size_t cnt[1 << 8] = {}; 244 | 245 | StringType *src = (flip ? temp_ : data_) + bgn; 246 | StringType *dst = (flip ? data_ : temp_) + bgn; 247 | uint8_t *let = letters8_ + bgn; 248 | size_t n = end - bgn; 249 | 250 | for (size_t i = 0; i < n; ++i) { 251 | let[i] = src[i][depth]; 252 | } 253 | 254 | for (size_t i = 0; i < n; ++i) { 255 | ++cnt[let[i]]; 256 | } 257 | 258 | size_t s = 0; 259 | for (int i = 0; i < 1 << 8; ++i) { 260 | std::swap(cnt[i], s); 261 | s += cnt[i]; 262 | } 263 | 264 | for (size_t i = 0; i < n; ++i) { 265 | std::swap(dst[cnt[let[i]]++], src[i]); 266 | } 267 | 268 | if (flip == false) { 269 | size_t b = 0, e = cnt[0]; 270 | for (size_t j = b; j < e; ++j) { 271 | std::swap(src[j], dst[j]); 272 | } 273 | } 274 | 275 | for (size_t i = 1; i < 1 << 8; ++i) { 276 | if (cnt[i] - cnt[i - 1] >= 1) { 277 | Recurse(bgn + cnt[i - 1], bgn + cnt[i], depth + 1, !flip); 278 | } 279 | } 280 | } 281 | 282 | template 283 | void ParallelStringRadixSort 284 | ::Sort16(size_t bgn, size_t end, size_t depth, bool flip) { 285 | size_t *cnt = new size_t[1 << 16](); 286 | PSRS_CHECK(cnt != NULL); 287 | 288 | StringType *src = (flip ? temp_ : data_) + bgn; 289 | StringType *dst = (flip ? data_ : temp_) + bgn; 290 | uint16_t *let = letters16_ + bgn; 291 | size_t n = end - bgn; 292 | 293 | for (size_t i = 0; i < n; ++i) { 294 | uint16_t x = src[i][depth]; 295 | let[i] = x == 0 ? 0 : ((x << 8) | src[i][depth + 1]); 296 | } 297 | 298 | for (size_t i = 0; i < n; ++i) { 299 | ++cnt[let[i]]; 300 | } 301 | 302 | size_t s = 0; 303 | for (int i = 0; i < 1 << 16; ++i) { 304 | std::swap(cnt[i], s); 305 | s += cnt[i]; 306 | } 307 | 308 | for (size_t i = 0; i < n; ++i) { 309 | std::swap(dst[cnt[let[i]]++], src[i]); 310 | } 311 | 312 | if (flip == false) { 313 | for (int i = 0; i < 1 << 8; ++i) { 314 | size_t b = i == 0 ? 0 : cnt[(i << 8) - 1]; 315 | size_t e = cnt[i << 8]; 316 | for (size_t j = b; j < e; ++j) { 317 | std::swap(src[j], dst[j]); 318 | } 319 | } 320 | } 321 | 322 | for (size_t i = 1; i < 1 << 16; ++i) { 323 | if ((i & 0xFF) != 0 && cnt[i] - cnt[i - 1] >= 1) { 324 | Recurse(bgn + cnt[i - 1], bgn + cnt[i], depth + 2, !flip); 325 | } 326 | } 327 | 328 | delete [] cnt; 329 | } 330 | 331 | template 332 | void ParallelStringRadixSort 333 | ::Sort16Parallel(size_t bgn, size_t end, size_t depth, bool flip) { 334 | size_t cnt[1 << 16] = {}; 335 | 336 | StringType *src = (flip ? temp_ : data_) + bgn; 337 | StringType *dst = (flip ? data_ : temp_) + bgn; 338 | uint16_t *let = letters16_ + bgn; 339 | size_t n = end - bgn; 340 | 341 | #ifdef _OPENMP 342 | #pragma omp parallel for schedule(static) 343 | #endif 344 | for (size_t i = 0; i < n; ++i) { 345 | uint16_t x = src[i][depth]; 346 | let[i] = x == 0 ? 0 : ((x << 8) | src[i][depth + 1]); 347 | } 348 | 349 | for (size_t i = 0; i < n; ++i) { 350 | ++cnt[let[i]]; 351 | } 352 | 353 | { 354 | size_t s = 0; 355 | for (int i = 0; i < 1 << 16; ++i) { 356 | std::swap(cnt[i], s); 357 | s += cnt[i]; 358 | } 359 | } 360 | 361 | for (size_t i = 0; i < n; ++i) { 362 | std::swap(dst[cnt[let[i]]++], src[i]); 363 | } 364 | 365 | if (flip == false) { 366 | #ifdef _OPENMP 367 | #pragma omp parallel for schedule(static) 368 | #endif 369 | for (int i = 0; i < 1 << 8; ++i) { 370 | size_t b = i == 0 ? 0 : cnt[(i << 8) - 1]; 371 | size_t e = cnt[i << 8]; 372 | for (size_t j = b; j < e; ++j) { 373 | src[j] = dst[j]; 374 | } 375 | } 376 | } 377 | 378 | #ifdef _OPENMP 379 | #pragma omp parallel for schedule(dynamic) 380 | #endif 381 | for (size_t i = 1; i < 1 << 16; ++i) { 382 | if ((i & 0xFF) != 0 && cnt[i] - cnt[i - 1] >= 1) { 383 | Recurse(bgn + cnt[i - 1], bgn + cnt[i], depth + 2, !flip); 384 | } 385 | } 386 | } 387 | 388 | /** 389 | * Sort an array of strings. 390 | * 391 | * This function automatically initialize an instance of 392 | * |ParallelStringRadixSort| and perform sorting. 393 | * 394 | * If you perform sorting more than once, you can avoid the cost of 395 | * initialization using |ParallelStringRadixSort| class by yourself. 396 | * 397 | * @param strings the array to be sorted 398 | * @param num_elems the number of the elements in the given array 399 | */ 400 | template 401 | void Sort(StringType *strings, size_t num_elems) { 402 | ParallelStringRadixSort psrs; 403 | psrs.Init(num_elems); 404 | psrs.Sort(strings, num_elems); 405 | } 406 | 407 | template 408 | void Sort(StringType (&strings)[kNumElems]) { 409 | Sort(strings, kNumElems); 410 | } 411 | } // namespace parallel_string_radix_sort 412 | 413 | #undef PSRS_CHECK 414 | 415 | #endif // PARALLEL_STRING_RADIX_SORT_H_ 416 | -------------------------------------------------------------------------------- /parallel_string_radix_sort_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2011, Takuya Akiba 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Takuya Akiba nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | // Usage: 31 | // % g++ -O3 parallel_string_radix_sort_test.cc -lgtest -lgtest_main -fopenmp 32 | // % ./a.out 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include "parallel_string_radix_sort.h" 40 | 41 | using testing::Types; 42 | 43 | namespace { 44 | template T GenerateString(int max_len, int num_alpha); 45 | 46 | template<> std::string GenerateString(int max_len, int num_alpha) { 47 | int len = 1 + rand() % max_len; 48 | std::string res(len, 'a'); 49 | for (int i = 0; i < len; ++i) { 50 | res[i] += rand() % num_alpha; 51 | } 52 | return res; 53 | } 54 | 55 | template<> const char *GenerateString(int max_len, int num_alpha) { 56 | int len = 1 + rand() % max_len; 57 | char *res = new char[len + 1]; 58 | for (int i = 0; i < len; ++i) { 59 | res[i] = 'a' + rand() % num_alpha; 60 | } 61 | res[len] = '\0'; 62 | return res; 63 | } 64 | 65 | template void DeleteString(T str); 66 | 67 | template<> void DeleteString(std::string str __attribute__((unused))) {} 68 | 69 | template<> void DeleteString(const char *str) { 70 | delete [] str; 71 | } 72 | 73 | template 74 | T *GenerateStrings(size_t num_elems, int max_len, int num_alpha) { 75 | T *res = new T[num_elems]; 76 | for (size_t i = 0; i < num_elems; ++i) { 77 | res[i] = GenerateString(max_len, num_alpha); 78 | } 79 | return res; 80 | } 81 | 82 | template 83 | void DeleteStrings(T *strs, size_t num_elems) { 84 | for (size_t i = 0; i < num_elems; ++i) { 85 | DeleteString(strs[i]); 86 | } 87 | delete [] strs; 88 | } 89 | 90 | template class Compare; 91 | 92 | template<> class Compare { 93 | public: 94 | inline bool operator()(const char* const &a, const char* const &b) { 95 | return strcmp(a, b) < 0; 96 | } 97 | }; 98 | 99 | template<> class Compare { 100 | public: 101 | inline bool operator()(const std::string &a, const std::string &b) { 102 | return a.compare(b) < 0; 103 | } 104 | }; 105 | 106 | template 108 | class Tester { 109 | public: 110 | void Run() { 111 | parallel_string_radix_sort::ParallelStringRadixSort psrs; 112 | psrs.Init(kMaxElems); 113 | 114 | for (int t = 0; t < kNumTrial; ++t) { 115 | size_t num_elems = rand() % (1 + kMaxElems); 116 | StringType *dat 117 | = GenerateStrings(num_elems, kMaxLen, kNumAlpha); 118 | 119 | StringType *ans = new StringType[num_elems]; 120 | for (size_t i = 0; i < num_elems; ++i) ans[i] = dat[i]; 121 | std::stable_sort(ans, ans + num_elems, Compare()); 122 | 123 | psrs.Sort(dat, num_elems); 124 | 125 | for (size_t i = 0; i < num_elems; ++i) { 126 | ASSERT_EQ(std::string(ans[i]), std::string(dat[i])); 127 | } 128 | 129 | DeleteStrings(dat, num_elems); 130 | delete [] ans; 131 | } 132 | } 133 | }; 134 | } // namespace 135 | 136 | typedef Types< 137 | // Small random cases 138 | Tester, 139 | Tester, 140 | 141 | // Large random cases 142 | Tester, 143 | Tester, 144 | 145 | // "AAA..." 146 | Tester, 147 | Tester, 148 | Tester, 149 | Tester, 150 | Tester, // Evil cases which may cause 151 | Tester, // stack overflow on 152 | // fragile implementations :) 153 | // "ABAB..." 154 | Tester, 155 | Tester, 156 | Tester, 157 | Tester, 158 | Tester, 159 | Tester, 160 | 161 | // Size 0 162 | Tester, 163 | Tester 164 | > Testers; 165 | 166 | template 167 | class TestRunner : public testing::Test {}; 168 | TYPED_TEST_CASE(TestRunner, Testers); 169 | 170 | TYPED_TEST(TestRunner, run) { 171 | TypeParam tester; 172 | tester.Run(); 173 | } 174 | -------------------------------------------------------------------------------- /sample.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "parallel_string_radix_sort.h" 5 | 6 | int main() { 7 | // Sorting std::string 8 | { 9 | std::string data[5] = { 10 | "hoge", 11 | "piyo", 12 | "fuga", 13 | "foo", 14 | "bar", 15 | }; 16 | 17 | parallel_string_radix_sort::Sort(data, 5); 18 | 19 | for (int i = 0; i < 5; ++i) { 20 | std::cout << data[i] << std::endl; 21 | } 22 | std::cout << std::endl; 23 | } 24 | 25 | // Sorting const char* 26 | { 27 | const char *data[5] = { 28 | "hoge", 29 | "piyo", 30 | "fuga", 31 | "foo", 32 | "bar", 33 | }; 34 | 35 | parallel_string_radix_sort::Sort(data, 5); 36 | 37 | for (int i = 0; i < 5; ++i) { 38 | std::cout << data[i] << std::endl; 39 | } 40 | std::cout << std::endl; 41 | } 42 | 43 | // When you perform sorting more than once, you can avoid 44 | // the cost of initialization using |ParallelStringRadixSort| class 45 | { 46 | parallel_string_radix_sort::ParallelStringRadixSort psrs; 47 | psrs.Init(10); // Maximum number of the elements 48 | 49 | std::string data[5] = { 50 | "hoge", 51 | "piyo", 52 | "fuga", 53 | "foo", 54 | "bar", 55 | }; 56 | 57 | psrs.Sort(data, 5); 58 | 59 | for (int i = 0; i < 5; ++i) { 60 | std::cout << data[i] << std::endl; 61 | } 62 | std::cout << std::endl; 63 | } 64 | 65 | return 0; 66 | } 67 | --------------------------------------------------------------------------------