├── .gitignore ├── LICENSE ├── README.md ├── baseline_test.go ├── config.go ├── doc ├── BENCHMARK.md ├── CHANGELOG.md ├── STRUCTS.md └── TUNER.md ├── experimental ├── ChunkCopyMergeSort.go ├── InPlaceParallelMergeSort.go ├── MinimalCopyMergeSort.go └── utils.go ├── float32.go ├── float32_test.go ├── float64.go ├── float64_test.go ├── go.mod ├── go.sum ├── int.go ├── int16.go ├── int16_test.go ├── int32.go ├── int32_test.go ├── int64.go ├── int64_test.go ├── int8.go ├── int8_test.go ├── int_test.go ├── scripts └── test.bash ├── string.go ├── string_test.go ├── struct.go ├── struct_test.go ├── time.go ├── time_test.go ├── tuner.go ├── tuner_test.go ├── uint.go ├── uint16.go ├── uint16_test.go ├── uint32.go ├── uint32_test.go ├── uint64.go ├── uint64_test.go ├── uint8.go ├── uint8_test.go ├── uint_test.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 - present Robert Artenie Horatiu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Go Report Card](https://goreportcard.com/badge/github.com/rah-0/parsort?v=1)](https://goreportcard.com/report/github.com/rah-0/parsort) 2 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 3 | 4 | 5 | Buy Me A Coffee 6 | 7 | 8 | 9 | # (par)allel (sort)ing 10 | A minimal, dependency-free Go library for parallel sorting of slices. Designed to scale across CPU cores and stay close to idiomatic Go. 11 | 12 | ## Installation 13 | 14 | ```bash 15 | go get github.com/rah-0/parsort 16 | ``` 17 | 18 | ## Algorithm 19 | The algorithm built is a **parallel merge sort**, more specifically a **multiway parallel merge sort with pairwise merging**. 20 | 21 | ### Breakdown: 22 | - **Divide phase**: The input slice is split into `N` chunks (`N = NumCPU`). 23 | - **Parallel sort phase**: Each chunk is sorted independently using `sort.Slice` in parallel goroutines. 24 | - **Merge phase**: All sorted chunks are merged in **parallel pairwise steps** (`log₂N steps total`). 25 | 26 | This approach isn't a classic recursive merge sort — instead, it's: 27 | - **Iterative**, not recursive. 28 | - Uses **parallelism for both sorting and merging**. 29 | - Merge is **pairwise**, not `N-way` (N-way can be faster for large chunk sets but more complex). 30 | 31 | ### Why not use generics? 32 | 1. Generics introduce performance overhead 33 | - Generic `sort.Slice` requires a comparison function (e.g., `func(i, j int) bool`). 34 | - This introduces: 35 | - **Function call overhead** for every comparison. 36 | - **Indirect branching**, which harms CPU branch prediction and cache locality. 37 | - Native sorts like `sort.Ints`, `sort.Float64s`, etc. are **heavily optimized** and compiled with inlined, tight loops and direct comparisons. 38 | 2. Avoiding heap allocations 39 | - Generic code may cause additional heap allocations (captured closures, interfaces). 40 | - Currently, allocate only when merging, minimizing GC pressure. 41 | 3. Use case is performance-focused 42 | - Generic versions are `1.5x–2x` slower than concrete ones. 43 | - Each type (`int`, `float64`, `string`, etc.) gets the **best possible performance**. 44 | - Since this a sorting library, it's worth specializing. 45 | 46 | ## Performance 47 | Overall, ***parsort*** can reduce ns/op by up to **90%** but at the expense of around **3** times more memory. 48 | 49 | ### Int 50 | | Size | Order | ns/op (%) | B/op (%) | 51 | |------------|-------|-----------|----------| 52 | | 10000 | Asc | -7.09 | +301.81 | 53 | | 10000 | Desc | -59.20 | +301.63 | 54 | | 100000 | Asc | -35.48 | +302.22 | 55 | | 100000 | Desc | -70.23 | +302.17 | 56 | | 1000000 | Asc | -72.71 | +300.43 | 57 | | 1000000 | Desc | -87.76 | +300.43 | 58 | | 10000000 | Asc | -74.58 | +300.02 | 59 | | 10000000 | Desc | -88.51 | +300.02 | 60 | 61 | ### Float 62 | | Size | Order | ns/op (%) | B/op (%) | 63 | |------------|-------|-----------|----------| 64 | | 10000 | Asc | -22.90 | +301.82 | 65 | | 10000 | Desc | -59.62 | +301.63 | 66 | | 100000 | Asc | -52.34 | +302.23 | 67 | | 100000 | Desc | -75.61 | +302.20 | 68 | | 1000000 | Asc | -73.67 | +300.43 | 69 | | 1000000 | Desc | -86.61 | +300.43 | 70 | | 10000000 | Asc | -76.69 | +300.02 | 71 | | 10000000 | Desc | -88.17 | +300.02 | 72 | 73 | ### String 74 | | Size | Order | ns/op (%) | B/op (%) | 75 | |------------|-------|-----------|----------| 76 | | 10000 | Asc | -21.91 | +300.92 | 77 | | 10000 | Desc | -44.02 | +300.83 | 78 | | 100000 | Asc | -48.78 | +300.09 | 79 | | 100000 | Desc | -63.57 | +300.08 | 80 | | 1000000 | Asc | -52.03 | +300.11 | 81 | | 1000000 | Desc | -66.70 | +300.08 | 82 | | 10000000 | Asc | -57.81 | +300.00 | 83 | | 10000000 | Desc | -72.57 | +300.00 | 84 | 85 | ### time.Time 86 | | Size | Order | ns/op (%) | B/op (%) | 87 | |------------|----------|-----------|----------| 88 | | 10000 | Asc/Desc | -35.86 | +307.43 | 89 | | 100000 | Asc/Desc | -63.49 | +301.43 | 90 | | 1000000 | Asc/Desc | -70.35 | +300.08 | 91 | | 10000000 | Asc/Desc | -72.07 | +300.01 | 92 | 93 | ## Supported Types 94 | 95 | Parsort provides specialized sorting functions for each of these types: 96 | 97 | - `int`, `int8`, `int16`, `int32`, `int64` 98 | - `uint`, `uint8`, `uint16`, `uint32`, `uint64` 99 | - `float32`, `float64` 100 | - `string` 101 | - `time.Time` 102 | - `struct` (via generics) 103 | 104 | Each type has both ascending (`TypeAsc`) and descending (`TypeDesc`) sorting functions. 105 | 106 | ## Performance Tuning 107 | 108 | Parsort automatically determines if a slice is large enough to benefit from parallel sorting. The default thresholds work well for most systems, but you can optimize them for your specific hardware: 109 | 110 | ```go 111 | // Optimize thresholds for your hardware 112 | parsort.Tune() 113 | 114 | // Or tune with specific parameters 115 | parsort.TuneSpecific(5, 1000, 1000, -10.0, true) 116 | ``` 117 | 118 | Thresholds can also be manually adjusted: 119 | 120 | ```go 121 | // Manually set threshold for int sorting 122 | parsort.IntMinParallelSize = 5000 123 | ``` 124 | 125 | ## 📌 Additional Resources 126 | - [Struct sorting details](https://github.com/rah-0/parsort/blob/master/doc/STRUCTS.md) 127 | - [Comparison to other libraries](https://github.com/rah-0/benchmarks/tree/master/meta#sorting) 128 | - [Raw benchmark data](https://github.com/rah-0/parsort/blob/master/doc/BENCHMARK.md) 129 | - [Changelog](https://github.com/rah-0/parsort/blob/master/doc/CHANGELOG.md) 130 | - [Tuner documentation](https://github.com/rah-0/parsort/blob/master/doc/TUNER.md) 131 | 132 | ## 💚 Support 133 | Parsort was built out of love for clean and fast code. 134 | If it saved you time or brought value to your project, feel free to show some support. Every bit is appreciated 🙂 135 | 136 | [![Buy Me A Coffee](https://cdn.buymeacoffee.com/buttons/default-orange.png)](https://www.buymeacoffee.com/rah.0) 137 | -------------------------------------------------------------------------------- /baseline_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func BenchmarkBaselineSortIntsAsc(b *testing.B) { 11 | for _, size := range testSizes { 12 | b.Run("Asc_Int_"+strconv.Itoa(size), func(b *testing.B) { 13 | data := genInts(size) 14 | b.ReportAllocs() 15 | b.ResetTimer() 16 | for i := 0; i < b.N; i++ { 17 | tmp := make([]int, len(data)) 18 | copy(tmp, data) 19 | sort.Ints(tmp) 20 | } 21 | }) 22 | } 23 | } 24 | 25 | func BenchmarkBaselineSortIntsDesc(b *testing.B) { 26 | for _, size := range testSizes { 27 | b.Run("Desc_Int_"+strconv.Itoa(size), func(b *testing.B) { 28 | data := genInts(size) 29 | b.ReportAllocs() 30 | b.ResetTimer() 31 | for i := 0; i < b.N; i++ { 32 | tmp := make([]int, len(data)) 33 | copy(tmp, data) 34 | sort.Sort(sort.Reverse(sort.IntSlice(tmp))) 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func BenchmarkBaselineSortFloatsAsc(b *testing.B) { 41 | for _, size := range testSizes { 42 | b.Run("Asc_Float_"+strconv.Itoa(size), func(b *testing.B) { 43 | data := genFloats(size) 44 | b.ReportAllocs() 45 | b.ResetTimer() 46 | for i := 0; i < b.N; i++ { 47 | tmp := make([]float64, len(data)) 48 | copy(tmp, data) 49 | sort.Float64s(tmp) 50 | } 51 | }) 52 | } 53 | } 54 | 55 | func BenchmarkBaselineSortFloatsDesc(b *testing.B) { 56 | for _, size := range testSizes { 57 | b.Run("Desc_Float_"+strconv.Itoa(size), func(b *testing.B) { 58 | data := genFloats(size) 59 | b.ReportAllocs() 60 | b.ResetTimer() 61 | for i := 0; i < b.N; i++ { 62 | tmp := make([]float64, len(data)) 63 | copy(tmp, data) 64 | sort.Sort(sort.Reverse(sort.Float64Slice(tmp))) 65 | } 66 | }) 67 | } 68 | } 69 | 70 | func BenchmarkBaselineSortTimes(b *testing.B) { 71 | for _, size := range testSizes { 72 | b.Run("Sort_Time_"+strconv.Itoa(size), func(b *testing.B) { 73 | data := genTimes(size) 74 | b.ReportAllocs() 75 | b.ResetTimer() 76 | for i := 0; i < b.N; i++ { 77 | tmp := make([]time.Time, len(data)) 78 | copy(tmp, data) 79 | sort.Slice(tmp, func(i, j int) bool { 80 | return tmp[i].Before(tmp[j]) 81 | }) 82 | } 83 | }) 84 | } 85 | } 86 | 87 | func BenchmarkBaselineSortStringsAsc(b *testing.B) { 88 | for _, size := range testSizes { 89 | b.Run("Asc_String_"+strconv.Itoa(size), func(b *testing.B) { 90 | data := genStrings(size) 91 | b.ReportAllocs() 92 | b.ResetTimer() 93 | for i := 0; i < b.N; i++ { 94 | tmp := make([]string, len(data)) 95 | copy(tmp, data) 96 | sort.Strings(tmp) 97 | } 98 | }) 99 | } 100 | } 101 | 102 | func BenchmarkBaselineSortStringsDesc(b *testing.B) { 103 | for _, size := range testSizes { 104 | b.Run("Desc_String_"+strconv.Itoa(size), func(b *testing.B) { 105 | data := genStrings(size) 106 | b.ReportAllocs() 107 | b.ResetTimer() 108 | for i := 0; i < b.N; i++ { 109 | tmp := make([]string, len(data)) 110 | copy(tmp, data) 111 | sort.Sort(sort.Reverse(sort.StringSlice(tmp))) 112 | } 113 | }) 114 | } 115 | } 116 | 117 | // Method 1: sort.Slice with index-based comparator 118 | func BenchmarkSortSlice_Arbitrary(b *testing.B) { 119 | for _, size := range testSizes { 120 | b.Run("Arbitrary_SortSlice_"+strconv.Itoa(size), func(b *testing.B) { 121 | data := genPeople(size) 122 | b.ReportAllocs() 123 | b.ResetTimer() 124 | for i := 0; i < b.N; i++ { 125 | tmp := make([]person, len(data)) 126 | copy(tmp, data) 127 | sort.Slice(tmp, func(i, j int) bool { 128 | return tmp[i].Age < tmp[j].Age 129 | }) 130 | } 131 | }) 132 | } 133 | } 134 | 135 | // Method 2: sort.Sort with full interface 136 | func BenchmarkSortInterface_Arbitrary(b *testing.B) { 137 | for _, size := range testSizes { 138 | b.Run("Arbitrary_SortInterface_"+strconv.Itoa(size), func(b *testing.B) { 139 | data := genPeople(size) 140 | b.ReportAllocs() 141 | b.ResetTimer() 142 | for i := 0; i < b.N; i++ { 143 | tmp := make([]person, len(data)) 144 | copy(tmp, data) 145 | sort.Sort(byAge(tmp)) 146 | } 147 | }) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | var ( 8 | // CoreCount determines the number of parallel operations to perform. 9 | // By default, it uses all available CPU cores. 10 | CoreCount = runtime.NumCPU() 11 | 12 | // MinParallelSize variables define the threshold at which parallel sorting becomes 13 | // more efficient than sequential sorting for each data type. 14 | // For slices smaller than these values, sequential sorting is used instead. 15 | // These values can be fine-tuned for specific hardware using the Tune() function. 16 | 17 | // Integer type thresholds 18 | IntMinParallelSize = 10000 19 | Int8MinParallelSize = 5000 20 | Int16MinParallelSize = 5000 21 | Int32MinParallelSize = 5000 22 | Int64MinParallelSize = 5000 23 | 24 | // Unsigned integer type thresholds 25 | UintMinParallelSize = 5000 26 | Uint8MinParallelSize = 5000 27 | Uint16MinParallelSize = 5000 28 | Uint32MinParallelSize = 5000 29 | Uint64MinParallelSize = 5000 30 | 31 | // Floating point type thresholds 32 | Float32MinParallelSize = 5000 33 | Float64MinParallelSize = 10000 34 | 35 | // Other type thresholds 36 | StringMinParallelSize = 10000 37 | StructMinParallelSize = 10000 38 | TimeMinParallelSize = 5000 39 | ) 40 | -------------------------------------------------------------------------------- /doc/BENCHMARK.md: -------------------------------------------------------------------------------- 1 | ## Baseline Bench 2 | All benchmarks are performed with: **1.000**, **10.000**, **100.000**, **1.000.000**, **10.000.000** values. 3 | The baseline benches the standard lib, the results are the following: 4 | 5 | ### Int Asc 6 | ``` 7 | Asc_Int_1000-8 100512 12235 ns/op 8192 B/op 1 allocs/op 8 | Asc_Int_10000-8 2882 406320 ns/op 81920 B/op 1 allocs/op 9 | Asc_Int_100000-8 224 5358521 ns/op 802821 B/op 1 allocs/op 10 | Asc_Int_1000000-8 19 60016454 ns/op 8003589 B/op 1 allocs/op 11 | Asc_Int_10000000-8 2 686426480 ns/op 80003072 B/op 1 allocs/op 12 | ``` 13 | ### Int Desc 14 | ``` 15 | Desc_Int_1000-8 28135 42971 ns/op 8232 B/op 3 allocs/op 16 | Desc_Int_10000-8 1280 925838 ns/op 81960 B/op 3 allocs/op 17 | Desc_Int_100000-8 99 11773261 ns/op 802918 B/op 3 allocs/op 18 | Desc_Int_1000000-8 8 137250452 ns/op 8003636 B/op 3 allocs/op 19 | Desc_Int_10000000-8 1 1591991046 ns/op 80003112 B/op 3 allocs/op 20 | ``` 21 | 22 | ### Float Asc 23 | ``` 24 | Asc_Float_1000-8 79406 15260 ns/op 8192 B/op 1 allocs/op 25 | Asc_Float_10000-8 2112 562764 ns/op 81920 B/op 1 allocs/op 26 | Asc_Float_100000-8 164 7243807 ns/op 802818 B/op 1 allocs/op 27 | Asc_Float_1000000-8 13 81742600 ns/op 8003584 B/op 1 allocs/op 28 | Asc_Float_10000000-8 2 946438351 ns/op 80003072 B/op 1 allocs/op 29 | ``` 30 | ### Float Desc 31 | ``` 32 | Desc_Float_1000-8 23719 50173 ns/op 8232 B/op 3 allocs/op 33 | Desc_Float_10000-8 1075 1090956 ns/op 81960 B/op 3 allocs/op 34 | Desc_Float_100000-8 85 13821467 ns/op 802859 B/op 3 allocs/op 35 | Desc_Float_1000000-8 7 160525362 ns/op 8003637 B/op 3 allocs/op 36 | Desc_Float_10000000-8 1 1892565552 ns/op 80003112 B/op 3 allocs/op 37 | ``` 38 | 39 | ### String Asc 40 | ``` 41 | Asc_String_1000-8 18469 63118 ns/op 16384 B/op 1 allocs/op 42 | Asc_String_10000-8 1078 1079163 ns/op 163840 B/op 1 allocs/op 43 | Asc_String_100000-8 78 14687354 ns/op 1605632 B/op 1 allocs/op 44 | Asc_String_1000000-8 5 225329861 ns/op 16007168 B/op 1 allocs/op 45 | Asc_String_10000000-8 1 3365926285 ns/op 160006144 B/op 1 allocs/op 46 | ``` 47 | ### String Desc 48 | ``` 49 | Desc_String_1000-8 13168 90850 ns/op 16424 B/op 3 allocs/op 50 | Desc_String_10000-8 777 1521293 ns/op 163880 B/op 3 allocs/op 51 | Desc_String_100000-8 50 21462943 ns/op 1605672 B/op 3 allocs/op 52 | Desc_String_1000000-8 4 316241439 ns/op 16008520 B/op 4 allocs/op 53 | Desc_String_10000000-8 1 5236124366 ns/op 160006184 B/op 3 allocs/op 54 | ``` 55 | 56 | ### time.Time Asc/Desc 57 | ``` 58 | Sort_Time_1000-8 10000 104695 ns/op 24672 B/op 4 allocs/op 59 | Sort_Time_10000-8 800 1546552 ns/op 245862 B/op 4 allocs/op 60 | Sort_Time_100000-8 62 18585826 ns/op 2400437 B/op 4 allocs/op 61 | Sort_Time_1000000-8 5 204948782 ns/op 24002656 B/op 4 allocs/op 62 | Sort_Time_10000000-8 1 2331454407 ns/op 240001120 B/op 4 allocs/op 63 | ``` 64 | 65 | ## Parsort Bench 66 | 67 | ### Int Asc 68 | ``` 69 | Parsort_Asc_Int_10000-8 3169 377494 ns/op 329166 B/op 47 allocs/op 70 | Parsort_Asc_Int_100000-8 350 3457374 ns/op 3229117 B/op 47 allocs/op 71 | Parsort_Asc_Int_1000000-8 67 16378208 ns/op 32048752 B/op 47 allocs/op 72 | Parsort_Asc_Int_10000000-8 6 174460002 ns/op 320030152 B/op 47 allocs/op 73 | ``` 74 | ### Int Desc 75 | ``` 76 | Parsort_Desc_Int_10000-8 3189 377779 ns/op 329178 B/op 47 allocs/op 77 | Parsort_Desc_Int_100000-8 422 3505281 ns/op 3229122 B/op 47 allocs/op 78 | Parsort_Desc_Int_1000000-8 67 16796855 ns/op 32048661 B/op 47 allocs/op 79 | Parsort_Desc_Int_10000000-8 6 182859490 ns/op 320030376 B/op 47 allocs/op 80 | ``` 81 | 82 | ### Float64 Asc 83 | ``` 84 | Parsort_Asc_Float64_10000-8 2722 433885 ns/op 329169 B/op 47 allocs/op 85 | Parsort_Asc_Float64_100000-8 309 3452451 ns/op 3229160 B/op 47 allocs/op 86 | Parsort_Asc_Float64_1000000-8 52 21521789 ns/op 32048636 B/op 47 allocs/op 87 | Parsort_Asc_Float64_10000000-8 5 220576750 ns/op 320030136 B/op 47 allocs/op 88 | ``` 89 | ### Float64 Desc 90 | ``` 91 | Parsort_Desc_Float64_10000-8 2798 440571 ns/op 329175 B/op 47 allocs/op 92 | Parsort_Desc_Float64_100000-8 434 3371673 ns/op 3229121 B/op 47 allocs/op 93 | Parsort_Desc_Float64_1000000-8 55 21498810 ns/op 32048576 B/op 47 allocs/op 94 | Parsort_Desc_Float64_10000000-8 5 223893618 ns/op 320030174 B/op 47 allocs/op 95 | ``` 96 | 97 | ### String Asc 98 | ``` 99 | Parsort_Asc_String_10000-8 1376 842687 ns/op 656868 B/op 47 allocs/op 100 | Parsort_Asc_String_100000-8 140 7522620 ns/op 6424024 B/op 47 allocs/op 101 | Parsort_Asc_String_1000000-8 10 108093949 ns/op 64046520 B/op 47 allocs/op 102 | Parsort_Asc_String_10000000-8 1 1419925603 ns/op 640026040 B/op 47 allocs/op 103 | ``` 104 | ### String Desc 105 | ``` 106 | Parsort_Desc_String_10000-8 1364 851547 ns/op 656874 B/op 47 allocs/op 107 | Parsort_Desc_String_100000-8 148 7818514 ns/op 6424022 B/op 47 allocs/op 108 | Parsort_Desc_String_1000000-8 10 105319618 ns/op 64046520 B/op 47 allocs/op 109 | Parsort_Desc_String_10000000-8 1 1436109086 ns/op 640026136 B/op 48 allocs/op 110 | ``` 111 | 112 | ### Time Asc/Desc 113 | ``` 114 | Parsort_Sort_Time_10000-8 1210 992026 ns/op 1001720 B/op 71 allocs/op 115 | Parsort_Sort_Time_100000-8 170 6785655 ns/op 9636026 B/op 71 allocs/op 116 | Parsort_Sort_Time_1000000-8 18 60761972 ns/op 96028856 B/op 71 allocs/op 117 | Parsort_Sort_Time_10000000-8 2 651085630 ns/op 960039096 B/op 71 allocs/op 118 | ``` 119 | -------------------------------------------------------------------------------- /doc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | # v1.3.0 4 | - Added [Tuner](https://github.com/rah-0/parsort/blob/v1.3.0/doc/TUNER.md) as an optional tool to update thresholds of when parallelization starts. 5 | 6 | # v1.2.0 7 | - Added support for structs: StructAsc and StructDesc 8 | - Increased `go version` requirement to 1.18 due to generics 9 | 10 | # v1.1.0 11 | - Added [configurable CoreCount](https://github.com/rah-0/parsort/commit/a2188f087117dc985faf8345fc520bc4d2d51842) 12 | - Lowered `go version` requirement to 1.12, minimum possible 13 | 14 | # v1.0.0 15 | First release. 16 | -------------------------------------------------------------------------------- /doc/STRUCTS.md: -------------------------------------------------------------------------------- 1 | # Struct Sorting 2 | 3 | Moving away from **multiway parallel merge sort with pairwise merging**, the algorithm used in this case is **Parallel Pairwise Merge Sort**. 4 | 5 | ### Why Multiway Merge Sort (with a priority queue) is not efficient for structs: 6 | 7 | 1. High Per-Comparison Cost 8 | - Multiway merge uses a **min-heap** (priority queue) to always extract the smallest among `k` sorted sequences. 9 | - For basic types like `int` or `float64`, comparisons are very cheap. 10 | - For structs, each comparison calls a user-defined function like `less(a, b)`, which is often **much slower** and may include: 11 | - Field access 12 | - Pointer chasing 13 | - Possibly nested comparisons 14 | - Heaps require **log k** comparisons per pop → high cost when each comparison is expensive. 15 | 16 | 2. Loss of Stability (unless handled manually) 17 | - A priority queue selects the smallest next element across `k` streams, but: 18 | - If two elements compare equal, the heap pops one arbitrarily. 19 | - For structs, where sorting is often based on fields like timestamps or IDs, stability is critical. 20 | - Preserving stability would require attaching original indices and modifying the comparison logic, adding overhead. 21 | 22 | 3. More Pointer Indirection 23 | - Priority queues for multiway merges usually hold items like `(value, index, chunkID)`. 24 | - For basic types, this is fast due to small size and value semantics. 25 | - For structs, this means more indirection and memory operations to track values and metadata. 26 | - In high-volume merges, this results in additional allocations and garbage collection pressure. 27 | 28 | 4. Cache Locality Penalty 29 | - Multiway merging involves simultaneous access to `k` sequences. 30 | - This breaks sequential access patterns and thrashes the cache. 31 | - For small types (e.g., `int`), this impact is minor. 32 | - For larger structs with non-contiguous memory layouts, cache misses increase and degrade performance. 33 | 34 | 5. Complexity for Generalized Types 35 | - Priority queues require a comparison interface compatible with heap operations. 36 | - For structs, this adds overhead: closures, interface boxing, or function indirection. 37 | - Supporting `less(a, b T)` in a heap-compatible form introduces complexity. 38 | - Pairwise merging avoids this by working directly on `[]T` and passing the `less` function transparently. 39 | 40 | # Benchmarking 41 | 42 | The struct used for benchmarks is: 43 | ``` 44 | type person struct { 45 | Name string 46 | Age int 47 | } 48 | ``` 49 | The benchmark is a simple as: 50 | ``` 51 | sort.Slice(people, func(i, j int) bool { 52 | return tmp[i].Age < tmp[j].Age 53 | }) 54 | ``` 55 | Which we will use as the first result: 56 | 57 | | Size | ns/op | ns/op (%) | B/op | B/op (%) | 58 | |------------|-----------|-----------|-------------|-----------| 59 | | 1000 | 52.13µs | 100.00 | 24.09KiB | 100.00 | 60 | | 10000 | 654.21µs | 100.00 | 240.09KiB | 100.00 | 61 | | 100000 | 6.40ms | 100.00 | 2.29MiB | 100.00 | 62 | | 1000000 | 58.21ms | 100.00 | 22.89MiB | 100.00 | 63 | | 10000000 | 553.95ms | 100.00 | 228.88MiB | 100.00 | 64 | | 100000000 | 7.94s | 100.00 | 2.24GiB | 100.00 | 65 | 66 | Let's see the performance with interfaces: 67 | ``` 68 | type byAge []person 69 | func (a byAge) Len() int { return len(a) } 70 | func (a byAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 71 | func (a byAge) Less(i, j int) bool { return a[i].Age < a[j].Age } 72 | 73 | sort.Sort(byAge(people)) 74 | ``` 75 | | Size | ns/op | ns/op (%) | B/op | B/op (%) | 76 | |------------|-----------|-----------|-------------|-----------| 77 | | 1000 | 28.66µs | -45.02 | 24.02KiB | -0.29 | 78 | | 10000 | 467.26µs | -28.58 | 240.02KiB | -0.03 | 79 | | 100000 | 5.01ms | -21.69 | 2.29MiB | 0.00 | 80 | | 1000000 | 43.74ms | -24.86 | 22.89MiB | 0.00 | 81 | | 10000000 | 416.89ms | -24.74 | 228.88MiB | 0.00 | 82 | | 100000000 | 4.30s | -45.88 | 2.24GiB | 0.00 | 83 | 84 | This is already a major improvement. 85 | 86 | So the next comparison will be relative to this, Parallel Pairwise Merge Sort: 87 | 88 | ``` 89 | StructAsc(people, func(a, b person) bool { 90 | return a.Age < b.Age 91 | }) 92 | ``` 93 | 94 | | Size | ns/op | ns/op (%) | B/op | B/op (%) | 95 | |------------|-----------|-----------|---------------|-----------| 96 | | 1000 | 111.50µs | +389.03 | 50.55KiB | +210.41 | 97 | | 10000 | 679.97µs | +145.52 | 482.55KiB | +201.04 | 98 | | 100000 | 4.03ms | -19.53 | 4.58MiB | +200.11 | 99 | | 1000000 | 32.13ms | -26.55 | 45.78MiB | +200.01 | 100 | | 10000000 | 249.71ms | -40.10 | 457.77MiB | +200.00 | 101 | | 100000000 | 2.65s | -38.35 | 4.47GiB | +200.00 | 102 | 103 | # 📊 Conclusions 104 | 105 | - **Under 10k elements** 106 | The standard library’s `sort.Sort` outperforms all alternatives. The overhead of parallelism and memory allocations isn’t worth it for small slices. 107 | 108 | - **100k elements and above** 109 | The **Parallel Pairwise Merge Sort** consistently outperforms both `sort.Slice` and `sort.Sort` in raw CPU performance (`ns/op`), achieving: 110 | - ~20–40% faster execution 111 | - ~2× higher memory usage (due to parallel merge buffers) 112 | 113 | - **Memory Tradeoff** 114 | While `B/op` is around **200%** of the baseline, this is a deliberate tradeoff to maximize **CPU throughput**, which is acceptable in modern systems for performance-critical workloads. 115 | 116 | - **Best Use Cases** 117 | - Sorting large slices of structs (e.g., event logs, database records) 118 | - Multi-core systems where parallel sorting improves throughput 119 | 120 | -------------------------------------------------------------------------------- /doc/TUNER.md: -------------------------------------------------------------------------------- 1 | # Tuner 2 | 3 | As observed by Reddit user **LearnedByError** in [this comment](https://www.reddit.com/r/golang/comments/1jliue6/comment/mk94phj/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button), the performance with the library on `Intel(R) Xeon(R) E5-2680` is quite poor. 4 | 5 | So poor in fact, that using the library might not even be worth it with old CPUs. 6 | 7 | Nevertheless and out of curiosity I have created [Tuner](https://github.com/rah-0/parsort/blob/master/tuner.go#L534) which is a tool that helps benchmark and updates configuration thresholds per type. 8 | 9 | Tuning is optional, you can either simply call `parsort.Tune()` which uses default values or [parsort.TuneSpecific](https://github.com/rah-0/parsort/blob/master/tuner.go#L27) to have more control. 10 | 11 | Changing any [Config](https://github.com/rah-0/parsort/blob/master/config.go)ParallelSize variable has an impact of when parallelization starts. 12 | 13 | ## Tuner output of systems 14 | 15 | ### AMD Ryzen 5950X 5GHz + 64GB DDR4 3600MHz CL19 16 | | Type | Label | Sequential ns/op | Concurrent ns/op | Sequential B/op | Concurrent B/op | Delta ns/op | Delta B/op | 17 | |----------|-------|------------------|------------------|------------------|------------------|-------------|-----------| 18 | | Int | 3000 | 119µs 97ns | 105µs 331ns | 24KB | 149KB 979B | -11.56% | +524.82% | 19 | | Int8 | 3000 | 137µs 121ns | 118µs 93ns | 3KB 56B | 25KB 726B | -13.88% | +741.62% | 20 | | Int16 | 2000 | 114µs 583ns | 98µs 645ns | 4KB 56B | 31KB 634B | -13.91% | +679.82% | 21 | | Int32 | 2000 | 121µs 541ns | 104µs 845ns | 8KB 56B | 55KB 626B | -13.74% | +590.42% | 22 | | Int64 | 2000 | 130µs 541ns | 110µs 404ns | 16KB 56B | 103KB 571B | -15.43% | +545.03% | 23 | | Uint | 3000 | 197µs 73ns | 142µs 351ns | 24KB 56B | 151KB 694B | -27.77% | +530.55% | 24 | | Uint8 | 2000 | 96µs 63ns | 85µs 803ns | 2KB 56B | 19KB 662B | -10.68% | +856.18% | 25 | | Uint16 | 2000 | 120µs 106ns | 93µs 586ns | 4KB 56B | 31KB 631B | -22.08% | +679.74% | 26 | | Uint32 | 2000 | 129µs 846ns | 108µs 297ns | 8KB 56B | 55KB 630B | -16.60% | +590.47% | 27 | | Uint64 | 3000 | 197µs 651ns | 156µs 712ns | 24KB 56B | 151KB 697B | -20.71% | +530.57% | 28 | | Float32 | 2000 | 137µs 196ns | 104µs 618ns | 8KB 56B | 55KB 632B | -23.75% | +590.49% | 29 | | Float64 | 2000 | 102µs 849ns | 88µs 100ns | 16KB | 101KB 818B | -14.34% | +536.24% | 30 | | String | 2000 | 170µs 972ns | 137µs 256ns | 32KB | 197KB 699B | -19.72% | +517.76% | 31 | | Time | 2000 | 243µs 177ns | 175µs 181ns | 48KB 96B | 296KB 564B | -27.96% | +516.61% | 32 | | Struct | 9000 | 386µs 464ns | 343µs 648ns | 216KB 24B | 442KB 75B | -11.08% | +104.64% | 33 | 34 | ### AMD Ryzen AI 9 HX 370 + 32GB LPDDR5X 7500MHz CL40 35 | | Type | Label | Sequential ns/op | Concurrent ns/op | Sequential B/op | Concurrent B/op | Delta ns/op | Delta B/op | 36 | |---------|-------|------------------|------------------|------------------|------------------|-------------|------------| 37 | | Int | 4000 | 281µs 699ns | 210µs 927ns | 32KB | 184KB 648B | -25.12% | +476.98% | 38 | | Int8 | 2000 | 162µs 994ns | 141µs 420ns | 2KB 56B | 17KB 304B | -13.24% | +741.83% | 39 | | Int16 | 2000 | 177µs 787ns | 143µs 355ns | 4KB 56B | 28KB 696B | -19.37% | +607.32% | 40 | | Int32 | 3000 | 321µs 114ns | 164µs 845ns | 12KB 56B | 73KB 782B | -48.66% | +511.91% | 41 | | Int64 | 3000 | 332µs 148ns | 242µs 518ns | 24KB 56B | 141KB 867B | -26.98% | +489.68% | 42 | | Uint | 3000 | 341µs 174ns | 206µs 797ns | 24KB 56B | 141KB 844B | -39.39% | +489.59% | 43 | | Uint8 | 3000 | 244µs 486ns | 169µs 152ns | 3KB 56B | 22KB 808B | -30.81% | +646.04% | 44 | | Uint16 | 2000 | 196µs 52ns | 144µs 786ns | 4KB 56B | 28KB 696B | -26.15% | +607.32% | 45 | | Uint32 | 3000 | 312µs 186ns | 220µs 323ns | 12KB 56B | 73KB 799B | -29.43% | +512.05% | 46 | | Uint64 | 2000 | 220µs 473ns | 163µs 498ns | 16KB 56B | 96KB 274B | -25.84% | +499.62% | 47 | | Float32 | 3000 | 365µs 292ns | 278µs 639ns | 12KB 56B | 73KB 783B | -23.72% | +511.92% | 48 | | Float64 | 3000 | 270µs 978ns | 186µs 395ns | 24KB | 140KB 464B | -31.21% | +485.22% | 49 | | String | 2000 | 324µs 240ns | 239µs 40ns | 32KB | 192KB 468B | -26.28% | +501.43% | 50 | | Time | 1000 | 233µs 360ns | 196µs 363ns | 24KB 96B | 142KB 732B | -15.85% | +492.33% | 51 | | Struct | 10000 | 733µs 238ns | 652µs 297ns | 240KB 24B | 487KB 702B | -11.04% | +103.18% | 52 | 53 | ### Intel i5-12450H + 16GB DDR4 3200MHz 54 | | Type | Label | Sequential ns/op | Concurrent ns/op | Sequential B/op | Concurrent B/op | Delta ns/op | Delta B/op | 55 | |---------|-------|------------------|------------------|------------------|------------------|-------------|------------| 56 | | Int | 3000 | 141µs 124ns | 121µs 66ns | 24KB | 114KB 397B | -14.21% | +376.62% | 57 | | Int8 | 2000 | 131µs 409ns | 93µs 400ns | 2KB 56B | 12KB 611B | -28.92% | +513.07% | 58 | | Int16 | 1000 | 69µs 295ns | 61µs 925ns | 2KB 56B | 12KB 357B | -10.64% | +501.00% | 59 | | Int32 | 2000 | 150µs 681ns | 99µs 318ns | 8KB 56B | 40KB 409B | -34.09% | +401.56% | 60 | | Int64 | 2000 | 151µs 609ns | 106µs 242ns | 16KB 56B | 76KB 1010B | -29.92% | +379.53% | 61 | | Uint | 2000 | 154µs 873ns | 100µs 13ns | 16KB 56B | 76KB 1023B | -35.42% | +379.60% | 62 | | Uint8 | 2000 | 126µs 654ns | 93µs 980ns | 2KB 56B | 12KB 381B | -25.80% | +502.14% | 63 | | Uint16 | 2000 | 151µs 568ns | 97µs 449ns | 4KB 56B | 21KB 758B | -35.71% | +436.18% | 64 | | Uint32 | 2000 | 155µs 882ns | 96µs 948ns | 8KB 56B | 40KB 258B | -37.81% | +399.73% | 65 | | Uint64 | 2000 | 156µs 599ns | 102µs 910ns | 16KB 56B | 77KB 6B | -34.28% | +379.65% | 66 | | Float32 | 2000 | 165µs 623ns | 105µs 953ns | 8KB 56B | 40KB 237B | -36.03% | +399.48% | 67 | | Float64 | 2000 | 109µs 327ns | 95µs 620ns | 16KB | 76KB 335B | -12.54% | +377.04% | 68 | | String | 2000 | 212µs 891ns | 163µs 840ns | 32KB | 150KB 870B | -23.04% | +371.41% | 69 | | Time | 1000 | 128µs 516ns | 102µs 80ns | 24KB 96B | 115KB 363B | -20.57% | +378.77% | 70 | | Struct | 5000 | 272µs 905ns | 242µs 204ns | 120KB 24B | 243KB 910B | -11.25% | +103.20% | 71 | 72 | ### AMD 7735HS + 64GB DDR5 5600MHz 73 | | Type | Label | Sequential ns/op | Concurrent ns/op | Sequential B/op | Concurrent B/op | Delta ns/op | Delta B/op | 74 | |---------|-------|------------------|------------------|------------------|------------------|-------------|------------| 75 | | Int | 4000 | 426µs 765ns | 365µs 404ns | 32KB | 162KB 951B | -14.38% | +409.15% | 76 | | Int8 | 2000 | 281µs 698ns | 241µs 27ns | 2KB 56B | 13KB 810B | -14.44% | +571.20% | 77 | | Int16 | 2000 | 339µs 124ns | 255µs 208ns | 4KB 56B | 23KB 903B | -24.74% | +488.99% | 78 | | Int32 | 2000 | 319µs 84ns | 235µs 767ns | 8KB 56B | 43KB 800B | -26.11% | +443.55% | 79 | | Int64 | 2000 | 332µs 968ns | 277µs 84ns | 16KB 56B | 83KB 806B | -16.78% | +421.89% | 80 | | Uint | 2000 | 339µs 122ns | 275µs 12ns | 16KB 56B | 83KB 793B | -18.90% | +421.81% | 81 | | Uint8 | 3000 | 405µs 272ns | 299µs 189ns | 3KB 56B | 18KB 910B | -26.18% | +518.35% | 82 | | Uint16 | 2000 | 347µs 478ns | 239µs 69ns | 4KB 56B | 23KB 798B | -31.20% | +486.46% | 83 | | Uint32 | 2000 | 315µs 216ns | 230µs 521ns | 8KB 56B | 43KB 812B | -26.87% | +443.70% | 84 | | Uint64 | 2000 | 320µs 752ns | 243µs 741ns | 16KB 56B | 83KB 801B | -24.01% | +421.86% | 85 | | Float32 | 2000 | 372µs 369ns | 273µs 910ns | 8KB 56B | 43KB 799B | -26.44% | +443.54% | 86 | | Float64 | 3000 | 376µs 217ns | 316µs 467ns | 24KB | 122KB 1011B | -15.88% | +412.45% | 87 | | String | 2000 | 476µs 691ns | 377µs 43ns | 32KB | 162KB 934B | -20.90% | +409.10% | 88 | | Time | 2000 | 696µs 118ns | 440µs 164ns | 48KB 96B | 244KB 474B | -36.77% | +408.30% | 89 | | Struct | 8000 | 967µs 328ns | 847µs 575ns | 192KB 24B | 389KB 134B | -12.38% | +102.65% | 90 | -------------------------------------------------------------------------------- /experimental/ChunkCopyMergeSort.go: -------------------------------------------------------------------------------- 1 | package experimental 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func ChunkCopyMergeSort[T any](data []T, less func(a, b T) bool) { 9 | n := len(data) 10 | if n <= 1 { 11 | return 12 | } 13 | 14 | chunkSize := (n + CoreCount - 1) / CoreCount 15 | chunks := make([][]T, 0, CoreCount) 16 | 17 | for i := 0; i < n; i += chunkSize { 18 | end := i + chunkSize 19 | if end > n { 20 | end = n 21 | } 22 | chunk := make([]T, end-i) 23 | copy(chunk, data[i:end]) 24 | chunks = append(chunks, chunk) 25 | } 26 | 27 | var wg sync.WaitGroup 28 | for i := range chunks { 29 | wg.Add(1) 30 | go func(i int) { 31 | defer wg.Done() 32 | sort.Slice(chunks[i], func(a, b int) bool { 33 | return less(chunks[i][a], chunks[i][b]) 34 | }) 35 | }(i) 36 | } 37 | wg.Wait() 38 | 39 | buffer := make([]T, len(data)) 40 | toggle := false 41 | 42 | // Merge chunks pairwise into shared buffer to reduce allocations 43 | for len(chunks) > 1 { 44 | mergedChunks := make([][]T, (len(chunks)+1)/2) 45 | var mWg sync.WaitGroup 46 | 47 | for i := 0; i < len(chunks); i += 2 { 48 | if i+1 == len(chunks) { 49 | mergedChunks[i/2] = chunks[i] 50 | continue 51 | } 52 | 53 | mWg.Add(1) 54 | go func(i int) { 55 | defer mWg.Done() 56 | a, b := chunks[i], chunks[i+1] 57 | start := 0 58 | for j := 0; j < i; j += 2 { 59 | start += len(chunks[j]) + len(chunks[j+1]) 60 | } 61 | mergeIntoBuffer(buffer[start:], a, b, less) 62 | mergedChunks[i/2] = buffer[start : start+len(a)+len(b)] 63 | }(i) 64 | } 65 | mWg.Wait() 66 | chunks = mergedChunks 67 | toggle = !toggle 68 | } 69 | 70 | copy(data, chunks[0]) 71 | } 72 | 73 | /* Benchmark results 74 | 75 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_1000-8 652042 110564 ns/op 76360 B/op 73 allocs/op 76 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_10000-8 89990 821030 ns/op 756302 B/op 73 allocs/op 77 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_100000-8 15708 4654315 ns/op 7228006 B/op 73 allocs/op 78 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_1000000-8 1774 38654809 ns/op 72059501 B/op 73 allocs/op 79 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_10000000-8 252 300209448 ns/op 720063074 B/op 73 allocs/op 80 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_100000000-8 25 2504587907 ns/op 7200066154 B/op 73 allocs/op 81 | */ 82 | -------------------------------------------------------------------------------- /experimental/InPlaceParallelMergeSort.go: -------------------------------------------------------------------------------- 1 | package experimental 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | type chunk struct{ start, end int } 9 | 10 | func InPlaceParallelMergeSort[T any](data []T, less func(a, b T) bool) { 11 | n := len(data) 12 | if n <= 1 { 13 | return 14 | } 15 | 16 | chunkSize := (n + CoreCount - 1) / CoreCount 17 | chunks := make([]chunk, 0, CoreCount) 18 | 19 | for i := 0; i < n; i += chunkSize { 20 | end := i + chunkSize 21 | if end > n { 22 | end = n 23 | } 24 | chunks = append(chunks, chunk{i, end}) 25 | } 26 | 27 | var wg sync.WaitGroup 28 | prefetch := make(chan struct{}, CoreCount) 29 | for _, ch := range chunks { 30 | wg.Add(1) 31 | prefetch <- struct{}{} 32 | go func(start, end int) { 33 | defer wg.Done() 34 | defer func() { <-prefetch }() 35 | sort.Slice(data[start:end], func(i, j int) bool { 36 | return less(data[start+i], data[start+j]) 37 | }) 38 | }(ch.start, ch.end) 39 | } 40 | wg.Wait() 41 | 42 | dst := make([]T, n) 43 | src := data 44 | 45 | for len(chunks) > 1 { 46 | merged := make([]chunk, 0, (len(chunks)+1)/2) 47 | var mWg sync.WaitGroup 48 | mergePrefetch := make(chan struct{}, CoreCount) 49 | 50 | for i := 0; i < len(chunks); i += 2 { 51 | if i+1 == len(chunks) { 52 | copy(dst[chunks[i].start:chunks[i].end], src[chunks[i].start:chunks[i].end]) 53 | merged = append(merged, chunks[i]) 54 | continue 55 | } 56 | 57 | a, b := chunks[i], chunks[i+1] 58 | outStart := a.start 59 | outEnd := b.end 60 | merged = append(merged, chunk{outStart, outEnd}) 61 | 62 | mWg.Add(1) 63 | mergePrefetch <- struct{}{} 64 | go func(a, b chunk) { 65 | defer mWg.Done() 66 | defer func() { <-mergePrefetch }() 67 | i, j, k := a.start, b.start, a.start 68 | for i < a.end && j < b.end { 69 | if less(src[i], src[j]) { 70 | dst[k] = src[i] 71 | i++ 72 | } else { 73 | dst[k] = src[j] 74 | j++ 75 | } 76 | k++ 77 | } 78 | copy(dst[k:], src[i:a.end]) 79 | copy(dst[k+(a.end-i):], src[j:b.end]) 80 | }(a, b) 81 | } 82 | mWg.Wait() 83 | src, dst = dst, src 84 | chunks = merged 85 | } 86 | 87 | if &src[0] != &data[0] { 88 | copy(data, src) 89 | } 90 | } 91 | 92 | /* Benchmark results 93 | 94 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_1000-8 641217 112847 ns/op 52320 B/op 72 allocs/op 95 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_10000-8 101338 711605 ns/op 494691 B/op 72 allocs/op 96 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_100000-8 18980 3834272 ns/op 4803692 B/op 72 allocs/op 97 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_1000000-8 2244 31901014 ns/op 48008297 B/op 72 allocs/op 98 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_10000000-8 279 250564309 ns/op 480005222 B/op 72 allocs/op 99 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_100000000-8 28 2580231136 ns/op 4800007267 B/op 72 allocs/op 100 | */ 101 | -------------------------------------------------------------------------------- /experimental/MinimalCopyMergeSort.go: -------------------------------------------------------------------------------- 1 | package experimental 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func MinimalCopyMergeSort[T any](data []T, less func(a, b T) bool) { 9 | n := len(data) 10 | if n <= 1 { 11 | return 12 | } 13 | 14 | chunkSize := (n + CoreCount - 1) / CoreCount 15 | chunks := make([][]T, 0, CoreCount) 16 | 17 | for i := 0; i < n; i += chunkSize { 18 | end := i + chunkSize 19 | if end > n { 20 | end = n 21 | } 22 | chunk := make([]T, end-i) 23 | copy(chunk, data[i:end]) 24 | chunks = append(chunks, chunk) 25 | } 26 | 27 | var wg sync.WaitGroup 28 | for i := range chunks { 29 | wg.Add(1) 30 | go func(i int) { 31 | defer wg.Done() 32 | sort.Slice(chunks[i], func(a, b int) bool { 33 | return less(chunks[i][a], chunks[i][b]) 34 | }) 35 | }(i) 36 | } 37 | wg.Wait() 38 | 39 | buffer := make([]T, len(data)) 40 | toggle := false 41 | 42 | // Merge chunks pairwise into shared buffer to reduce allocations 43 | for len(chunks) > 1 { 44 | mergedChunks := make([][]T, (len(chunks)+1)/2) 45 | var mWg sync.WaitGroup 46 | 47 | for i := 0; i < len(chunks); i += 2 { 48 | if i+1 == len(chunks) { 49 | mergedChunks[i/2] = chunks[i] 50 | continue 51 | } 52 | 53 | mWg.Add(1) 54 | go func(i int) { 55 | defer mWg.Done() 56 | a, b := chunks[i], chunks[i+1] 57 | start := 0 58 | for j := 0; j < i; j += 2 { 59 | start += len(chunks[j]) + len(chunks[j+1]) 60 | } 61 | mergeIntoBuffer(buffer[start:], a, b, less) 62 | mergedChunks[i/2] = buffer[start : start+len(a)+len(b)] 63 | }(i) 64 | } 65 | mWg.Wait() 66 | chunks = mergedChunks 67 | toggle = !toggle 68 | } 69 | 70 | copy(data, chunks[0]) 71 | } 72 | 73 | /* Benchmark results 74 | 75 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_1000-8 690885 108141 ns/op 76361 B/op 73 allocs/op 76 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_10000-8 89863 821504 ns/op 756302 B/op 73 allocs/op 77 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_100000-8 15216 4713591 ns/op 7228005 B/op 73 allocs/op 78 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_1000000-8 1728 41428352 ns/op 72059502 B/op 73 allocs/op 79 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_10000000-8 258 292931133 ns/op 720063072 B/op 73 allocs/op 80 | BenchmarkSortStruct_Arbitrary/Arbitrary_SortStruct_100000000-8 26 2726562098 ns/op 7200066175 B/op 73 allocs/op 81 | */ 82 | -------------------------------------------------------------------------------- /experimental/utils.go: -------------------------------------------------------------------------------- 1 | package experimental 2 | 3 | import ( 4 | "runtime" 5 | ) 6 | 7 | var ( 8 | CoreCount = runtime.NumCPU() 9 | ) 10 | 11 | func mergeIntoBuffer[T any](dst, a, b []T, less func(a, b T) bool) { 12 | i, j, k := 0, 0, 0 13 | for i < len(a) && j < len(b) { 14 | if less(a[i], b[j]) { 15 | dst[k] = a[i] 16 | i++ 17 | } else { 18 | dst[k] = b[j] 19 | j++ 20 | } 21 | k++ 22 | } 23 | for i < len(a) { 24 | dst[k] = a[i] 25 | i++ 26 | k++ 27 | } 28 | for j < len(b) { 29 | dst[k] = b[j] 30 | j++ 31 | k++ 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /float32.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Float32Asc(data []float32) { 9 | float32Sort(data, false) 10 | } 11 | 12 | func Float32Desc(data []float32) { 13 | float32Sort(data, true) 14 | } 15 | 16 | func float32Sort(data []float32, reverse bool) { 17 | n := len(data) 18 | if n < Float32MinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | float32Reverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]float32, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []float32) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]float32, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = float32MergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | float32Reverse(data) 74 | } 75 | } 76 | 77 | func float32MergeSorted(a, b []float32) []float32 { 78 | res := make([]float32, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func float32Reverse(a []float32) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /float32_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestFloat32Asc_EmptySlice(t *testing.T) { 10 | var data []float32 11 | Float32Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestFloat32Desc_EmptySlice(t *testing.T) { 18 | var data []float32 19 | Float32Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestFloat32Asc_SingleElement(t *testing.T) { 26 | data := []float32{3.14} 27 | Float32Asc(data) 28 | if data[0] != 3.14 { 29 | t.Errorf("expected [3.14], got %v", data) 30 | } 31 | } 32 | 33 | func TestFloat32Desc_SingleElement(t *testing.T) { 34 | data := []float32{3.14} 35 | Float32Desc(data) 36 | if data[0] != 3.14 { 37 | t.Errorf("expected [3.14], got %v", data) 38 | } 39 | } 40 | 41 | func TestFloat32Asc_AllEqual(t *testing.T) { 42 | data := make([]float32, 1000) 43 | for i := range data { 44 | data[i] = 7.5 45 | } 46 | Float32Asc(data) 47 | for _, v := range data { 48 | if v != 7.5 { 49 | t.Errorf("expected all elements to be 7.5, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestFloat32Desc_AllEqual(t *testing.T) { 56 | data := make([]float32, 1000) 57 | for i := range data { 58 | data[i] = 7.5 59 | } 60 | Float32Desc(data) 61 | for _, v := range data { 62 | if v != 7.5 { 63 | t.Errorf("expected all elements to be 7.5, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestFloat32Asc_AlreadySorted(t *testing.T) { 70 | data := make([]float32, 10000) 71 | for i := range data { 72 | data[i] = float32(i) 73 | } 74 | Float32Asc(data) 75 | for i := 1; i < len(data); i++ { 76 | if data[i-1] > data[i] { 77 | t.Errorf("slice not sorted in ascending order") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestFloat32Desc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]float32, n) 86 | for i := range data { 87 | data[i] = float32(n - i - 1) 88 | } 89 | Float32Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("slice not sorted in descending order") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestFloat32Asc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]float32, n) 101 | for i := range data { 102 | data[i] = float32(n - i) 103 | } 104 | Float32Asc(data) 105 | for i := 1; i < len(data); i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestFloat32Desc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]float32, n) 116 | for i := range data { 117 | data[i] = float32(i) 118 | } 119 | Float32Desc(data) 120 | for i := 1; i < len(data); i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestFloat32Asc_SmallRandom(t *testing.T) { 129 | data := genFloat32s(1000) 130 | expected := append([]float32(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | Float32Asc(data) 133 | if !float32SlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for small ascending slice") 135 | } 136 | } 137 | 138 | func TestFloat32Desc_SmallRandom(t *testing.T) { 139 | data := genFloat32s(1000) 140 | expected := append([]float32(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | Float32Desc(data) 143 | if !float32SlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for small descending slice") 145 | } 146 | } 147 | 148 | func TestFloat32Asc_LargeRandom(t *testing.T) { 149 | data := genFloat32s(2000000) 150 | expected := append([]float32(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | Float32Asc(data) 153 | if !float32SlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for large slice") 155 | } 156 | } 157 | 158 | func TestFloat32Desc_LargeRandom(t *testing.T) { 159 | data := genFloat32s(2000000) 160 | expected := append([]float32(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | Float32Desc(data) 163 | if !float32SlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for large descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortFloat32Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Float32_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genFloat32s(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]float32, len(data)) 176 | copy(tmp, data) 177 | Float32Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortFloat32Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Float32_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genFloat32s(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]float32, len(data)) 191 | copy(tmp, data) 192 | Float32Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /float64.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Float64Asc(data []float64) { 9 | float64Sort(data, false) 10 | } 11 | 12 | func Float64Desc(data []float64) { 13 | float64Sort(data, true) 14 | } 15 | 16 | func float64Sort(data []float64, reverse bool) { 17 | n := len(data) 18 | if n < Float64MinParallelSize { 19 | sort.Float64s(data) 20 | if reverse { 21 | float64Reverse(data) 22 | } 23 | return 24 | } 25 | 26 | chunkSize := (n + CoreCount - 1) / CoreCount 27 | 28 | chunks := make([][]float64, 0, CoreCount) 29 | for i := 0; i < n; i += chunkSize { 30 | end := i + chunkSize 31 | if end > n { 32 | end = n 33 | } 34 | chunks = append(chunks, data[i:end]) 35 | } 36 | 37 | var wg sync.WaitGroup 38 | for _, chunk := range chunks { 39 | wg.Add(1) 40 | go func(c []float64) { 41 | defer wg.Done() 42 | sort.Float64s(c) 43 | }(chunk) 44 | } 45 | wg.Wait() 46 | 47 | // Parallel merging loop 48 | for len(chunks) > 1 { 49 | mergedCount := (len(chunks) + 1) / 2 50 | merged := make([][]float64, mergedCount) 51 | 52 | var mWg sync.WaitGroup 53 | for i := 0; i < len(chunks); i += 2 { 54 | if i+1 == len(chunks) { 55 | merged[i/2] = chunks[i] 56 | continue 57 | } 58 | mWg.Add(1) 59 | go func(i int) { 60 | defer mWg.Done() 61 | merged[i/2] = float64MergeSorted(chunks[i], chunks[i+1]) 62 | }(i) 63 | } 64 | mWg.Wait() 65 | chunks = merged 66 | } 67 | 68 | copy(data, chunks[0]) 69 | if reverse { 70 | float64Reverse(data) 71 | } 72 | } 73 | 74 | func float64MergeSorted(a, b []float64) []float64 { 75 | res := make([]float64, len(a)+len(b)) 76 | i, j, k := 0, 0, 0 77 | for i < len(a) && j < len(b) { 78 | if a[i] <= b[j] { 79 | res[k] = a[i] 80 | i++ 81 | } else { 82 | res[k] = b[j] 83 | j++ 84 | } 85 | k++ 86 | } 87 | for i < len(a) { 88 | res[k] = a[i] 89 | i++ 90 | k++ 91 | } 92 | for j < len(b) { 93 | res[k] = b[j] 94 | j++ 95 | k++ 96 | } 97 | return res 98 | } 99 | 100 | func float64Reverse(a []float64) { 101 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 102 | a[i], a[j] = a[j], a[i] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /float64_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestFloat64Asc_EmptySlice(t *testing.T) { 10 | var data []float64 11 | Float64Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestFloat64Desc_EmptySlice(t *testing.T) { 18 | var data []float64 19 | Float64Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestFloat64Asc_SingleElement(t *testing.T) { 26 | data := []float64{42.0} 27 | Float64Asc(data) 28 | if data[0] != 42.0 { 29 | t.Errorf("expected [42.0], got %v", data) 30 | } 31 | } 32 | 33 | func TestFloat64Desc_SingleElement(t *testing.T) { 34 | data := []float64{42.0} 35 | Float64Desc(data) 36 | if data[0] != 42.0 { 37 | t.Errorf("expected [42.0], got %v", data) 38 | } 39 | } 40 | 41 | func TestFloat64Asc_AllEqual(t *testing.T) { 42 | data := make([]float64, 1000) 43 | for i := range data { 44 | data[i] = 3.14 45 | } 46 | Float64Asc(data) 47 | for _, v := range data { 48 | if v != 3.14 { 49 | t.Errorf("expected all elements to be 3.14, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestFloat64Desc_AllEqual(t *testing.T) { 56 | data := make([]float64, 1000) 57 | for i := range data { 58 | data[i] = 3.14 59 | } 60 | Float64Desc(data) 61 | for _, v := range data { 62 | if v != 3.14 { 63 | t.Errorf("expected all elements to be 3.14, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestFloat64Asc_AlreadySorted(t *testing.T) { 70 | data := make([]float64, 10000) 71 | for i := range data { 72 | data[i] = float64(i) 73 | } 74 | Float64Asc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != float64(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestFloat64Desc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]float64, n) 86 | for i := range data { 87 | data[i] = float64(n - i - 1) 88 | } 89 | Float64Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected already descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestFloat64Asc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]float64, n) 101 | for i := range data { 102 | data[i] = float64(n - i) 103 | } 104 | Float64Asc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestFloat64Desc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]float64, n) 116 | for i := range data { 117 | data[i] = float64(i) 118 | } 119 | Float64Desc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestFloat64Asc_LargeRandom(t *testing.T) { 129 | data := genFloats(2000000) 130 | expected := append([]float64(nil), data...) 131 | sort.Float64s(expected) 132 | Float64Asc(data) 133 | if !floatSlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for large slice") 135 | } 136 | } 137 | 138 | func TestFloat64Desc_LargeRandom(t *testing.T) { 139 | data := genFloats(2000000) 140 | expected := append([]float64(nil), data...) 141 | sort.Sort(sort.Reverse(sort.Float64Slice(expected))) 142 | Float64Desc(data) 143 | if !floatSlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for large descending slice") 145 | } 146 | } 147 | 148 | func TestFloat64Asc_SmallRandom(t *testing.T) { 149 | data := genFloats(1000) 150 | expected := append([]float64(nil), data...) 151 | sort.Float64s(expected) 152 | Float64Asc(data) 153 | if !floatSlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for small ascending slice") 155 | } 156 | } 157 | 158 | func TestFloat64Desc_SmallRandom(t *testing.T) { 159 | data := genFloats(1000) 160 | expected := append([]float64(nil), data...) 161 | sort.Sort(sort.Reverse(sort.Float64Slice(expected))) 162 | Float64Desc(data) 163 | if !floatSlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for small descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortFloat64Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Float64_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genFloats(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]float64, len(data)) 176 | copy(tmp, data) 177 | Float64Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortFloat64Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Float64_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genFloats(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]float64, len(data)) 191 | copy(tmp, data) 192 | Float64Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/rah-0/parsort 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rah-0/parsort/9861ee523218c3afde170f3ebf2743cbb5ba534f/go.sum -------------------------------------------------------------------------------- /int.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func IntAsc(data []int) { 9 | intSort(data, false) 10 | } 11 | 12 | func IntDesc(data []int) { 13 | intSort(data, true) 14 | } 15 | 16 | func intSort(data []int, reverse bool) { 17 | n := len(data) 18 | if n < IntMinParallelSize { 19 | sort.Ints(data) 20 | if reverse { 21 | intReverse(data) 22 | } 23 | return 24 | } 25 | 26 | chunkSize := (n + CoreCount - 1) / CoreCount 27 | 28 | chunks := make([][]int, 0, CoreCount) 29 | for i := 0; i < n; i += chunkSize { 30 | end := i + chunkSize 31 | if end > n { 32 | end = n 33 | } 34 | chunks = append(chunks, data[i:end]) 35 | } 36 | 37 | var wg sync.WaitGroup 38 | for _, chunk := range chunks { 39 | wg.Add(1) 40 | go func(c []int) { 41 | defer wg.Done() 42 | sort.Ints(c) 43 | }(chunk) 44 | } 45 | wg.Wait() 46 | 47 | // Parallel merging loop 48 | for len(chunks) > 1 { 49 | mergedCount := (len(chunks) + 1) / 2 50 | merged := make([][]int, mergedCount) 51 | 52 | var mWg sync.WaitGroup 53 | for i := 0; i < len(chunks); i += 2 { 54 | if i+1 == len(chunks) { 55 | merged[i/2] = chunks[i] 56 | continue 57 | } 58 | mWg.Add(1) 59 | go func(i int) { 60 | defer mWg.Done() 61 | merged[i/2] = intMergeSorted(chunks[i], chunks[i+1]) 62 | }(i) 63 | } 64 | mWg.Wait() 65 | chunks = merged 66 | } 67 | 68 | copy(data, chunks[0]) 69 | if reverse { 70 | intReverse(data) 71 | } 72 | } 73 | 74 | func intMergeSorted(a, b []int) []int { 75 | res := make([]int, len(a)+len(b)) 76 | i, j, k := 0, 0, 0 77 | for i < len(a) && j < len(b) { 78 | if a[i] <= b[j] { 79 | res[k] = a[i] 80 | i++ 81 | } else { 82 | res[k] = b[j] 83 | j++ 84 | } 85 | k++ 86 | } 87 | for i < len(a) { 88 | res[k] = a[i] 89 | i++ 90 | k++ 91 | } 92 | for j < len(b) { 93 | res[k] = b[j] 94 | j++ 95 | k++ 96 | } 97 | return res 98 | } 99 | 100 | func intReverse(a []int) { 101 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 102 | a[i], a[j] = a[j], a[i] 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /int16.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Int16Asc(data []int16) { 9 | int16Sort(data, false) 10 | } 11 | 12 | func Int16Desc(data []int16) { 13 | int16Sort(data, true) 14 | } 15 | 16 | func int16Sort(data []int16, reverse bool) { 17 | n := len(data) 18 | if n < Int16MinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | int16Reverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]int16, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []int16) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]int16, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = int16MergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | int16Reverse(data) 74 | } 75 | } 76 | 77 | func int16MergeSorted(a, b []int16) []int16 { 78 | res := make([]int16, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func int16Reverse(a []int16) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /int16_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestInt16Asc_EmptySlice(t *testing.T) { 10 | var data []int16 11 | Int16Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestInt16Desc_EmptySlice(t *testing.T) { 18 | var data []int16 19 | Int16Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestInt16Asc_SingleElement(t *testing.T) { 26 | data := []int16{42} 27 | Int16Asc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestInt16Desc_SingleElement(t *testing.T) { 34 | data := []int16{42} 35 | Int16Desc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestInt16Asc_AllEqual(t *testing.T) { 42 | data := make([]int16, 1000) 43 | for i := range data { 44 | data[i] = 7 45 | } 46 | Int16Asc(data) 47 | for _, v := range data { 48 | if v != 7 { 49 | t.Errorf("expected all elements to be 7, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestInt16Desc_AllEqual(t *testing.T) { 56 | data := make([]int16, 1000) 57 | for i := range data { 58 | data[i] = 7 59 | } 60 | Int16Desc(data) 61 | for _, v := range data { 62 | if v != 7 { 63 | t.Errorf("expected all elements to be 7, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestInt16Asc_AlreadySorted(t *testing.T) { 70 | data := make([]int16, 10000) 71 | for i := range data { 72 | data[i] = int16(i) 73 | } 74 | Int16Asc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != int16(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestInt16Desc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]int16, n) 86 | for i := range data { 87 | data[i] = int16(n - i - 1) 88 | } 89 | Int16Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestInt16Asc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]int16, n) 101 | for i := range data { 102 | data[i] = int16(n - i) 103 | } 104 | Int16Asc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestInt16Desc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]int16, n) 116 | for i := range data { 117 | data[i] = int16(i) 118 | } 119 | Int16Desc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestInt16Asc_SmallRandom(t *testing.T) { 129 | data := genInt16s(1000) 130 | expected := append([]int16(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | Int16Asc(data) 133 | if !int16SlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for small ascending slice") 135 | } 136 | } 137 | 138 | func TestInt16Desc_SmallRandom(t *testing.T) { 139 | data := genInt16s(1000) 140 | expected := append([]int16(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | Int16Desc(data) 143 | if !int16SlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for small descending slice") 145 | } 146 | } 147 | 148 | func TestInt16Asc_LargeRandom(t *testing.T) { 149 | data := genInt16s(2000000) 150 | expected := append([]int16(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | Int16Asc(data) 153 | if !int16SlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for large slice") 155 | } 156 | } 157 | 158 | func TestInt16Desc_LargeRandom(t *testing.T) { 159 | data := genInt16s(2000000) 160 | expected := append([]int16(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | Int16Desc(data) 163 | if !int16SlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for large descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortInt16Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Int16_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genInt16s(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]int16, len(data)) 176 | copy(tmp, data) 177 | Int16Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortInt16Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Int16_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genInt16s(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]int16, len(data)) 191 | copy(tmp, data) 192 | Int16Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /int32.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Int32Asc(data []int32) { 9 | int32Sort(data, false) 10 | } 11 | 12 | func Int32Desc(data []int32) { 13 | int32Sort(data, true) 14 | } 15 | 16 | func int32Sort(data []int32, reverse bool) { 17 | n := len(data) 18 | if n < Int32MinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | int32Reverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]int32, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []int32) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]int32, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = int32MergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | int32Reverse(data) 74 | } 75 | } 76 | 77 | func int32MergeSorted(a, b []int32) []int32 { 78 | res := make([]int32, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func int32Reverse(a []int32) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /int32_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestInt32Asc_EmptySlice(t *testing.T) { 10 | var data []int32 11 | Int32Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestInt32Desc_EmptySlice(t *testing.T) { 18 | var data []int32 19 | Int32Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestInt32Asc_SingleElement(t *testing.T) { 26 | data := []int32{42} 27 | Int32Asc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestInt32Desc_SingleElement(t *testing.T) { 34 | data := []int32{42} 35 | Int32Desc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestInt32Asc_AllEqual(t *testing.T) { 42 | data := make([]int32, 1000) 43 | for i := range data { 44 | data[i] = 7 45 | } 46 | Int32Asc(data) 47 | for _, v := range data { 48 | if v != 7 { 49 | t.Errorf("expected all elements to be 7, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestInt32Desc_AllEqual(t *testing.T) { 56 | data := make([]int32, 1000) 57 | for i := range data { 58 | data[i] = 7 59 | } 60 | Int32Desc(data) 61 | for _, v := range data { 62 | if v != 7 { 63 | t.Errorf("expected all elements to be 7, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestInt32Asc_AlreadySorted(t *testing.T) { 70 | data := make([]int32, 10000) 71 | for i := range data { 72 | data[i] = int32(i) 73 | } 74 | Int32Asc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != int32(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestInt32Desc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]int32, n) 86 | for i := range data { 87 | data[i] = int32(n - i - 1) 88 | } 89 | Int32Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected already descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestInt32Asc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]int32, n) 101 | for i := range data { 102 | data[i] = int32(n - i) 103 | } 104 | Int32Asc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestInt32Desc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]int32, n) 116 | for i := range data { 117 | data[i] = int32(i) 118 | } 119 | Int32Desc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestInt32Asc_LargeRandom(t *testing.T) { 129 | data := genInt32s(2000000) 130 | expected := append([]int32(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | Int32Asc(data) 133 | if !int32SlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for large slice") 135 | } 136 | } 137 | 138 | func TestInt32Desc_LargeRandom(t *testing.T) { 139 | data := genInt32s(2000000) 140 | expected := append([]int32(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | Int32Desc(data) 143 | if !int32SlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for large descending slice") 145 | } 146 | } 147 | 148 | func TestInt32Asc_SmallRandom(t *testing.T) { 149 | data := genInt32s(1000) 150 | expected := append([]int32(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | Int32Asc(data) 153 | if !int32SlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for small ascending slice") 155 | } 156 | } 157 | 158 | func TestInt32Desc_SmallRandom(t *testing.T) { 159 | data := genInt32s(1000) 160 | expected := append([]int32(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | Int32Desc(data) 163 | if !int32SlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for small descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortInt32Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Int32_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genInt32s(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]int32, len(data)) 176 | copy(tmp, data) 177 | Int32Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortInt32Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Int32_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genInt32s(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]int32, len(data)) 191 | copy(tmp, data) 192 | Int32Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /int64.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Int64Asc(data []int64) { 9 | int64Sort(data, false) 10 | } 11 | 12 | func Int64Desc(data []int64) { 13 | int64Sort(data, true) 14 | } 15 | 16 | func int64Sort(data []int64, reverse bool) { 17 | n := len(data) 18 | if n < Int64MinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | int64Reverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]int64, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []int64) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]int64, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = int64MergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | int64Reverse(data) 74 | } 75 | } 76 | 77 | func int64MergeSorted(a, b []int64) []int64 { 78 | res := make([]int64, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func int64Reverse(a []int64) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /int64_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestInt64Asc_EmptySlice(t *testing.T) { 10 | var data []int64 11 | Int64Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestInt64Desc_EmptySlice(t *testing.T) { 18 | var data []int64 19 | Int64Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestInt64Asc_SingleElement(t *testing.T) { 26 | data := []int64{42} 27 | Int64Asc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestInt64Desc_SingleElement(t *testing.T) { 34 | data := []int64{42} 35 | Int64Desc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestInt64Asc_AllEqual(t *testing.T) { 42 | data := make([]int64, 1000) 43 | for i := range data { 44 | data[i] = 7 45 | } 46 | Int64Asc(data) 47 | for _, v := range data { 48 | if v != 7 { 49 | t.Errorf("expected all elements to be 7, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestInt64Desc_AllEqual(t *testing.T) { 56 | data := make([]int64, 1000) 57 | for i := range data { 58 | data[i] = 7 59 | } 60 | Int64Desc(data) 61 | for _, v := range data { 62 | if v != 7 { 63 | t.Errorf("expected all elements to be 7, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestInt64Asc_AlreadySorted(t *testing.T) { 70 | data := make([]int64, 10000) 71 | for i := range data { 72 | data[i] = int64(i) 73 | } 74 | Int64Asc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != int64(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestInt64Desc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]int64, n) 86 | for i := range data { 87 | data[i] = int64(n - i - 1) 88 | } 89 | Int64Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestInt64Asc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]int64, n) 101 | for i := range data { 102 | data[i] = int64(n - i) 103 | } 104 | Int64Asc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestInt64Desc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]int64, n) 116 | for i := range data { 117 | data[i] = int64(i) 118 | } 119 | Int64Desc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestInt64Asc_SmallRandom(t *testing.T) { 129 | data := genInt64s(1000) 130 | expected := append([]int64(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | Int64Asc(data) 133 | if !int64SlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for small ascending slice") 135 | } 136 | } 137 | 138 | func TestInt64Desc_SmallRandom(t *testing.T) { 139 | data := genInt64s(1000) 140 | expected := append([]int64(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | Int64Desc(data) 143 | if !int64SlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for small descending slice") 145 | } 146 | } 147 | 148 | func TestInt64Asc_LargeRandom(t *testing.T) { 149 | data := genInt64s(2000000) 150 | expected := append([]int64(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | Int64Asc(data) 153 | if !int64SlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for large slice") 155 | } 156 | } 157 | 158 | func TestInt64Desc_LargeRandom(t *testing.T) { 159 | data := genInt64s(2000000) 160 | expected := append([]int64(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | Int64Desc(data) 163 | if !int64SlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for large descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortInt64Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Int64_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genInt64s(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]int64, len(data)) 176 | copy(tmp, data) 177 | Int64Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortInt64Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Int64_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genInt64s(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]int64, len(data)) 191 | copy(tmp, data) 192 | Int64Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /int8.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Int8Asc(data []int8) { 9 | int8Sort(data, false) 10 | } 11 | 12 | func Int8Desc(data []int8) { 13 | int8Sort(data, true) 14 | } 15 | 16 | func int8Sort(data []int8, reverse bool) { 17 | n := len(data) 18 | if n < Int8MinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | int8Reverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]int8, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []int8) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]int8, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = int8MergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | int8Reverse(data) 74 | } 75 | } 76 | 77 | func int8MergeSorted(a, b []int8) []int8 { 78 | res := make([]int8, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func int8Reverse(a []int8) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /int8_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestInt8Asc_EmptySlice(t *testing.T) { 10 | var data []int8 11 | Int8Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestInt8Desc_EmptySlice(t *testing.T) { 18 | var data []int8 19 | Int8Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestInt8Asc_SingleElement(t *testing.T) { 26 | data := []int8{42} 27 | Int8Asc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestInt8Desc_SingleElement(t *testing.T) { 34 | data := []int8{42} 35 | Int8Desc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestInt8Asc_AllEqual(t *testing.T) { 42 | data := make([]int8, 1000) 43 | for i := range data { 44 | data[i] = 7 45 | } 46 | Int8Asc(data) 47 | for _, v := range data { 48 | if v != 7 { 49 | t.Errorf("expected all elements to be 7, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestInt8Desc_AllEqual(t *testing.T) { 56 | data := make([]int8, 1000) 57 | for i := range data { 58 | data[i] = 7 59 | } 60 | Int8Desc(data) 61 | for _, v := range data { 62 | if v != 7 { 63 | t.Errorf("expected all elements to be 7, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestInt8Asc_AlreadySorted(t *testing.T) { 70 | data := make([]int8, 127) 71 | for i := range data { 72 | data[i] = int8(i) 73 | } 74 | Int8Asc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != int8(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestInt8Desc_AlreadySorted(t *testing.T) { 84 | n := 127 85 | data := make([]int8, n) 86 | for i := range data { 87 | data[i] = int8(n - i - 1) 88 | } 89 | Int8Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected already descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestInt8Asc_ReverseSorted(t *testing.T) { 99 | n := 127 100 | data := make([]int8, n) 101 | for i := range data { 102 | data[i] = int8(n - i) 103 | } 104 | Int8Asc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestInt8Desc_ReverseSorted(t *testing.T) { 114 | n := 127 115 | data := make([]int8, n) 116 | for i := range data { 117 | data[i] = int8(i) 118 | } 119 | Int8Desc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestInt8Asc_LargeRandom(t *testing.T) { 129 | data := genInt8s(2000000) 130 | expected := append([]int8(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | Int8Asc(data) 133 | if !int8SlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for large slice") 135 | } 136 | } 137 | 138 | func TestInt8Desc_LargeRandom(t *testing.T) { 139 | data := genInt8s(2000000) 140 | expected := append([]int8(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | Int8Desc(data) 143 | if !int8SlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for large descending slice") 145 | } 146 | } 147 | 148 | func TestInt8Asc_SmallRandom(t *testing.T) { 149 | data := genInt8s(1000) 150 | expected := append([]int8(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | Int8Asc(data) 153 | if !int8SlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for small ascending slice") 155 | } 156 | } 157 | 158 | func TestInt8Desc_SmallRandom(t *testing.T) { 159 | data := genInt8s(1000) 160 | expected := append([]int8(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | Int8Desc(data) 163 | if !int8SlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for small descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortInt8Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Int8_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genInt8s(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]int8, len(data)) 176 | copy(tmp, data) 177 | Int8Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortInt8Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Int8_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genInt8s(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]int8, len(data)) 191 | copy(tmp, data) 192 | Int8Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /int_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestIntAsc_EmptySlice(t *testing.T) { 10 | var data []int 11 | IntAsc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestIntDesc_EmptySlice(t *testing.T) { 18 | var data []int 19 | IntDesc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestIntAsc_SingleElement(t *testing.T) { 26 | data := []int{42} 27 | IntAsc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestIntDesc_SingleElement(t *testing.T) { 34 | data := []int{42} 35 | IntDesc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestIntAsc_AllEqual(t *testing.T) { 42 | data := make([]int, 1000) 43 | for i := range data { 44 | data[i] = 7 45 | } 46 | IntAsc(data) 47 | for _, v := range data { 48 | if v != 7 { 49 | t.Errorf("expected all elements to be 7, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestIntDesc_AllEqual(t *testing.T) { 56 | data := make([]int, 1000) 57 | for i := range data { 58 | data[i] = 7 59 | } 60 | IntDesc(data) 61 | for _, v := range data { 62 | if v != 7 { 63 | t.Errorf("expected all elements to be 7, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestIntAsc_AlreadySorted(t *testing.T) { 70 | data := make([]int, 10000) 71 | for i := range data { 72 | data[i] = i 73 | } 74 | IntAsc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != i { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestIntDesc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]int, n) 86 | for i := range data { 87 | data[i] = n - i - 1 88 | } 89 | IntDesc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected already descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestIntAsc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]int, n) 101 | for i := range data { 102 | data[i] = n - i 103 | } 104 | IntAsc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestIntDesc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]int, n) 116 | for i := range data { 117 | data[i] = i 118 | } 119 | IntDesc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestIntAsc_LargeRandom(t *testing.T) { 129 | data := genInts(2000000) 130 | expected := append([]int(nil), data...) 131 | sort.Ints(expected) 132 | IntAsc(data) 133 | if !intSlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for large slice") 135 | } 136 | } 137 | 138 | func TestIntDesc_LargeRandom(t *testing.T) { 139 | data := genInts(2000000) 140 | expected := append([]int(nil), data...) 141 | sort.Sort(sort.Reverse(sort.IntSlice(expected))) 142 | IntDesc(data) 143 | if !intSlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for large descending slice") 145 | } 146 | } 147 | 148 | func TestIntAsc_SmallRandom(t *testing.T) { 149 | data := genInts(1000) 150 | expected := append([]int(nil), data...) 151 | sort.Ints(expected) 152 | IntAsc(data) 153 | if !intSlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for small ascending slice") 155 | } 156 | } 157 | 158 | func TestIntDesc_SmallRandom(t *testing.T) { 159 | data := genInts(1000) 160 | expected := append([]int(nil), data...) 161 | sort.Sort(sort.Reverse(sort.IntSlice(expected))) 162 | IntDesc(data) 163 | if !intSlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for small descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortIntAsc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Int_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genInts(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]int, len(data)) 176 | copy(tmp, data) 177 | IntAsc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortIntDesc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Int_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genInts(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]int, len(data)) 191 | copy(tmp, data) 192 | IntDesc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /scripts/test.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Exit on first error 3 | set -e 4 | 5 | # Navigate to the parent directory where the project is 6 | cd "$(dirname "$0")/.." 7 | 8 | # Define Go versions to test 9 | go_versions=("1.24.0" "1.23.0" "1.22.0" "1.21.0" "1.20" "1.19" "1.18" "1.17" "1.16" "1.15" "1.14" "1.13" "1.12") 10 | 11 | # Install and test with different Go versions 12 | for version in "${go_versions[@]}" 13 | do 14 | echo "Setting up go$version" 15 | go install golang.org/dl/go$version@latest 16 | go$version download 17 | 18 | echo "Testing with go$version" 19 | # Clear cache and run test 20 | go$version clean -testcache 21 | go$version test ./... -v -race -covermode=atomic -timeout 1h 22 | done 23 | -------------------------------------------------------------------------------- /string.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func StringAsc(data []string) { 9 | stringSort(data, false) 10 | } 11 | 12 | func StringDesc(data []string) { 13 | stringSort(data, true) 14 | } 15 | 16 | func stringSort(data []string, reverse bool) { 17 | n := len(data) 18 | if n < StringMinParallelSize { 19 | sort.Strings(data) 20 | if reverse { 21 | stringReverse(data) 22 | } 23 | return 24 | } 25 | 26 | chunkSize := (n + CoreCount - 1) / CoreCount 27 | 28 | chunks := make([][]string, 0, CoreCount) 29 | for i := 0; i < n; i += chunkSize { 30 | end := i + chunkSize 31 | if end > n { 32 | end = n 33 | } 34 | chunks = append(chunks, data[i:end]) 35 | } 36 | 37 | var wg sync.WaitGroup 38 | for _, chunk := range chunks { 39 | wg.Add(1) 40 | go func(c []string) { 41 | defer wg.Done() 42 | sort.Strings(c) 43 | }(chunk) 44 | } 45 | wg.Wait() 46 | 47 | for len(chunks) > 1 { 48 | mergedCount := (len(chunks) + 1) / 2 49 | merged := make([][]string, mergedCount) 50 | 51 | var mWg sync.WaitGroup 52 | for i := 0; i < len(chunks); i += 2 { 53 | if i+1 == len(chunks) { 54 | merged[i/2] = chunks[i] 55 | continue 56 | } 57 | mWg.Add(1) 58 | go func(i int) { 59 | defer mWg.Done() 60 | merged[i/2] = stringMergeSorted(chunks[i], chunks[i+1]) 61 | }(i) 62 | } 63 | mWg.Wait() 64 | chunks = merged 65 | } 66 | 67 | copy(data, chunks[0]) 68 | if reverse { 69 | stringReverse(data) 70 | } 71 | } 72 | 73 | func stringMergeSorted(a, b []string) []string { 74 | res := make([]string, len(a)+len(b)) 75 | i, j, k := 0, 0, 0 76 | for i < len(a) && j < len(b) { 77 | if a[i] <= b[j] { 78 | res[k] = a[i] 79 | i++ 80 | } else { 81 | res[k] = b[j] 82 | j++ 83 | } 84 | k++ 85 | } 86 | for i < len(a) { 87 | res[k] = a[i] 88 | i++ 89 | k++ 90 | } 91 | for j < len(b) { 92 | res[k] = b[j] 93 | j++ 94 | k++ 95 | } 96 | return res 97 | } 98 | 99 | func stringReverse(a []string) { 100 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 101 | a[i], a[j] = a[j], a[i] 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /string_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestStringAsc_EmptySlice(t *testing.T) { 10 | var data []string 11 | StringAsc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestStringDesc_EmptySlice(t *testing.T) { 18 | var data []string 19 | StringDesc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestStringAsc_SingleElement(t *testing.T) { 26 | data := []string{"abc"} 27 | StringAsc(data) 28 | if data[0] != "abc" { 29 | t.Errorf("expected [\"abc\"], got %v", data) 30 | } 31 | } 32 | 33 | func TestStringDesc_SingleElement(t *testing.T) { 34 | data := []string{"abc"} 35 | StringDesc(data) 36 | if data[0] != "abc" { 37 | t.Errorf("expected [\"abc\"], got %v", data) 38 | } 39 | } 40 | 41 | func TestStringAsc_AllEqual(t *testing.T) { 42 | data := make([]string, 1000) 43 | for i := range data { 44 | data[i] = "same" 45 | } 46 | StringAsc(data) 47 | for _, v := range data { 48 | if v != "same" { 49 | t.Errorf("expected all elements to be \"same\", got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestStringDesc_AllEqual(t *testing.T) { 56 | data := make([]string, 1000) 57 | for i := range data { 58 | data[i] = "same" 59 | } 60 | StringDesc(data) 61 | for _, v := range data { 62 | if v != "same" { 63 | t.Errorf("expected all elements to be \"same\", got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestStringAsc_AlreadySorted(t *testing.T) { 70 | data := []string{"a", "b", "c", "d", "e"} 71 | StringAsc(data) 72 | for i := 1; i < len(data); i++ { 73 | if data[i-1] > data[i] { 74 | t.Errorf("expected sorted slice to remain unchanged") 75 | break 76 | } 77 | } 78 | } 79 | 80 | func TestStringDesc_AlreadySorted(t *testing.T) { 81 | data := []string{"z", "y", "x", "w", "v"} 82 | StringDesc(data) 83 | for i := 1; i < len(data); i++ { 84 | if data[i-1] < data[i] { 85 | t.Errorf("expected descending slice to remain unchanged") 86 | break 87 | } 88 | } 89 | } 90 | 91 | func TestStringAsc_ReverseSorted(t *testing.T) { 92 | data := []string{"z", "y", "x", "w", "v"} 93 | StringAsc(data) 94 | for i := 1; i < len(data); i++ { 95 | if data[i-1] > data[i] { 96 | t.Errorf("slice not sorted in ascending order") 97 | break 98 | } 99 | } 100 | } 101 | 102 | func TestStringDesc_ReverseSorted(t *testing.T) { 103 | data := []string{"a", "b", "c", "d", "e"} 104 | StringDesc(data) 105 | for i := 1; i < len(data); i++ { 106 | if data[i-1] < data[i] { 107 | t.Errorf("slice not sorted in descending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestStringAsc_SmallRandom(t *testing.T) { 114 | data := genStrings(1000) 115 | expected := append([]string(nil), data...) 116 | sort.Strings(expected) 117 | StringAsc(data) 118 | if !stringSlicesEqual(data, expected) { 119 | t.Errorf("sorted result incorrect for small ascending slice") 120 | } 121 | } 122 | 123 | func TestStringDesc_SmallRandom(t *testing.T) { 124 | data := genStrings(1000) 125 | expected := append([]string(nil), data...) 126 | sort.Sort(sort.Reverse(sort.StringSlice(expected))) 127 | StringDesc(data) 128 | if !stringSlicesEqual(data, expected) { 129 | t.Errorf("sorted result incorrect for small descending slice") 130 | } 131 | } 132 | 133 | func TestStringAsc_LargeRandom(t *testing.T) { 134 | data := genStrings(2000000) 135 | expected := append([]string(nil), data...) 136 | sort.Strings(expected) 137 | StringAsc(data) 138 | if !stringSlicesEqual(data, expected) { 139 | t.Errorf("sorted result incorrect for large ascending slice") 140 | } 141 | } 142 | 143 | func TestStringDesc_LargeRandom(t *testing.T) { 144 | data := genStrings(2000000) 145 | expected := append([]string(nil), data...) 146 | sort.Sort(sort.Reverse(sort.StringSlice(expected))) 147 | StringDesc(data) 148 | if !stringSlicesEqual(data, expected) { 149 | t.Errorf("sorted result incorrect for large descending slice") 150 | } 151 | } 152 | 153 | func BenchmarkParsortStringAsc(b *testing.B) { 154 | for _, size := range testSizes { 155 | b.Run("Parsort_Asc_String_"+strconv.Itoa(size), func(b *testing.B) { 156 | data := genStrings(size) 157 | b.ReportAllocs() 158 | b.ResetTimer() 159 | for i := 0; i < b.N; i++ { 160 | tmp := make([]string, len(data)) 161 | copy(tmp, data) 162 | StringAsc(tmp) 163 | } 164 | }) 165 | } 166 | } 167 | 168 | func BenchmarkParsortStringDesc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Desc_String_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genStrings(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]string, len(data)) 176 | copy(tmp, data) 177 | StringDesc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /struct.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | type chunk struct{ start, end int } 9 | 10 | // structSortUnstable sorts a slice using parallel unstable sorting and in-place merging. 11 | func structSortUnstable[T any](data []T, less func(a, b T) bool) { 12 | n := len(data) 13 | if n <= 1 { 14 | return 15 | } 16 | 17 | if n < StructMinParallelSize { 18 | sort.Slice(data, func(i, j int) bool { 19 | return less(data[i], data[j]) 20 | }) 21 | return 22 | } 23 | 24 | chunkSize := (n + CoreCount - 1) / CoreCount 25 | chunks := make([]chunk, 0, CoreCount) 26 | 27 | for i := 0; i < n; i += chunkSize { 28 | end := i + chunkSize 29 | if end > n { 30 | end = n 31 | } 32 | chunks = append(chunks, chunk{i, end}) 33 | } 34 | 35 | var wg sync.WaitGroup 36 | for _, ch := range chunks { 37 | wg.Add(1) 38 | go func(start, end int) { 39 | defer wg.Done() 40 | sort.Slice(data[start:end], func(i, j int) bool { 41 | return less(data[start+i], data[start+j]) 42 | }) 43 | }(ch.start, ch.end) 44 | } 45 | wg.Wait() 46 | 47 | dst := make([]T, n) 48 | src := data 49 | 50 | for len(chunks) > 1 { 51 | merged := make([]chunk, 0, (len(chunks)+1)/2) 52 | var mWg sync.WaitGroup 53 | 54 | for i := 0; i < len(chunks); i += 2 { 55 | if i+1 == len(chunks) { 56 | copy(dst[chunks[i].start:chunks[i].end], src[chunks[i].start:chunks[i].end]) 57 | merged = append(merged, chunks[i]) 58 | continue 59 | } 60 | 61 | a, b := chunks[i], chunks[i+1] 62 | outStart := a.start 63 | outEnd := b.end 64 | merged = append(merged, chunk{outStart, outEnd}) 65 | 66 | mWg.Add(1) 67 | go func(a, b chunk) { 68 | defer mWg.Done() 69 | i, j, k := a.start, b.start, a.start 70 | for i < a.end && j < b.end { 71 | if less(src[i], src[j]) { 72 | dst[k] = src[i] 73 | i++ 74 | } else { 75 | dst[k] = src[j] 76 | j++ 77 | } 78 | k++ 79 | } 80 | copy(dst[k:], src[i:a.end]) 81 | copy(dst[k+(a.end-i):], src[j:b.end]) 82 | }(a, b) 83 | } 84 | mWg.Wait() 85 | src, dst = dst, src 86 | chunks = merged 87 | } 88 | 89 | if &src[0] != &data[0] { 90 | copy(data, src) 91 | } 92 | } 93 | 94 | // structSortStable sorts a slice using parallel stable sorting and in-place merging. 95 | func structSortStable[T any](data []T, less func(a, b T) bool) { 96 | n := len(data) 97 | if n <= 1 { 98 | return 99 | } 100 | 101 | if n < StructMinParallelSize { 102 | sort.SliceStable(data, func(i, j int) bool { 103 | return less(data[i], data[j]) 104 | }) 105 | return 106 | } 107 | 108 | chunkSize := (n + CoreCount - 1) / CoreCount 109 | chunks := make([]chunk, 0, CoreCount) 110 | 111 | for i := 0; i < n; i += chunkSize { 112 | end := i + chunkSize 113 | if end > n { 114 | end = n 115 | } 116 | chunks = append(chunks, chunk{i, end}) 117 | } 118 | 119 | var wg sync.WaitGroup 120 | for _, ch := range chunks { 121 | wg.Add(1) 122 | go func(start, end int) { 123 | defer wg.Done() 124 | sort.SliceStable(data[start:end], func(i, j int) bool { 125 | return less(data[start+i], data[start+j]) 126 | }) 127 | }(ch.start, ch.end) 128 | } 129 | wg.Wait() 130 | 131 | dst := make([]T, n) 132 | src := data 133 | 134 | for len(chunks) > 1 { 135 | merged := make([]chunk, 0, (len(chunks)+1)/2) 136 | var mWg sync.WaitGroup 137 | 138 | for i := 0; i < len(chunks); i += 2 { 139 | if i+1 == len(chunks) { 140 | copy(dst[chunks[i].start:chunks[i].end], src[chunks[i].start:chunks[i].end]) 141 | merged = append(merged, chunks[i]) 142 | continue 143 | } 144 | 145 | a, b := chunks[i], chunks[i+1] 146 | outStart := a.start 147 | outEnd := b.end 148 | merged = append(merged, chunk{outStart, outEnd}) 149 | 150 | mWg.Add(1) 151 | go func(a, b chunk) { 152 | defer mWg.Done() 153 | i, j, k := a.start, b.start, a.start 154 | for i < a.end && j < b.end { 155 | switch { 156 | case less(src[i], src[j]): 157 | dst[k] = src[i] 158 | i++ 159 | case less(src[j], src[i]): 160 | dst[k] = src[j] 161 | j++ 162 | default: 163 | dst[k] = src[i] // preserve left-side element 164 | i++ 165 | } 166 | k++ 167 | } 168 | copy(dst[k:], src[i:a.end]) 169 | copy(dst[k+(a.end-i):], src[j:b.end]) 170 | }(a, b) 171 | } 172 | mWg.Wait() 173 | src, dst = dst, src 174 | chunks = merged 175 | } 176 | 177 | if &src[0] != &data[0] { 178 | copy(data, src) 179 | } 180 | } 181 | 182 | // StructAsc sorts a slice of structs in ascending order using unstable sort. 183 | func StructAsc[T any](data []T, less func(a, b T) bool) { 184 | structSortUnstable(data, less) 185 | } 186 | 187 | // StructDesc sorts a slice of structs in descending order using unstable sort. 188 | func StructDesc[T any](data []T, less func(a, b T) bool) { 189 | structSortUnstable(data, func(a, b T) bool { 190 | return less(b, a) 191 | }) 192 | } 193 | 194 | // StructAscStable sorts a slice of structs in ascending order using stable sort. 195 | func StructAscStable[T any](data []T, less func(a, b T) bool) { 196 | structSortStable(data, less) 197 | } 198 | 199 | // StructDescStable sorts a slice of structs in descending order using stable sort. 200 | func StructDescStable[T any](data []T, less func(a, b T) bool) { 201 | structSortStable(data, func(a, b T) bool { 202 | return less(b, a) 203 | }) 204 | } 205 | -------------------------------------------------------------------------------- /struct_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func TestStructAsc(t *testing.T) { 9 | data := genPeople(50000) 10 | StructAsc(data, func(a, b person) bool { return a.Age < b.Age }) 11 | if !isSortedAsc(data) { 12 | t.Errorf("StructAsc failed to sort correctly") 13 | } 14 | } 15 | 16 | func TestStructDesc(t *testing.T) { 17 | data := genPeople(50000) 18 | StructDesc(data, func(a, b person) bool { return a.Age < b.Age }) 19 | if !isSortedDesc(data) { 20 | t.Errorf("StructDesc failed to sort correctly") 21 | } 22 | } 23 | 24 | func TestStructAsc_Small(t *testing.T) { 25 | data := genPeople(5) 26 | StructAsc(data, func(a, b person) bool { return a.Age < b.Age }) 27 | if !isSortedAsc(data) { 28 | t.Errorf("Small slice sort (Asc) failed") 29 | } 30 | } 31 | 32 | func TestStructDesc_Small(t *testing.T) { 33 | data := genPeople(5) 34 | StructDesc(data, func(a, b person) bool { return a.Age < b.Age }) 35 | if !isSortedDesc(data) { 36 | t.Errorf("Small slice sort (Desc) failed") 37 | } 38 | } 39 | 40 | func TestStructAsc_Empty(t *testing.T) { 41 | data := genPeople(0) 42 | StructAsc(data, func(a, b person) bool { return a.Age < b.Age }) 43 | if len(data) != 0 { 44 | t.Errorf("Expected empty slice, got: %v", data) 45 | } 46 | } 47 | 48 | func TestStructAsc_Single(t *testing.T) { 49 | data := genPeople(1) 50 | expected := data[0] 51 | StructAsc(data, func(a, b person) bool { return a.Age < b.Age }) 52 | if data[0] != expected { 53 | t.Errorf("Single element slice changed unexpectedly") 54 | } 55 | } 56 | 57 | func TestStructAsc_AlreadySorted(t *testing.T) { 58 | data := []person{{"A", 1}, {"B", 2}, {"C", 3}} 59 | StructAsc(data, func(a, b person) bool { return a.Age < b.Age }) 60 | if !isSortedAsc(data) { 61 | t.Errorf("Already sorted slice failed Asc sort") 62 | } 63 | } 64 | 65 | func TestStructAsc_ReversedInput(t *testing.T) { 66 | data := []person{{"C", 3}, {"B", 2}, {"A", 1}} 67 | StructAsc(data, func(a, b person) bool { return a.Age < b.Age }) 68 | if !isSortedAsc(data) { 69 | t.Errorf("Reversed input failed to sort Asc") 70 | } 71 | } 72 | 73 | func TestStructAsc_AllEqual(t *testing.T) { 74 | data := []person{{"A", 5}, {"B", 5}, {"C", 5}} 75 | StructAsc(data, func(a, b person) bool { return a.Age < b.Age }) 76 | if !isSortedAsc(data) { 77 | t.Errorf("Equal elements failed to remain in order") 78 | } 79 | } 80 | 81 | func BenchmarkSortStruct_Arbitrary(b *testing.B) { 82 | for _, size := range testSizes { 83 | b.Run("Arbitrary_SortStruct_"+strconv.Itoa(size), func(b *testing.B) { 84 | original := genPeople(size) 85 | b.ReportAllocs() 86 | b.ResetTimer() 87 | for i := 0; i < b.N; i++ { 88 | tmp := make([]person, len(original)) 89 | copy(tmp, original) 90 | structSortUnstable(tmp, func(a, b person) bool { 91 | return a.Age < b.Age 92 | }) 93 | } 94 | }) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /time.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | func TimeAsc(data []time.Time) { 10 | timeSort(data, false) 11 | } 12 | 13 | func TimeDesc(data []time.Time) { 14 | timeSort(data, true) 15 | } 16 | 17 | func timeSort(data []time.Time, reverse bool) { 18 | n := len(data) 19 | if n < TimeMinParallelSize { 20 | sort.Slice(data, func(i, j int) bool { 21 | return data[i].Before(data[j]) 22 | }) 23 | if reverse { 24 | timeReverse(data) 25 | } 26 | return 27 | } 28 | 29 | chunkSize := (n + CoreCount - 1) / CoreCount 30 | 31 | chunks := make([][]time.Time, 0, CoreCount) 32 | for i := 0; i < n; i += chunkSize { 33 | end := i + chunkSize 34 | if end > n { 35 | end = n 36 | } 37 | chunks = append(chunks, data[i:end]) 38 | } 39 | 40 | var wg sync.WaitGroup 41 | for _, chunk := range chunks { 42 | wg.Add(1) 43 | go func(c []time.Time) { 44 | defer wg.Done() 45 | sort.Slice(c, func(i, j int) bool { 46 | return c[i].Before(c[j]) 47 | }) 48 | }(chunk) 49 | } 50 | wg.Wait() 51 | 52 | for len(chunks) > 1 { 53 | mergedCount := (len(chunks) + 1) / 2 54 | merged := make([][]time.Time, mergedCount) 55 | 56 | var mWg sync.WaitGroup 57 | for i := 0; i < len(chunks); i += 2 { 58 | if i+1 == len(chunks) { 59 | merged[i/2] = chunks[i] 60 | continue 61 | } 62 | mWg.Add(1) 63 | go func(i int) { 64 | defer mWg.Done() 65 | merged[i/2] = timeMergeSorted(chunks[i], chunks[i+1]) 66 | }(i) 67 | } 68 | mWg.Wait() 69 | chunks = merged 70 | } 71 | 72 | copy(data, chunks[0]) 73 | if reverse { 74 | timeReverse(data) 75 | } 76 | } 77 | 78 | func timeMergeSorted(a, b []time.Time) []time.Time { 79 | res := make([]time.Time, len(a)+len(b)) 80 | i, j, k := 0, 0, 0 81 | for i < len(a) && j < len(b) { 82 | if a[i].Before(b[j]) || a[i].Equal(b[j]) { 83 | res[k] = a[i] 84 | i++ 85 | } else { 86 | res[k] = b[j] 87 | j++ 88 | } 89 | k++ 90 | } 91 | for i < len(a) { 92 | res[k] = a[i] 93 | i++ 94 | k++ 95 | } 96 | for j < len(b) { 97 | res[k] = b[j] 98 | j++ 99 | k++ 100 | } 101 | return res 102 | } 103 | 104 | func timeReverse(a []time.Time) { 105 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 106 | a[i], a[j] = a[j], a[i] 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /time_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestTimeAsc_EmptySlice(t *testing.T) { 11 | var data []time.Time 12 | TimeAsc(data) 13 | if len(data) != 0 { 14 | t.Errorf("expected empty slice, got %v", data) 15 | } 16 | } 17 | 18 | func TestTimeDesc_EmptySlice(t *testing.T) { 19 | var data []time.Time 20 | TimeDesc(data) 21 | if len(data) != 0 { 22 | t.Errorf("expected empty slice, got %v", data) 23 | } 24 | } 25 | 26 | func TestTimeAsc_SingleElement(t *testing.T) { 27 | now := time.Now() 28 | data := []time.Time{now} 29 | TimeAsc(data) 30 | if !data[0].Equal(now) { 31 | t.Errorf("expected [%v], got %v", now, data) 32 | } 33 | } 34 | 35 | func TestTimeDesc_SingleElement(t *testing.T) { 36 | now := time.Now() 37 | data := []time.Time{now} 38 | TimeDesc(data) 39 | if !data[0].Equal(now) { 40 | t.Errorf("expected [%v], got %v", now, data) 41 | } 42 | } 43 | 44 | func TestTimeAsc_AllEqual(t *testing.T) { 45 | t0 := time.Unix(1000000, 0) 46 | data := make([]time.Time, 1000) 47 | for i := range data { 48 | data[i] = t0 49 | } 50 | TimeAsc(data) 51 | for _, v := range data { 52 | if !v.Equal(t0) { 53 | t.Errorf("expected all elements to be %v, got %v", t0, data) 54 | break 55 | } 56 | } 57 | } 58 | 59 | func TestTimeDesc_AllEqual(t *testing.T) { 60 | t0 := time.Unix(1000000, 0) 61 | data := make([]time.Time, 1000) 62 | for i := range data { 63 | data[i] = t0 64 | } 65 | TimeDesc(data) 66 | for _, v := range data { 67 | if !v.Equal(t0) { 68 | t.Errorf("expected all elements to be %v, got %v", t0, data) 69 | break 70 | } 71 | } 72 | } 73 | 74 | func TestTimeAsc_AlreadySorted(t *testing.T) { 75 | base := time.Now() 76 | data := make([]time.Time, 10000) 77 | for i := range data { 78 | data[i] = base.Add(time.Duration(i) * time.Second) 79 | } 80 | TimeAsc(data) 81 | for i := 1; i < len(data); i++ { 82 | if data[i].Before(data[i-1]) { 83 | t.Errorf("expected already sorted slice to remain unchanged") 84 | break 85 | } 86 | } 87 | } 88 | 89 | func TestTimeDesc_AlreadySorted(t *testing.T) { 90 | base := time.Now() 91 | n := 10000 92 | data := make([]time.Time, n) 93 | for i := range data { 94 | data[i] = base.Add(time.Duration(n-i) * time.Second) 95 | } 96 | TimeDesc(data) 97 | for i := 1; i < len(data); i++ { 98 | if data[i].After(data[i-1]) { 99 | t.Errorf("expected descending slice to remain unchanged") 100 | break 101 | } 102 | } 103 | } 104 | 105 | func TestTimeAsc_ReverseSorted(t *testing.T) { 106 | base := time.Now() 107 | n := 10000 108 | data := make([]time.Time, n) 109 | for i := range data { 110 | data[i] = base.Add(time.Duration(n-i) * time.Second) 111 | } 112 | TimeAsc(data) 113 | for i := 1; i < len(data); i++ { 114 | if data[i].Before(data[i-1]) { 115 | t.Errorf("slice not sorted in ascending order") 116 | break 117 | } 118 | } 119 | } 120 | 121 | func TestTimeDesc_ReverseSorted(t *testing.T) { 122 | base := time.Now() 123 | n := 10000 124 | data := make([]time.Time, n) 125 | for i := range data { 126 | data[i] = base.Add(time.Duration(i) * time.Second) 127 | } 128 | TimeDesc(data) 129 | for i := 1; i < len(data); i++ { 130 | if data[i].After(data[i-1]) { 131 | t.Errorf("slice not sorted in descending order") 132 | break 133 | } 134 | } 135 | } 136 | 137 | func TestTimeAsc_SmallRandom(t *testing.T) { 138 | data := genTimes(1000) 139 | expected := append([]time.Time(nil), data...) 140 | sort.Slice(expected, func(i, j int) bool { 141 | return expected[i].Before(expected[j]) 142 | }) 143 | TimeAsc(data) 144 | if !timeSlicesEqual(data, expected) { 145 | t.Errorf("sorted result incorrect for small ascending slice") 146 | } 147 | } 148 | 149 | func TestTimeDesc_SmallRandom(t *testing.T) { 150 | data := genTimes(1000) 151 | expected := append([]time.Time(nil), data...) 152 | sort.Slice(expected, func(i, j int) bool { 153 | return expected[i].After(expected[j]) 154 | }) 155 | TimeDesc(data) 156 | if !timeSlicesEqual(data, expected) { 157 | t.Errorf("sorted result incorrect for small descending slice") 158 | } 159 | } 160 | 161 | func TestTimeAsc_LargeRandom(t *testing.T) { 162 | data := genTimes(2000000) 163 | expected := append([]time.Time(nil), data...) 164 | sort.Slice(expected, func(i, j int) bool { 165 | return expected[i].Before(expected[j]) 166 | }) 167 | TimeAsc(data) 168 | if !timeSlicesEqual(data, expected) { 169 | t.Errorf("sorted result incorrect for large ascending slice") 170 | } 171 | } 172 | 173 | func TestTimeDesc_LargeRandom(t *testing.T) { 174 | data := genTimes(2000000) 175 | expected := append([]time.Time(nil), data...) 176 | sort.Slice(expected, func(i, j int) bool { 177 | return expected[i].After(expected[j]) 178 | }) 179 | TimeDesc(data) 180 | if !timeSlicesEqual(data, expected) { 181 | t.Errorf("sorted result incorrect for large descending slice") 182 | } 183 | } 184 | 185 | func BenchmarkParsortTimeAsc(b *testing.B) { 186 | for _, size := range testSizes { 187 | b.Run("Parsort_Sort_Time_"+strconv.Itoa(size), func(b *testing.B) { 188 | data := genTimes(size) 189 | b.ReportAllocs() 190 | b.ResetTimer() 191 | for i := 0; i < b.N; i++ { 192 | tmp := make([]time.Time, len(data)) 193 | copy(tmp, data) 194 | TimeAsc(tmp) 195 | } 196 | }) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tuner.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "sort" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | // TuneSpecific benchmarks sequential vs. concurrent sorting for increasing data sizes, 12 | // starting from `startSize` and increasing by `increment` each iteration. 13 | // For each case, it runs the benchmark `runs` times. 14 | // The loop continues indefinitely, but is intended to be stopped by logic inside 15 | // the callback (e.g., once a performance delta threshold is met). 16 | // 17 | // Parameters: 18 | // - runs: Number of repetitions per case for averaging 19 | // - startSize: Initial slice size to test 20 | // - increment: Amount to increase slice size per iteration 21 | // - deltaThreshold: Intended threshold for delta evaluation (used externally in callback) 22 | // - showOutput: Prints the bench results 23 | // 24 | // Example use case: 25 | // - Automatically find the minimum slice size where parallel sorting becomes faster. 26 | // - Tunes ParallelSize variables for each type based on real CPU performance. 27 | func TuneSpecific(runs int, startSize int, increment int, deltaThreshold float64, showOutput bool) { 28 | tuner := NewTuner().SetRuns(runs) 29 | 30 | IntMinParallelSize = 0 31 | size := startSize 32 | stop := false 33 | for !stop { 34 | label := strconv.Itoa(size) 35 | sampleData := genInts(size) 36 | 37 | tuner.AddCase(label, 38 | func() { 39 | data := make([]int, len(sampleData)) 40 | copy(data, sampleData) 41 | sort.Ints(data) 42 | }, 43 | func() { 44 | data := make([]int, len(sampleData)) 45 | copy(data, sampleData) 46 | IntAsc(data) 47 | }, 48 | func(r BenchResult) { 49 | if r.DeltaNsPct < deltaThreshold { 50 | if showOutput { 51 | fmt.Println("Int") 52 | tuner.PrintResult() 53 | } 54 | IntMinParallelSize = size 55 | stop = true 56 | } 57 | }, 58 | ) 59 | 60 | tuner.Run().Reset() 61 | size += increment 62 | } 63 | 64 | Int8MinParallelSize = 0 65 | size = startSize 66 | stop = false 67 | for !stop { 68 | label := strconv.Itoa(size) 69 | sampleData := genInt8s(size) 70 | 71 | tuner.AddCase(label, 72 | func() { 73 | data := make([]int8, len(sampleData)) 74 | copy(data, sampleData) 75 | sort.Slice(data, func(i, j int) bool { 76 | return data[i] < data[j] 77 | }) 78 | }, 79 | func() { 80 | data := make([]int8, len(sampleData)) 81 | copy(data, sampleData) 82 | Int8Asc(data) 83 | }, 84 | func(r BenchResult) { 85 | if r.DeltaNsPct < deltaThreshold { 86 | if showOutput { 87 | fmt.Println("Int8") 88 | tuner.PrintResult() 89 | } 90 | Int8MinParallelSize = size 91 | stop = true 92 | } 93 | }, 94 | ) 95 | 96 | tuner.Run().Reset() 97 | size += increment 98 | } 99 | 100 | Int16MinParallelSize = 0 101 | size = startSize 102 | stop = false 103 | for !stop { 104 | label := strconv.Itoa(size) 105 | sampleData := genInt16s(size) 106 | 107 | tuner.AddCase(label, 108 | func() { 109 | data := make([]int16, len(sampleData)) 110 | copy(data, sampleData) 111 | sort.Slice(data, func(i, j int) bool { 112 | return data[i] < data[j] 113 | }) 114 | }, 115 | func() { 116 | data := make([]int16, len(sampleData)) 117 | copy(data, sampleData) 118 | Int16Asc(data) 119 | }, 120 | func(r BenchResult) { 121 | if r.DeltaNsPct < deltaThreshold { 122 | if showOutput { 123 | fmt.Println("Int16") 124 | tuner.PrintResult() 125 | } 126 | Int16MinParallelSize = size 127 | stop = true 128 | } 129 | }, 130 | ) 131 | 132 | tuner.Run().Reset() 133 | size += increment 134 | } 135 | 136 | Int32MinParallelSize = 0 137 | size = startSize 138 | stop = false 139 | for !stop { 140 | label := strconv.Itoa(size) 141 | sampleData := genInt32s(size) 142 | 143 | tuner.AddCase(label, 144 | func() { 145 | data := make([]int32, len(sampleData)) 146 | copy(data, sampleData) 147 | sort.Slice(data, func(i, j int) bool { 148 | return data[i] < data[j] 149 | }) 150 | }, 151 | func() { 152 | data := make([]int32, len(sampleData)) 153 | copy(data, sampleData) 154 | Int32Asc(data) 155 | }, 156 | func(r BenchResult) { 157 | if r.DeltaNsPct < deltaThreshold { 158 | if showOutput { 159 | fmt.Println("Int32") 160 | tuner.PrintResult() 161 | } 162 | Int32MinParallelSize = size 163 | stop = true 164 | } 165 | }, 166 | ) 167 | 168 | tuner.Run().Reset() 169 | size += increment 170 | } 171 | 172 | Int64MinParallelSize = 0 173 | size = startSize 174 | stop = false 175 | for !stop { 176 | label := strconv.Itoa(size) 177 | sampleData := genInt64s(size) 178 | 179 | tuner.AddCase(label, 180 | func() { 181 | data := make([]int64, len(sampleData)) 182 | copy(data, sampleData) 183 | sort.Slice(data, func(i, j int) bool { 184 | return data[i] < data[j] 185 | }) 186 | }, 187 | func() { 188 | data := make([]int64, len(sampleData)) 189 | copy(data, sampleData) 190 | Int64Asc(data) 191 | }, 192 | func(r BenchResult) { 193 | if r.DeltaNsPct < deltaThreshold { 194 | if showOutput { 195 | fmt.Println("Int64") 196 | tuner.PrintResult() 197 | } 198 | Int64MinParallelSize = size 199 | stop = true 200 | } 201 | }, 202 | ) 203 | 204 | tuner.Run().Reset() 205 | size += increment 206 | } 207 | 208 | UintMinParallelSize = 0 209 | size = startSize 210 | stop = false 211 | for !stop { 212 | label := strconv.Itoa(size) 213 | sampleData := genUints(size) 214 | 215 | tuner.AddCase(label, 216 | func() { 217 | data := make([]uint, len(sampleData)) 218 | copy(data, sampleData) 219 | sort.Slice(data, func(i, j int) bool { 220 | return data[i] < data[j] 221 | }) 222 | }, 223 | func() { 224 | data := make([]uint, len(sampleData)) 225 | copy(data, sampleData) 226 | UintAsc(data) 227 | }, 228 | func(r BenchResult) { 229 | if r.DeltaNsPct < deltaThreshold { 230 | if showOutput { 231 | fmt.Println("Uint") 232 | tuner.PrintResult() 233 | } 234 | UintMinParallelSize = size 235 | stop = true 236 | } 237 | }, 238 | ) 239 | 240 | tuner.Run().Reset() 241 | size += increment 242 | } 243 | 244 | Uint8MinParallelSize = 0 245 | size = startSize 246 | stop = false 247 | for !stop { 248 | label := strconv.Itoa(size) 249 | sampleData := genUint8s(size) 250 | 251 | tuner.AddCase(label, 252 | func() { 253 | data := make([]uint8, len(sampleData)) 254 | copy(data, sampleData) 255 | sort.Slice(data, func(i, j int) bool { 256 | return data[i] < data[j] 257 | }) 258 | }, 259 | func() { 260 | data := make([]uint8, len(sampleData)) 261 | copy(data, sampleData) 262 | Uint8Asc(data) 263 | }, 264 | func(r BenchResult) { 265 | if r.DeltaNsPct < deltaThreshold { 266 | if showOutput { 267 | fmt.Println("Uint8") 268 | tuner.PrintResult() 269 | } 270 | Uint8MinParallelSize = size 271 | stop = true 272 | } 273 | }, 274 | ) 275 | 276 | tuner.Run().Reset() 277 | size += increment 278 | } 279 | 280 | Uint16MinParallelSize = 0 281 | size = startSize 282 | stop = false 283 | for !stop { 284 | label := strconv.Itoa(size) 285 | sampleData := genUint16s(size) 286 | 287 | tuner.AddCase(label, 288 | func() { 289 | data := make([]uint16, len(sampleData)) 290 | copy(data, sampleData) 291 | sort.Slice(data, func(i, j int) bool { 292 | return data[i] < data[j] 293 | }) 294 | }, 295 | func() { 296 | data := make([]uint16, len(sampleData)) 297 | copy(data, sampleData) 298 | Uint16Asc(data) 299 | }, 300 | func(r BenchResult) { 301 | if r.DeltaNsPct < deltaThreshold { 302 | if showOutput { 303 | fmt.Println("Uint16") 304 | tuner.PrintResult() 305 | } 306 | Uint16MinParallelSize = size 307 | stop = true 308 | } 309 | }, 310 | ) 311 | 312 | tuner.Run().Reset() 313 | size += increment 314 | } 315 | 316 | Uint32MinParallelSize = 0 317 | size = startSize 318 | stop = false 319 | for !stop { 320 | label := strconv.Itoa(size) 321 | sampleData := genUint32s(size) 322 | 323 | tuner.AddCase(label, 324 | func() { 325 | data := make([]uint32, len(sampleData)) 326 | copy(data, sampleData) 327 | sort.Slice(data, func(i, j int) bool { 328 | return data[i] < data[j] 329 | }) 330 | }, 331 | func() { 332 | data := make([]uint32, len(sampleData)) 333 | copy(data, sampleData) 334 | Uint32Asc(data) 335 | }, 336 | func(r BenchResult) { 337 | if r.DeltaNsPct < deltaThreshold { 338 | if showOutput { 339 | fmt.Println("Uint32") 340 | tuner.PrintResult() 341 | } 342 | Uint32MinParallelSize = size 343 | stop = true 344 | } 345 | }, 346 | ) 347 | 348 | tuner.Run().Reset() 349 | size += increment 350 | } 351 | 352 | Uint64MinParallelSize = 0 353 | size = startSize 354 | stop = false 355 | for !stop { 356 | label := strconv.Itoa(size) 357 | sampleData := genUint64s(size) 358 | 359 | tuner.AddCase(label, 360 | func() { 361 | data := make([]uint64, len(sampleData)) 362 | copy(data, sampleData) 363 | sort.Slice(data, func(i, j int) bool { 364 | return data[i] < data[j] 365 | }) 366 | }, 367 | func() { 368 | data := make([]uint64, len(sampleData)) 369 | copy(data, sampleData) 370 | Uint64Asc(data) 371 | }, 372 | func(r BenchResult) { 373 | if r.DeltaNsPct < deltaThreshold { 374 | if showOutput { 375 | fmt.Println("Uint64") 376 | tuner.PrintResult() 377 | } 378 | Uint64MinParallelSize = size 379 | stop = true 380 | } 381 | }, 382 | ) 383 | 384 | tuner.Run().Reset() 385 | size += increment 386 | } 387 | 388 | Float32MinParallelSize = 0 389 | size = startSize 390 | stop = false 391 | for !stop { 392 | label := strconv.Itoa(size) 393 | sampleData := genFloat32s(size) 394 | 395 | tuner.AddCase(label, 396 | func() { 397 | data := make([]float32, len(sampleData)) 398 | copy(data, sampleData) 399 | sort.Slice(data, func(i, j int) bool { 400 | return data[i] < data[j] 401 | }) 402 | }, 403 | func() { 404 | data := make([]float32, len(sampleData)) 405 | copy(data, sampleData) 406 | Float32Asc(data) 407 | }, 408 | func(r BenchResult) { 409 | if r.DeltaNsPct < deltaThreshold { 410 | if showOutput { 411 | fmt.Println("Float32") 412 | tuner.PrintResult() 413 | } 414 | Float32MinParallelSize = size 415 | stop = true 416 | } 417 | }, 418 | ) 419 | 420 | tuner.Run().Reset() 421 | size += increment 422 | } 423 | 424 | Float64MinParallelSize = 0 425 | size = startSize 426 | stop = false 427 | for !stop { 428 | label := strconv.Itoa(size) 429 | sampleData := genFloats(size) 430 | 431 | tuner.AddCase(label, 432 | func() { 433 | data := make([]float64, len(sampleData)) 434 | copy(data, sampleData) 435 | sort.Float64s(data) 436 | }, 437 | func() { 438 | data := make([]float64, len(sampleData)) 439 | copy(data, sampleData) 440 | Float64Asc(data) 441 | }, 442 | func(r BenchResult) { 443 | if r.DeltaNsPct < deltaThreshold { 444 | if showOutput { 445 | fmt.Println("Float64") 446 | tuner.PrintResult() 447 | } 448 | Float64MinParallelSize = size 449 | stop = true 450 | } 451 | }, 452 | ) 453 | 454 | tuner.Run().Reset() 455 | size += increment 456 | } 457 | 458 | StringMinParallelSize = 0 459 | size = startSize 460 | stop = false 461 | for !stop { 462 | label := strconv.Itoa(size) 463 | sampleData := genStrings(size) 464 | 465 | tuner.AddCase(label, 466 | func() { 467 | data := make([]string, len(sampleData)) 468 | copy(data, sampleData) 469 | sort.Strings(data) 470 | }, 471 | func() { 472 | data := make([]string, len(sampleData)) 473 | copy(data, sampleData) 474 | StringAsc(data) 475 | }, 476 | func(r BenchResult) { 477 | if r.DeltaNsPct < deltaThreshold { 478 | if showOutput { 479 | fmt.Println("String") 480 | tuner.PrintResult() 481 | } 482 | StringMinParallelSize = size 483 | stop = true 484 | } 485 | }, 486 | ) 487 | 488 | tuner.Run().Reset() 489 | size += increment 490 | } 491 | 492 | TimeMinParallelSize = 0 493 | size = startSize 494 | stop = false 495 | for !stop { 496 | label := strconv.Itoa(size) 497 | sampleData := genTimes(size) 498 | 499 | tuner.AddCase(label, 500 | func() { 501 | data := make([]time.Time, len(sampleData)) 502 | copy(data, sampleData) 503 | sort.Slice(data, func(i, j int) bool { 504 | return data[i].Before(data[j]) 505 | }) 506 | }, 507 | func() { 508 | data := make([]time.Time, len(sampleData)) 509 | copy(data, sampleData) 510 | TimeAsc(data) 511 | }, 512 | func(r BenchResult) { 513 | if r.DeltaNsPct < deltaThreshold { 514 | if showOutput { 515 | fmt.Println("Time") 516 | tuner.PrintResult() 517 | } 518 | TimeMinParallelSize = size 519 | stop = true 520 | } 521 | }, 522 | ) 523 | 524 | tuner.Run().Reset() 525 | size += increment 526 | } 527 | 528 | StructMinParallelSize = 0 529 | size = startSize 530 | stop = false 531 | for !stop { 532 | label := strconv.Itoa(size) 533 | sampleData := genPeople(size) 534 | 535 | tuner.AddCase(label, 536 | func() { 537 | data := make([]person, len(sampleData)) 538 | copy(data, sampleData) 539 | sort.Sort(byAge(data)) 540 | }, 541 | func() { 542 | data := make([]person, len(sampleData)) 543 | copy(data, sampleData) 544 | StructAsc(data, func(a, b person) bool { return a.Age < b.Age }) 545 | }, 546 | func(r BenchResult) { 547 | if r.DeltaNsPct < deltaThreshold { 548 | if showOutput { 549 | fmt.Println("Struct") 550 | tuner.PrintResult() 551 | } 552 | StructMinParallelSize = size 553 | stop = true 554 | } 555 | }, 556 | ) 557 | 558 | tuner.Run().Reset() 559 | size += increment 560 | } 561 | } 562 | 563 | func Tune() { 564 | TuneSpecific(100, 1000, 1000, -10, false) 565 | } 566 | 567 | type Tuner struct { 568 | runsPerCase int 569 | cases []benchCase 570 | results []BenchResult 571 | } 572 | 573 | type BenchResult struct { 574 | Label string 575 | SeqTimeNs int64 576 | ConTimeNs int64 577 | SeqBytes int64 578 | ConBytes int64 579 | DeltaNsPct float64 580 | DeltaBPct float64 581 | } 582 | 583 | type benchCase struct { 584 | Label string 585 | FuncSequential func() 586 | FuncConcurrent func() 587 | Callback func(BenchResult) 588 | } 589 | 590 | func NewTuner() *Tuner { 591 | return &Tuner{ 592 | runsPerCase: 5, 593 | } 594 | } 595 | 596 | func (x *Tuner) SetRuns(n int) *Tuner { 597 | x.runsPerCase = n 598 | return x 599 | } 600 | 601 | func (x *Tuner) AddCase(label string, seq, con func(), cb func(BenchResult)) *Tuner { 602 | x.cases = append(x.cases, benchCase{ 603 | Label: label, 604 | FuncSequential: seq, 605 | FuncConcurrent: con, 606 | Callback: cb, 607 | }) 608 | return x 609 | } 610 | 611 | func (x *Tuner) Reset() { 612 | x.cases = []benchCase{} 613 | x.results = []BenchResult{} 614 | } 615 | 616 | func (x *Tuner) Run() *Tuner { 617 | for _, c := range x.cases { 618 | var seqTotalNs, conTotalNs, seqTotalB, conTotalB int64 619 | 620 | for i := 0; i < x.runsPerCase; i++ { 621 | seqAlloc := x.measureAlloc(c.FuncSequential) 622 | seqTotalNs += seqAlloc.timeNs 623 | seqTotalB += seqAlloc.bytes 624 | 625 | conAlloc := x.measureAlloc(c.FuncConcurrent) 626 | conTotalNs += conAlloc.timeNs 627 | conTotalB += conAlloc.bytes 628 | } 629 | 630 | seqAvgNs := seqTotalNs / int64(x.runsPerCase) 631 | conAvgNs := conTotalNs / int64(x.runsPerCase) 632 | seqAvgB := seqTotalB / int64(x.runsPerCase) 633 | conAvgB := conTotalB / int64(x.runsPerCase) 634 | 635 | deltaNsPct := float64(conAvgNs-seqAvgNs) / float64(seqAvgNs) * 100 636 | deltaBPct := float64(conAvgB-seqAvgB) / float64(seqAvgB) * 100 637 | 638 | result := BenchResult{ 639 | Label: c.Label, 640 | SeqTimeNs: seqAvgNs, 641 | ConTimeNs: conAvgNs, 642 | SeqBytes: seqAvgB, 643 | ConBytes: conAvgB, 644 | DeltaNsPct: deltaNsPct, 645 | DeltaBPct: deltaBPct, 646 | } 647 | x.results = append(x.results, result) 648 | if c.Callback != nil { 649 | c.Callback(result) 650 | } 651 | } 652 | return x 653 | } 654 | 655 | type allocResult struct { 656 | timeNs int64 657 | bytes int64 658 | } 659 | 660 | func (x *Tuner) measureAlloc(fn func()) allocResult { 661 | var memBefore, memAfter runtime.MemStats 662 | runtime.GC() 663 | runtime.ReadMemStats(&memBefore) 664 | start := time.Now() 665 | fn() 666 | dur := time.Since(start).Nanoseconds() 667 | runtime.ReadMemStats(&memAfter) 668 | return allocResult{ 669 | timeNs: dur, 670 | bytes: int64(memAfter.TotalAlloc - memBefore.TotalAlloc), 671 | } 672 | } 673 | 674 | func (x *Tuner) PrintResult() *Tuner { 675 | titles := []string{"Label", "Sequential ns/op", "Concurrent ns/op", "Sequential B/op", "Concurrent B/op", "Delta ns/op", "Delta B/op"} 676 | maxLabel := len(titles[0]) 677 | for _, r := range x.results { 678 | maxLabel = x.max(maxLabel, len(r.Label)) 679 | } 680 | maxNs := len(titles[1]) 681 | maxB := len(titles[3]) 682 | for _, r := range x.results { 683 | maxNs = x.max(maxNs, len(x.HumanNs(r.SeqTimeNs))) 684 | maxNs = x.max(maxNs, len(x.HumanNs(r.ConTimeNs))) 685 | maxB = x.max(maxB, len(x.HumanBytes(r.SeqBytes))) 686 | maxB = x.max(maxB, len(x.HumanBytes(r.ConBytes))) 687 | } 688 | 689 | fmt.Printf("| %-*s | %s | %s | %s | %s | %s | %s |\n", 690 | maxLabel, titles[0], 691 | x.padCenter(titles[1], maxNs), 692 | x.padCenter(titles[2], maxNs), 693 | x.padCenter(titles[3], maxB), 694 | x.padCenter(titles[4], maxB), 695 | x.padCenter(titles[5], 12), 696 | x.padCenter(titles[6], 11)) 697 | fmt.Printf("|-%s-|-%s-|-%s-|-%s-|-%s-|-%s-|-%s-|\n", 698 | x.repeat("-", maxLabel), x.repeat("-", maxNs), x.repeat("-", maxNs), x.repeat("-", maxB), x.repeat("-", maxB), x.repeat("-", 12), x.repeat("-", 11)) 699 | 700 | for _, r := range x.results { 701 | deltaNs := fmt.Sprintf("%+.2f%%", r.DeltaNsPct) 702 | deltaB := fmt.Sprintf("%+.2f%%", r.DeltaBPct) 703 | fmt.Printf("| %-*s | %*s | %*s | %*s | %*s | %12s | %11s |\n", 704 | maxLabel, r.Label, 705 | maxNs, x.HumanNs(r.SeqTimeNs), 706 | maxNs, x.HumanNs(r.ConTimeNs), 707 | maxB, x.HumanBytes(r.SeqBytes), 708 | maxB, x.HumanBytes(r.ConBytes), 709 | deltaNs, 710 | deltaB) 711 | } 712 | fmt.Println() 713 | return x 714 | } 715 | 716 | func (x *Tuner) HumanNs(ns int64) string { 717 | out := "" 718 | if ns >= 3600000000000 { 719 | out += fmt.Sprintf("%dh ", ns/3600000000000) 720 | ns = ns % 3600000000000 721 | } 722 | if ns >= 60000000000 { 723 | out += fmt.Sprintf("%dm ", ns/60000000000) 724 | ns = ns % 60000000000 725 | } 726 | if ns >= 1000000000 { 727 | out += fmt.Sprintf("%ds ", ns/1000000000) 728 | ns = ns % 1000000000 729 | } 730 | if ns >= 1000000 { 731 | out += fmt.Sprintf("%dms ", ns/1000000) 732 | ns = ns % 1000000 733 | } 734 | if ns >= 1000 { 735 | out += fmt.Sprintf("%dµs ", ns/1000) 736 | ns = ns % 1000 737 | } 738 | if ns > 0 || out == "" { 739 | out += fmt.Sprintf("%dns", ns) 740 | } 741 | return out 742 | } 743 | 744 | func (x *Tuner) HumanBytes(b int64) string { 745 | out := "" 746 | if b >= 1073741824 { 747 | out += fmt.Sprintf("%dGB ", b/1073741824) 748 | b = b % 1073741824 749 | } 750 | if b >= 1048576 { 751 | out += fmt.Sprintf("%dMB ", b/1048576) 752 | b = b % 1048576 753 | } 754 | if b >= 1024 { 755 | out += fmt.Sprintf("%dKB ", b/1024) 756 | b = b % 1024 757 | } 758 | if b > 0 || out == "" { 759 | out += fmt.Sprintf("%dB", b) 760 | } 761 | return out 762 | } 763 | 764 | func (x *Tuner) repeat(s string, count int) string { 765 | res := "" 766 | for i := 0; i < count; i++ { 767 | res += s 768 | } 769 | return res 770 | } 771 | 772 | func (x *Tuner) padCenter(s string, width int) string { 773 | pad := width - len(s) 774 | if pad <= 0 { 775 | return s 776 | } 777 | left := pad / 2 778 | right := pad - left 779 | return x.repeat(" ", left) + s + x.repeat(" ", right) 780 | } 781 | 782 | func (x *Tuner) max(a, b int) int { 783 | if a > b { 784 | return a 785 | } 786 | return b 787 | } 788 | -------------------------------------------------------------------------------- /tuner_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTune(t *testing.T) { 8 | t.Skip() 9 | 10 | TuneSpecific(100, 1000, 1000, -10, true) 11 | } 12 | -------------------------------------------------------------------------------- /uint.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func UintAsc(data []uint) { 9 | uintSort(data, false) 10 | } 11 | 12 | func UintDesc(data []uint) { 13 | uintSort(data, true) 14 | } 15 | 16 | func uintSort(data []uint, reverse bool) { 17 | n := len(data) 18 | if n < UintMinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | uintReverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]uint, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []uint) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]uint, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = uintMergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | uintReverse(data) 74 | } 75 | } 76 | 77 | func uintMergeSorted(a, b []uint) []uint { 78 | res := make([]uint, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func uintReverse(a []uint) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /uint16.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Uint16Asc(data []uint16) { 9 | uint16Sort(data, false) 10 | } 11 | 12 | func Uint16Desc(data []uint16) { 13 | uint16Sort(data, true) 14 | } 15 | 16 | func uint16Sort(data []uint16, reverse bool) { 17 | n := len(data) 18 | if n < Uint16MinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | uint16Reverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]uint16, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []uint16) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]uint16, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = uint16MergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | uint16Reverse(data) 74 | } 75 | } 76 | 77 | func uint16MergeSorted(a, b []uint16) []uint16 { 78 | res := make([]uint16, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func uint16Reverse(a []uint16) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /uint16_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestUint16Asc_EmptySlice(t *testing.T) { 10 | var data []uint16 11 | Uint16Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestUint16Desc_EmptySlice(t *testing.T) { 18 | var data []uint16 19 | Uint16Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestUint16Asc_SingleElement(t *testing.T) { 26 | data := []uint16{42} 27 | Uint16Asc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestUint16Desc_SingleElement(t *testing.T) { 34 | data := []uint16{42} 35 | Uint16Desc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestUint16Asc_AllEqual(t *testing.T) { 42 | data := make([]uint16, 1000) 43 | for i := range data { 44 | data[i] = 123 45 | } 46 | Uint16Asc(data) 47 | for _, v := range data { 48 | if v != 123 { 49 | t.Errorf("expected all elements to be 123, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestUint16Desc_AllEqual(t *testing.T) { 56 | data := make([]uint16, 1000) 57 | for i := range data { 58 | data[i] = 123 59 | } 60 | Uint16Desc(data) 61 | for _, v := range data { 62 | if v != 123 { 63 | t.Errorf("expected all elements to be 123, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestUint16Asc_AlreadySorted(t *testing.T) { 70 | data := make([]uint16, 10000) 71 | for i := range data { 72 | data[i] = uint16(i) 73 | } 74 | Uint16Asc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != uint16(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestUint16Desc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]uint16, n) 86 | for i := range data { 87 | data[i] = uint16(n - i - 1) 88 | } 89 | Uint16Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestUint16Asc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]uint16, n) 101 | for i := range data { 102 | data[i] = uint16(n - i) 103 | } 104 | Uint16Asc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestUint16Desc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]uint16, n) 116 | for i := range data { 117 | data[i] = uint16(i) 118 | } 119 | Uint16Desc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestUint16Asc_SmallRandom(t *testing.T) { 129 | data := genUint16s(1000) 130 | expected := append([]uint16(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | Uint16Asc(data) 133 | if !uint16SlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for small ascending slice") 135 | } 136 | } 137 | 138 | func TestUint16Desc_SmallRandom(t *testing.T) { 139 | data := genUint16s(1000) 140 | expected := append([]uint16(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | Uint16Desc(data) 143 | if !uint16SlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for small descending slice") 145 | } 146 | } 147 | 148 | func TestUint16Asc_LargeRandom(t *testing.T) { 149 | data := genUint16s(2000000) 150 | expected := append([]uint16(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | Uint16Asc(data) 153 | if !uint16SlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for large slice") 155 | } 156 | } 157 | 158 | func TestUint16Desc_LargeRandom(t *testing.T) { 159 | data := genUint16s(2000000) 160 | expected := append([]uint16(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | Uint16Desc(data) 163 | if !uint16SlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for large descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortUint16Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Uint16_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genUint16s(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]uint16, len(data)) 176 | copy(tmp, data) 177 | Uint16Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortUint16Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Uint16_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genUint16s(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]uint16, len(data)) 191 | copy(tmp, data) 192 | Uint16Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /uint32.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Uint32Asc(data []uint32) { 9 | uint32Sort(data, false) 10 | } 11 | 12 | func Uint32Desc(data []uint32) { 13 | uint32Sort(data, true) 14 | } 15 | 16 | func uint32Sort(data []uint32, reverse bool) { 17 | n := len(data) 18 | if n < Uint32MinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | uint32Reverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]uint32, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []uint32) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]uint32, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = uint32MergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | uint32Reverse(data) 74 | } 75 | } 76 | 77 | func uint32MergeSorted(a, b []uint32) []uint32 { 78 | res := make([]uint32, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func uint32Reverse(a []uint32) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /uint32_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestUint32Asc_EmptySlice(t *testing.T) { 10 | var data []uint32 11 | Uint32Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestUint32Desc_EmptySlice(t *testing.T) { 18 | var data []uint32 19 | Uint32Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestUint32Asc_SingleElement(t *testing.T) { 26 | data := []uint32{42} 27 | Uint32Asc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestUint32Desc_SingleElement(t *testing.T) { 34 | data := []uint32{42} 35 | Uint32Desc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestUint32Asc_AllEqual(t *testing.T) { 42 | data := make([]uint32, 1000) 43 | for i := range data { 44 | data[i] = 7 45 | } 46 | Uint32Asc(data) 47 | for _, v := range data { 48 | if v != 7 { 49 | t.Errorf("expected all elements to be 7, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestUint32Desc_AllEqual(t *testing.T) { 56 | data := make([]uint32, 1000) 57 | for i := range data { 58 | data[i] = 7 59 | } 60 | Uint32Desc(data) 61 | for _, v := range data { 62 | if v != 7 { 63 | t.Errorf("expected all elements to be 7, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestUint32Asc_AlreadySorted(t *testing.T) { 70 | data := make([]uint32, 10000) 71 | for i := range data { 72 | data[i] = uint32(i) 73 | } 74 | Uint32Asc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != uint32(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestUint32Desc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]uint32, n) 86 | for i := range data { 87 | data[i] = uint32(n - i - 1) 88 | } 89 | Uint32Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestUint32Asc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]uint32, n) 101 | for i := range data { 102 | data[i] = uint32(n - i) 103 | } 104 | Uint32Asc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestUint32Desc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]uint32, n) 116 | for i := range data { 117 | data[i] = uint32(i) 118 | } 119 | Uint32Desc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestUint32Asc_SmallRandom(t *testing.T) { 129 | data := genUint32s(1000) 130 | expected := append([]uint32(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | Uint32Asc(data) 133 | if !uint32SlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for small ascending slice") 135 | } 136 | } 137 | 138 | func TestUint32Desc_SmallRandom(t *testing.T) { 139 | data := genUint32s(1000) 140 | expected := append([]uint32(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | Uint32Desc(data) 143 | if !uint32SlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for small descending slice") 145 | } 146 | } 147 | 148 | func TestUint32Asc_LargeRandom(t *testing.T) { 149 | data := genUint32s(2000000) 150 | expected := append([]uint32(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | Uint32Asc(data) 153 | if !uint32SlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for large slice") 155 | } 156 | } 157 | 158 | func TestUint32Desc_LargeRandom(t *testing.T) { 159 | data := genUint32s(2000000) 160 | expected := append([]uint32(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | Uint32Desc(data) 163 | if !uint32SlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for large descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortUint32Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Uint32_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genUint32s(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]uint32, len(data)) 176 | copy(tmp, data) 177 | Uint32Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortUint32Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Uint32_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genUint32s(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]uint32, len(data)) 191 | copy(tmp, data) 192 | Uint32Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /uint64.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Uint64Asc(data []uint64) { 9 | uint64Sort(data, false) 10 | } 11 | 12 | func Uint64Desc(data []uint64) { 13 | uint64Sort(data, true) 14 | } 15 | 16 | func uint64Sort(data []uint64, reverse bool) { 17 | n := len(data) 18 | if n < Uint64MinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | uint64Reverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]uint64, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []uint64) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]uint64, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = uint64MergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | uint64Reverse(data) 74 | } 75 | } 76 | 77 | func uint64MergeSorted(a, b []uint64) []uint64 { 78 | res := make([]uint64, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func uint64Reverse(a []uint64) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /uint64_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestUint64Asc_EmptySlice(t *testing.T) { 10 | var data []uint64 11 | Uint64Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestUint64Desc_EmptySlice(t *testing.T) { 18 | var data []uint64 19 | Uint64Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestUint64Asc_SingleElement(t *testing.T) { 26 | data := []uint64{42} 27 | Uint64Asc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestUint64Desc_SingleElement(t *testing.T) { 34 | data := []uint64{42} 35 | Uint64Desc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestUint64Asc_AllEqual(t *testing.T) { 42 | data := make([]uint64, 1000) 43 | for i := range data { 44 | data[i] = 7 45 | } 46 | Uint64Asc(data) 47 | for _, v := range data { 48 | if v != 7 { 49 | t.Errorf("expected all elements to be 7, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestUint64Desc_AllEqual(t *testing.T) { 56 | data := make([]uint64, 1000) 57 | for i := range data { 58 | data[i] = 7 59 | } 60 | Uint64Desc(data) 61 | for _, v := range data { 62 | if v != 7 { 63 | t.Errorf("expected all elements to be 7, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestUint64Asc_AlreadySorted(t *testing.T) { 70 | data := make([]uint64, 10000) 71 | for i := range data { 72 | data[i] = uint64(i) 73 | } 74 | Uint64Asc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != uint64(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestUint64Desc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]uint64, n) 86 | for i := range data { 87 | data[i] = uint64(n - i - 1) 88 | } 89 | Uint64Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestUint64Asc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]uint64, n) 101 | for i := range data { 102 | data[i] = uint64(n - i) 103 | } 104 | Uint64Asc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestUint64Desc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]uint64, n) 116 | for i := range data { 117 | data[i] = uint64(i) 118 | } 119 | Uint64Desc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestUint64Asc_SmallRandom(t *testing.T) { 129 | data := genUint64s(1000) 130 | expected := append([]uint64(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | Uint64Asc(data) 133 | if !uint64SlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for small ascending slice") 135 | } 136 | } 137 | 138 | func TestUint64Desc_SmallRandom(t *testing.T) { 139 | data := genUint64s(1000) 140 | expected := append([]uint64(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | Uint64Desc(data) 143 | if !uint64SlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for small descending slice") 145 | } 146 | } 147 | 148 | func TestUint64Asc_LargeRandom(t *testing.T) { 149 | data := genUint64s(2000000) 150 | expected := append([]uint64(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | Uint64Asc(data) 153 | if !uint64SlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for large slice") 155 | } 156 | } 157 | 158 | func TestUint64Desc_LargeRandom(t *testing.T) { 159 | data := genUint64s(2000000) 160 | expected := append([]uint64(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | Uint64Desc(data) 163 | if !uint64SlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for large descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortUint64Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Uint64_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genUint64s(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]uint64, len(data)) 176 | copy(tmp, data) 177 | Uint64Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortUint64Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Uint64_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genUint64s(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]uint64, len(data)) 191 | copy(tmp, data) 192 | Uint64Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /uint8.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "sync" 6 | ) 7 | 8 | func Uint8Asc(data []uint8) { 9 | uint8Sort(data, false) 10 | } 11 | 12 | func Uint8Desc(data []uint8) { 13 | uint8Sort(data, true) 14 | } 15 | 16 | func uint8Sort(data []uint8, reverse bool) { 17 | n := len(data) 18 | if n < Uint8MinParallelSize { 19 | sort.Slice(data, func(i, j int) bool { 20 | return data[i] < data[j] 21 | }) 22 | if reverse { 23 | uint8Reverse(data) 24 | } 25 | return 26 | } 27 | 28 | chunkSize := (n + CoreCount - 1) / CoreCount 29 | 30 | chunks := make([][]uint8, 0, CoreCount) 31 | for i := 0; i < n; i += chunkSize { 32 | end := i + chunkSize 33 | if end > n { 34 | end = n 35 | } 36 | chunks = append(chunks, data[i:end]) 37 | } 38 | 39 | var wg sync.WaitGroup 40 | for _, chunk := range chunks { 41 | wg.Add(1) 42 | go func(c []uint8) { 43 | defer wg.Done() 44 | sort.Slice(c, func(i, j int) bool { 45 | return c[i] < c[j] 46 | }) 47 | }(chunk) 48 | } 49 | wg.Wait() 50 | 51 | for len(chunks) > 1 { 52 | mergedCount := (len(chunks) + 1) / 2 53 | merged := make([][]uint8, mergedCount) 54 | 55 | var mWg sync.WaitGroup 56 | for i := 0; i < len(chunks); i += 2 { 57 | if i+1 == len(chunks) { 58 | merged[i/2] = chunks[i] 59 | continue 60 | } 61 | mWg.Add(1) 62 | go func(i int) { 63 | defer mWg.Done() 64 | merged[i/2] = uint8MergeSorted(chunks[i], chunks[i+1]) 65 | }(i) 66 | } 67 | mWg.Wait() 68 | chunks = merged 69 | } 70 | 71 | copy(data, chunks[0]) 72 | if reverse { 73 | uint8Reverse(data) 74 | } 75 | } 76 | 77 | func uint8MergeSorted(a, b []uint8) []uint8 { 78 | res := make([]uint8, len(a)+len(b)) 79 | i, j, k := 0, 0, 0 80 | for i < len(a) && j < len(b) { 81 | if a[i] <= b[j] { 82 | res[k] = a[i] 83 | i++ 84 | } else { 85 | res[k] = b[j] 86 | j++ 87 | } 88 | k++ 89 | } 90 | for i < len(a) { 91 | res[k] = a[i] 92 | i++ 93 | k++ 94 | } 95 | for j < len(b) { 96 | res[k] = b[j] 97 | j++ 98 | k++ 99 | } 100 | return res 101 | } 102 | 103 | func uint8Reverse(a []uint8) { 104 | for i, j := 0, len(a)-1; i < j; i, j = i+1, j-1 { 105 | a[i], a[j] = a[j], a[i] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /uint8_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestUint8Asc_EmptySlice(t *testing.T) { 10 | var data []uint8 11 | Uint8Asc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestUint8Desc_EmptySlice(t *testing.T) { 18 | var data []uint8 19 | Uint8Desc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestUint8Asc_SingleElement(t *testing.T) { 26 | data := []uint8{42} 27 | Uint8Asc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestUint8Desc_SingleElement(t *testing.T) { 34 | data := []uint8{42} 35 | Uint8Desc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestUint8Asc_AllEqual(t *testing.T) { 42 | data := make([]uint8, 1000) 43 | for i := range data { 44 | data[i] = 7 45 | } 46 | Uint8Asc(data) 47 | for _, v := range data { 48 | if v != 7 { 49 | t.Errorf("expected all elements to be 7, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestUint8Desc_AllEqual(t *testing.T) { 56 | data := make([]uint8, 1000) 57 | for i := range data { 58 | data[i] = 7 59 | } 60 | Uint8Desc(data) 61 | for _, v := range data { 62 | if v != 7 { 63 | t.Errorf("expected all elements to be 7, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestUint8Asc_AlreadySorted(t *testing.T) { 70 | data := make([]uint8, 255) 71 | for i := range data { 72 | data[i] = uint8(i) 73 | } 74 | Uint8Asc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != uint8(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestUint8Desc_AlreadySorted(t *testing.T) { 84 | n := 255 85 | data := make([]uint8, n) 86 | for i := range data { 87 | data[i] = uint8(n - i - 1) 88 | } 89 | Uint8Desc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestUint8Asc_ReverseSorted(t *testing.T) { 99 | n := 255 100 | data := make([]uint8, n) 101 | for i := range data { 102 | data[i] = uint8(n - i) 103 | } 104 | Uint8Asc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestUint8Desc_ReverseSorted(t *testing.T) { 114 | n := 255 115 | data := make([]uint8, n) 116 | for i := range data { 117 | data[i] = uint8(i) 118 | } 119 | Uint8Desc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestUint8Asc_SmallRandom(t *testing.T) { 129 | data := genUint8s(1000) 130 | expected := append([]uint8(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | Uint8Asc(data) 133 | if !uint8SlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for small ascending slice") 135 | } 136 | } 137 | 138 | func TestUint8Desc_SmallRandom(t *testing.T) { 139 | data := genUint8s(1000) 140 | expected := append([]uint8(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | Uint8Desc(data) 143 | if !uint8SlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for small descending slice") 145 | } 146 | } 147 | 148 | func TestUint8Asc_LargeRandom(t *testing.T) { 149 | data := genUint8s(2000000) 150 | expected := append([]uint8(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | Uint8Asc(data) 153 | if !uint8SlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for large slice") 155 | } 156 | } 157 | 158 | func TestUint8Desc_LargeRandom(t *testing.T) { 159 | data := genUint8s(2000000) 160 | expected := append([]uint8(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | Uint8Desc(data) 163 | if !uint8SlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for large descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortUint8Asc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Uint8_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genUint8s(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]uint8, len(data)) 176 | copy(tmp, data) 177 | Uint8Asc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortUint8Desc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Uint8_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genUint8s(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]uint8, len(data)) 191 | copy(tmp, data) 192 | Uint8Desc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /uint_test.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "sort" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestUintAsc_EmptySlice(t *testing.T) { 10 | var data []uint 11 | UintAsc(data) 12 | if len(data) != 0 { 13 | t.Errorf("expected empty slice, got %v", data) 14 | } 15 | } 16 | 17 | func TestUintDesc_EmptySlice(t *testing.T) { 18 | var data []uint 19 | UintDesc(data) 20 | if len(data) != 0 { 21 | t.Errorf("expected empty slice, got %v", data) 22 | } 23 | } 24 | 25 | func TestUintAsc_SingleElement(t *testing.T) { 26 | data := []uint{42} 27 | UintAsc(data) 28 | if data[0] != 42 { 29 | t.Errorf("expected [42], got %v", data) 30 | } 31 | } 32 | 33 | func TestUintDesc_SingleElement(t *testing.T) { 34 | data := []uint{42} 35 | UintDesc(data) 36 | if data[0] != 42 { 37 | t.Errorf("expected [42], got %v", data) 38 | } 39 | } 40 | 41 | func TestUintAsc_AllEqual(t *testing.T) { 42 | data := make([]uint, 1000) 43 | for i := range data { 44 | data[i] = 7 45 | } 46 | UintAsc(data) 47 | for _, v := range data { 48 | if v != 7 { 49 | t.Errorf("expected all elements to be 7, got %v", data) 50 | break 51 | } 52 | } 53 | } 54 | 55 | func TestUintDesc_AllEqual(t *testing.T) { 56 | data := make([]uint, 1000) 57 | for i := range data { 58 | data[i] = 7 59 | } 60 | UintDesc(data) 61 | for _, v := range data { 62 | if v != 7 { 63 | t.Errorf("expected all elements to be 7, got %v", data) 64 | break 65 | } 66 | } 67 | } 68 | 69 | func TestUintAsc_AlreadySorted(t *testing.T) { 70 | data := make([]uint, 10000) 71 | for i := range data { 72 | data[i] = uint(i) 73 | } 74 | UintAsc(data) 75 | for i := 0; i < len(data); i++ { 76 | if data[i] != uint(i) { 77 | t.Errorf("expected already sorted slice to remain unchanged") 78 | break 79 | } 80 | } 81 | } 82 | 83 | func TestUintDesc_AlreadySorted(t *testing.T) { 84 | n := 10000 85 | data := make([]uint, n) 86 | for i := range data { 87 | data[i] = uint(n - i - 1) 88 | } 89 | UintDesc(data) 90 | for i := 1; i < len(data); i++ { 91 | if data[i-1] < data[i] { 92 | t.Errorf("expected already descending slice to remain unchanged") 93 | break 94 | } 95 | } 96 | } 97 | 98 | func TestUintAsc_ReverseSorted(t *testing.T) { 99 | n := 10000 100 | data := make([]uint, n) 101 | for i := range data { 102 | data[i] = uint(n - i) 103 | } 104 | UintAsc(data) 105 | for i := 1; i < n; i++ { 106 | if data[i-1] > data[i] { 107 | t.Errorf("slice not sorted in ascending order") 108 | break 109 | } 110 | } 111 | } 112 | 113 | func TestUintDesc_ReverseSorted(t *testing.T) { 114 | n := 10000 115 | data := make([]uint, n) 116 | for i := range data { 117 | data[i] = uint(i) 118 | } 119 | UintDesc(data) 120 | for i := 1; i < n; i++ { 121 | if data[i-1] < data[i] { 122 | t.Errorf("slice not sorted in descending order") 123 | break 124 | } 125 | } 126 | } 127 | 128 | func TestUintAsc_SmallRandom(t *testing.T) { 129 | data := genUints(1000) 130 | expected := append([]uint(nil), data...) 131 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 132 | UintAsc(data) 133 | if !uintSlicesEqual(data, expected) { 134 | t.Errorf("sorted result incorrect for small ascending slice") 135 | } 136 | } 137 | 138 | func TestUintDesc_SmallRandom(t *testing.T) { 139 | data := genUints(1000) 140 | expected := append([]uint(nil), data...) 141 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 142 | UintDesc(data) 143 | if !uintSlicesEqual(data, expected) { 144 | t.Errorf("sorted result incorrect for small descending slice") 145 | } 146 | } 147 | 148 | func TestUintAsc_LargeRandom(t *testing.T) { 149 | data := genUints(2000000) 150 | expected := append([]uint(nil), data...) 151 | sort.Slice(expected, func(i, j int) bool { return expected[i] < expected[j] }) 152 | UintAsc(data) 153 | if !uintSlicesEqual(data, expected) { 154 | t.Errorf("sorted result incorrect for large slice") 155 | } 156 | } 157 | 158 | func TestUintDesc_LargeRandom(t *testing.T) { 159 | data := genUints(2000000) 160 | expected := append([]uint(nil), data...) 161 | sort.Slice(expected, func(i, j int) bool { return expected[i] > expected[j] }) 162 | UintDesc(data) 163 | if !uintSlicesEqual(data, expected) { 164 | t.Errorf("sorted result incorrect for large descending slice") 165 | } 166 | } 167 | 168 | func BenchmarkParsortUintAsc(b *testing.B) { 169 | for _, size := range testSizes { 170 | b.Run("Parsort_Asc_Uint_"+strconv.Itoa(size), func(b *testing.B) { 171 | data := genUints(size) 172 | b.ReportAllocs() 173 | b.ResetTimer() 174 | for i := 0; i < b.N; i++ { 175 | tmp := make([]uint, len(data)) 176 | copy(tmp, data) 177 | UintAsc(tmp) 178 | } 179 | }) 180 | } 181 | } 182 | 183 | func BenchmarkParsortUintDesc(b *testing.B) { 184 | for _, size := range testSizes { 185 | b.Run("Parsort_Desc_Uint_"+strconv.Itoa(size), func(b *testing.B) { 186 | data := genUints(size) 187 | b.ReportAllocs() 188 | b.ResetTimer() 189 | for i := 0; i < b.N; i++ { 190 | tmp := make([]uint, len(data)) 191 | copy(tmp, data) 192 | UintDesc(tmp) 193 | } 194 | }) 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package parsort 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "math/rand" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | var testSizes = []int{1000, 10000, 100000, 1000000, 10000000} 12 | 13 | func genInts(n int) []int { 14 | a := make([]int, n) 15 | for i := range a { 16 | a[i] = rand.Int() 17 | } 18 | return a 19 | } 20 | 21 | func intSlicesEqual(a, b []int) bool { 22 | if len(a) != len(b) { 23 | return false 24 | } 25 | for i := range a { 26 | if a[i] != b[i] { 27 | return false 28 | } 29 | } 30 | return true 31 | } 32 | 33 | func genInt8s(n int) []int8 { 34 | a := make([]int8, n) 35 | for i := range a { 36 | a[i] = int8(rand.Intn(256) - 128) 37 | } 38 | return a 39 | } 40 | 41 | func int8SlicesEqual(a, b []int8) bool { 42 | if len(a) != len(b) { 43 | return false 44 | } 45 | for i := range a { 46 | if a[i] != b[i] { 47 | return false 48 | } 49 | } 50 | return true 51 | } 52 | 53 | func genInt16s(n int) []int16 { 54 | a := make([]int16, n) 55 | for i := range a { 56 | a[i] = int16(rand.Intn(65536) - 32768) 57 | } 58 | return a 59 | } 60 | 61 | func int16SlicesEqual(a, b []int16) bool { 62 | if len(a) != len(b) { 63 | return false 64 | } 65 | for i := range a { 66 | if a[i] != b[i] { 67 | return false 68 | } 69 | } 70 | return true 71 | } 72 | 73 | func genInt32s(n int) []int32 { 74 | a := make([]int32, n) 75 | for i := range a { 76 | a[i] = rand.Int31() 77 | } 78 | return a 79 | } 80 | 81 | func int32SlicesEqual(a, b []int32) bool { 82 | if len(a) != len(b) { 83 | return false 84 | } 85 | for i := range a { 86 | if a[i] != b[i] { 87 | return false 88 | } 89 | } 90 | return true 91 | } 92 | 93 | func genInt64s(n int) []int64 { 94 | a := make([]int64, n) 95 | for i := range a { 96 | a[i] = rand.Int63() 97 | } 98 | return a 99 | } 100 | 101 | func int64SlicesEqual(a, b []int64) bool { 102 | if len(a) != len(b) { 103 | return false 104 | } 105 | for i := range a { 106 | if a[i] != b[i] { 107 | return false 108 | } 109 | } 110 | return true 111 | } 112 | 113 | func genUints(n int) []uint { 114 | a := make([]uint, n) 115 | for i := range a { 116 | a[i] = uint(rand.Uint32()) // 32-bit range is safe for most platforms 117 | } 118 | return a 119 | } 120 | 121 | func uintSlicesEqual(a, b []uint) bool { 122 | if len(a) != len(b) { 123 | return false 124 | } 125 | for i := range a { 126 | if a[i] != b[i] { 127 | return false 128 | } 129 | } 130 | return true 131 | } 132 | 133 | func genUint8s(n int) []uint8 { 134 | a := make([]uint8, n) 135 | for i := range a { 136 | a[i] = uint8(rand.Intn(256)) 137 | } 138 | return a 139 | } 140 | 141 | func uint8SlicesEqual(a, b []uint8) bool { 142 | if len(a) != len(b) { 143 | return false 144 | } 145 | for i := range a { 146 | if a[i] != b[i] { 147 | return false 148 | } 149 | } 150 | return true 151 | } 152 | 153 | func genUint16s(n int) []uint16 { 154 | a := make([]uint16, n) 155 | for i := range a { 156 | a[i] = uint16(rand.Intn(65536)) 157 | } 158 | return a 159 | } 160 | 161 | func uint16SlicesEqual(a, b []uint16) bool { 162 | if len(a) != len(b) { 163 | return false 164 | } 165 | for i := range a { 166 | if a[i] != b[i] { 167 | return false 168 | } 169 | } 170 | return true 171 | } 172 | 173 | func genUint32s(n int) []uint32 { 174 | a := make([]uint32, n) 175 | for i := range a { 176 | a[i] = rand.Uint32() 177 | } 178 | return a 179 | } 180 | 181 | func uint32SlicesEqual(a, b []uint32) bool { 182 | if len(a) != len(b) { 183 | return false 184 | } 185 | for i := range a { 186 | if a[i] != b[i] { 187 | return false 188 | } 189 | } 190 | return true 191 | } 192 | 193 | func genUint64s(n int) []uint64 { 194 | a := make([]uint64, n) 195 | for i := range a { 196 | a[i] = rand.Uint64() 197 | } 198 | return a 199 | } 200 | 201 | func uint64SlicesEqual(a, b []uint64) bool { 202 | if len(a) != len(b) { 203 | return false 204 | } 205 | for i := range a { 206 | if a[i] != b[i] { 207 | return false 208 | } 209 | } 210 | return true 211 | } 212 | 213 | func genFloat32s(n int) []float32 { 214 | a := make([]float32, n) 215 | for i := range a { 216 | a[i] = rand.Float32() 217 | } 218 | return a 219 | } 220 | 221 | func float32SlicesEqual(a, b []float32) bool { 222 | if len(a) != len(b) { 223 | return false 224 | } 225 | for i := range a { 226 | if math.Abs(float64(a[i]-b[i])) > 1e-5 { 227 | return false 228 | } 229 | } 230 | return true 231 | } 232 | 233 | func genFloats(n int) []float64 { 234 | a := make([]float64, n) 235 | for i := range a { 236 | a[i] = rand.Float64() 237 | } 238 | return a 239 | } 240 | 241 | func floatSlicesEqual(a, b []float64) bool { 242 | if len(a) != len(b) { 243 | return false 244 | } 245 | for i := range a { 246 | if math.Abs(a[i]-b[i]) > 1e-9 { 247 | return false 248 | } 249 | } 250 | return true 251 | } 252 | 253 | func genStrings(n int) []string { 254 | a := make([]string, n) 255 | for i := range a { 256 | a[i] = "str" + strconv.Itoa(rand.Intn(1000000)) 257 | } 258 | return a 259 | } 260 | 261 | func stringSlicesEqual(a, b []string) bool { 262 | if len(a) != len(b) { 263 | return false 264 | } 265 | for i := range a { 266 | if a[i] != b[i] { 267 | return false 268 | } 269 | } 270 | return true 271 | } 272 | 273 | func genTimes(n int) []time.Time { 274 | a := make([]time.Time, n) 275 | base := time.Now() 276 | for i := range a { 277 | a[i] = base.Add(time.Duration(rand.Int63n(1e9))) 278 | } 279 | return a 280 | } 281 | 282 | func timeSlicesEqual(a, b []time.Time) bool { 283 | if len(a) != len(b) { 284 | return false 285 | } 286 | for i := range a { 287 | if !a[i].Equal(b[i]) { 288 | return false 289 | } 290 | } 291 | return true 292 | } 293 | 294 | type person struct { 295 | Name string 296 | Age int 297 | } 298 | 299 | func genPeople(n int) []person { 300 | a := make([]person, n) 301 | for i := range a { 302 | a[i] = person{ 303 | Name: fmt.Sprintf("Name%d", rand.Intn(1000000)), 304 | Age: rand.Intn(100), 305 | } 306 | } 307 | return a 308 | } 309 | 310 | type byAge []person 311 | 312 | func (a byAge) Len() int { return len(a) } 313 | func (a byAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 314 | func (a byAge) Less(i, j int) bool { return a[i].Age < a[j].Age } 315 | 316 | func isSortedAsc(data []person) bool { 317 | for i := 1; i < len(data); i++ { 318 | if data[i-1].Age > data[i].Age { 319 | return false 320 | } 321 | } 322 | return true 323 | } 324 | 325 | func isSortedDesc(data []person) bool { 326 | for i := 1; i < len(data); i++ { 327 | if data[i-1].Age < data[i].Age { 328 | return false 329 | } 330 | } 331 | return true 332 | } 333 | --------------------------------------------------------------------------------