Skip to content

Commit

Permalink
Merge pull request #209 from Morwenn/develop
Browse files Browse the repository at this point in the history
Release 1.13.1
  • Loading branch information
Morwenn committed Oct 2, 2022
2 parents b3ce19e + b0de0fa commit aeb535f
Show file tree
Hide file tree
Showing 106 changed files with 3,557 additions and 3,004 deletions.
10 changes: 4 additions & 6 deletions .github/workflows/build-macos.yml
Expand Up @@ -23,14 +23,14 @@ on:

jobs:
build:
runs-on: macos-10.15
runs-on: macos-11

strategy:
fail-fast: false
matrix:
cxx:
- g++-9
- $(brew --prefix llvm)/bin/clang++ # Clang 11
- g++-10
- clang++
config:
# Release build
- build_type: Release
Expand All @@ -41,8 +41,7 @@ jobs:
sanitize: undefined

steps:
- uses: actions/checkout@v2
- uses: seanmiddleditch/gha-setup-ninja@master
- uses: actions/checkout@v3

- name: Configure CMake
working-directory: ${{runner.workspace}}
Expand All @@ -52,7 +51,6 @@ jobs:
-DCMAKE_CONFIGURATION_TYPES=${{matrix.config.build_type}} \
-DCMAKE_BUILD_TYPE=${{matrix.config.build_type}} \
-DCPPSORT_SANITIZE=${{matrix.config.sanitize}} \
-GNinja \
-DCPPSORT_BUILD_EXAMPLES=ON
- name: Build the test suite
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-mingw.yml
Expand Up @@ -31,7 +31,7 @@ jobs:
build_type: [Debug, Release]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Configure CMake
shell: pwsh
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-msvc.yml
Expand Up @@ -31,7 +31,7 @@ jobs:
build_type: [Debug, Release]

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Configure CMake
shell: pwsh
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-ubuntu.yml
Expand Up @@ -43,7 +43,7 @@ jobs:
sanitize: undefined

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- name: Install GCC
if: ${{matrix.cxx == 'g++-5'}}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/code-coverage.yml
Expand Up @@ -23,7 +23,7 @@ jobs:

steps:
- name: Checkout project
uses: actions/checkout@v2
uses: actions/checkout@v3

- name: Install LCOV
run: sudo apt-get install -y lcov
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/deploy-to-wiki.yml
Expand Up @@ -18,13 +18,13 @@ jobs:

steps:
- name: Checkout /docs
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: ${{github.repository}}
path: main

- name: Checkout wiki
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: ${{github.repository}}.wiki
path: wiki
Expand Down
4 changes: 2 additions & 2 deletions .gitignore
@@ -1,8 +1,8 @@
# Copyright (c) 2015-2021 Morwenn
# Copyright (c) 2015-2022 Morwenn
# SPDX-License-Identifier: MIT

# Usual build directory
build
build*

# Benchmark results directories
results
Expand Down
4 changes: 2 additions & 2 deletions CMakeLists.txt
@@ -1,11 +1,11 @@
# Copyright (c) 2015-2021 Morwenn
# Copyright (c) 2015-2022 Morwenn
# SPDX-License-Identifier: MIT

cmake_minimum_required(VERSION 3.8.0)

list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)

project(cpp-sort VERSION 1.13.0 LANGUAGES CXX)
project(cpp-sort VERSION 1.13.1 LANGUAGES CXX)

include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
Expand Down
4 changes: 2 additions & 2 deletions README.md
@@ -1,7 +1,7 @@
![cpp-sort logo](docs/images/cpp-sort-logo.svg)

[![Latest Release](https://img.shields.io/badge/release-1.13.0-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.13.0)
[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.13.0-blue.svg)](https://conan.io/center/cpp-sort?version=1.13.0)
[![Latest Release](https://img.shields.io/badge/release-1.13.1-blue.svg)](https://github.com/Morwenn/cpp-sort/releases/tag/1.13.1)
[![Conan Package](https://img.shields.io/badge/conan-cpp--sort%2F1.13.1-blue.svg)](https://conan.io/center/cpp-sort?version=1.13.1)
[![Code Coverage](https://codecov.io/gh/Morwenn/cpp-sort/branch/develop/graph/badge.svg)](https://codecov.io/gh/Morwenn/cpp-sort)
[![Pitchfork Layout](https://img.shields.io/badge/standard-PFL-orange.svg)](https://github.com/vector-of-bool/pitchfork)

Expand Down
8 changes: 5 additions & 3 deletions benchmarks/benchmarking-tools/distributions.h
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015-2021 Morwenn
* Copyright (c) 2015-2022 Morwenn
* SPDX-License-Identifier: MIT
*/
#include <algorithm>
Expand All @@ -13,6 +13,7 @@
#include <cpp-sort/detail/type_traits.h>
#include <cpp-sort/utility/as_function.h>
#include <cpp-sort/utility/functional.h>
#include <cpp-sort/utility/functional.h>

// Pseudo-random number generator, used by some distributions
thread_local std::mt19937_64 distributions_prng(std::time(nullptr));
Expand Down Expand Up @@ -400,9 +401,10 @@ namespace dist
static constexpr const char* output = "vergesort_killer.txt";
};

struct as_long_string
struct as_long_string:
cppsort::utility::projection_base
{
auto operator()(long long int value)
auto operator()(long long int value) const
-> std::string
{
auto str = std::to_string(value);
Expand Down
4 changes: 2 additions & 2 deletions conanfile.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

# Copyright (c) 2018-2021 Morwenn
# Copyright (c) 2018-2022 Morwenn
# SPDX-License-Identifier: MIT

from conans import CMake, ConanFile
Expand All @@ -10,7 +10,7 @@

class CppSortConan(ConanFile):
name = "cpp-sort"
version = "1.13.0"
version = "1.13.1"
description = "Additional sorting algorithms & related tools"
topics = "conan", "cpp-sort", "sorting", "algorithms"
url = "https://github.com/Morwenn/cpp-sort"
Expand Down
37 changes: 20 additions & 17 deletions docs/Benchmarks.md
@@ -1,14 +1,17 @@
*Note: this page only benchmarls sorting algorithms under specific conditions. It can be used as a quick guide but if you really need a fast algorithm for a specific use case, you better run your own benchmarks.*
*Note: this page only benchmarks sorting algorithms under specific conditions. It can be used as a quick guide but if you really need a fast algorithm for a specific use case, you better run your own benchmarks.*

*Last meaningful update: 1.9.0 release, 1.12.0 for measures of presortedness.*
*Last meaningful updates:*
* *1.13.1 for unstable random-access sorts, slow O(n log n) sorts, forward sorts, and the expensive move/cheap comparison benchmark*
* *1.12.0 for measures of presortedness*
* *1.9.0 otherwise*

Benchmarking is hard and I might not be doing it right. Moreover, benchmarking sorting algorithms highlights that the time needed to sort a collection of elements depends on several things: the type to sort, the size of the collection, the cost of comparing two values, the cost of moving an element, the patterns formed by the distribution of the values in the collection to sort, the type of the collection itself, etc. The aim of this page is to help you choose a sorting algorithm depending on your needs. You can find two main kinds of benchmarks: the ones that compare algorithms against shuffled collections of different sizes, and the ones that compare algorithms against different data patterns for a given collection size.

It is worth noting that most benchmarks on this page use collections of `double`: the idea is to sort collections of a simple enough type without getting numbers skewed by the impressive amount of optimizations that compilers are able to perform for integer types. While not perfect, `double` (without NaNs or infinities) falls into the "cheap enough to compare, cheap enough to move" category that most of the benchmarks here target.

All of the graphs on this page have been generated with slightly modified versions of the scripts found in the project's benchmarks folder. There are just too many things to check; if you ever want a specific benchmark, don't hesitate to ask for it.

*The benchmarks were run on Windows 10 with 64-bit MinGW-w64 g++10.1, with the flags -O3 -march=native -std=c++2a.*
*The latest benchmarks were run on Windows 10 with 64-bit MinGW-w64 g++12.0, with the flags -O3 -march=native -std=c++20.*

# Random-access iterables

Expand All @@ -19,7 +22,7 @@ Most sorting algorithms are designed to work with random-access iterators, so th
Sorting a random-access collection with an unstable sort is probably one of the most common things to want, and not only are those sorts among the fastest comparison sorts, but type-specific sorters can also be used to sort a variety of types. If you don't know what algorithm you want and don't have specific needs, then you probably want one of these.

![Benchmark speed of unstable sorts with increasing size for std::vector<double>](https://i.imgur.com/Q3IEeci.png)
![Benchmark speed of unstable sorts with increasing size for std::deque<double>](https://i.imgur.com/XjRGUmc.png)
![Benchmark speed of unstable sorts with increasing size for std::deque<double>](https://i.imgur.com/oRW5kFr.png)

The plots above show a few general tendencies:
* `selection_sort` is O(n²) and doesn't scale.
Expand All @@ -28,8 +31,8 @@ The plots above show a few general tendencies:

The quicksort derivatives and the hybrid radix sorts are generally the fastest of the lot, yet `drop_merge_sort` seems to offer interesting speedups for `std::deque` despite not being designed to be the fastest on truly shuffled data. Part of the explanation is that it uses `pdq_sort` in a contiguous memory buffer underneath, which might be faster for `std::deque` than sorting completely in-place.

![Benchmark unstable sorts over different patterns for std::vector<double>](https://i.imgur.com/MlEcGuL.png)
![Benchmark unstable sorts over different patterns for std::deque<double>](https://i.imgur.com/o7sOfMB.png)
![Benchmark unstable sorts over different patterns for std::vector<double>](https://i.imgur.com/WZ4s6Xt.png)
![Benchmark unstable sorts over different patterns for std::deque<double>](https://i.imgur.com/UAaObUW.png)

A few random takeways:
* All the algorithms are more or less adaptive, not always for the same patterns.
Expand Down Expand Up @@ -61,15 +64,15 @@ These plots highlight a few important things:

I decided to include a dedicated category for slow O(n log n) sorts, because I find this class of algorithms interesting. This category contains experimental algorithms, often taken from rather old research papers. `heap_sort` is used as the "fast" algorithm in this category, despite it being consistently the slowest in the previous category.

![Benchmark speed of slow O(n log n) sorts with increasing size for std::vector<double>](https://i.imgur.com/nSX9n1q.png)
![Benchmark slow O(n log n) sorts over different patterns for std::vector<double>](https://i.imgur.com/z9dR16G.png)
![Benchmark slow O(n log n) sorts over different patterns for std::deque<double>](https://i.imgur.com/Viu13nj.png)
![Benchmark speed of slow O(n log n) sorts with increasing size for std::vector<double>](https://i.imgur.com/2sx8Hk7.png)
![Benchmark slow O(n log n) sorts over different patterns for std::vector<double>](https://i.imgur.com/RkiYdy8.png)
![Benchmark slow O(n log n) sorts over different patterns for std::deque<double>](https://i.imgur.com/Z9O4I6p.png)

The analysis is pretty simple here:
* Most of the algorithms in this category are slow, but exhibit a good adaptiveness with most kinds of patterns. It isn't all that surprising since I specifically found them in literature about adaptive sorting.
* `poplar_sort` is slower for `std::vector` than for `std::deque`, which makes me suspect a codegen issue somewhere.
* `poplar_sort` is a bit slower for `std::vector` than for `std::deque`, which makes me suspect a weird issue somewhere.
* As a result `smooth_sort` and `poplar_sort` beat each other depending on the type of the collection to sort.
* Slabsort has an unusual graph: it seems that even for shuffled data it might end up beating `heap_sort` when the collection grows big enough.
* Slabsort has an unusual graph: even for shuffled data it might end up beating `heap_sort` when the collection becomes big enough.

# Bidirectional iterables

Expand All @@ -91,14 +94,14 @@ For elements as small as `double`, there are two clear winners here: `drop_merge

Even fewer sorters can handle forward iterators. `out_of_place_adapter(pdq_sort)` was not included in the patterns benchmark, because it adapts to patterns the same way `pdq_sort` does.

![Benchmark speed of sorts with increasing size for std::forward_list<double>](https://i.imgur.com/SMTKhqG.png)
![Benchmark sorts over different patterns for std::forward_list<double>](https://i.imgur.com/XLndRbU.png)
![Benchmark speed of sorts with increasing size for std::forward_list<double>](https://i.imgur.com/if15kX1.png)
![Benchmark sorts over different patterns for std::forward_list<double>](https://i.imgur.com/uF0UzLm.png)

The results are roughly the same than with bidirectional iterables:
* Sorting out-of-place is faster than anything else.
* [`std::forward_list::sort`][std-forward-list-sort] doesn't scale well unless moves are expensive.
* [`std::forward_list::sort`][std-forward-list-sort] doesn't scale well when moves are inexpensive.
* `quick_sort` and `quick_merge_sort` are good enough contenders when trying to avoid heap memory allocations.
* `mel_sort` is still bad, but becomes a dcent alternative when the input exhibits recognizable patterns.
* `mel_sort` is still bad, but becomes a decent alternative when the input exhibits recognizable patterns.

# Sorting under specific constraints

Expand Down Expand Up @@ -129,11 +132,11 @@ Both algorithms can be interesting depending on the sorting scenario.

## Expensive moves, cheap comparisons

Sometimes we have to sort a collection whose elements are expensive to move around but cheap to compare. In such a situation we can use `indirect_adapter` which sorts iterators to the elements and moves the elements into their direct place once the sorting order is known.
Sometimes one has to sort a collection whose elements are expensive to move around but cheap to compare. In such a situation `indirect_adapter` can be used: it sorts a collection of iterators to the elements, and moves the elements into their direct place once the sorting order is known.

The following example uses a collection of `std::array<doube, 100>` whose first element is the only one compared during the sort. Albeit a bit artificial, it illustrates the point well enough.

![Benchmark heap_sort vs. indirect_adapter(heap_sort) for a collection of std::array<double, 100>](https://i.imgur.com/mYUaxRT.png)
![Benchmark heap_sort vs. indirect_adapter(heap_sort) for a collection of std::array<double, 100>](https://i.imgur.com/Okkahwf.png)

The improvements are not always as clear as in this benchmark, but it shows that `indirect_adapter` might be an interesting tool to have in your sorting toolbox in such a scenario.

Expand Down
2 changes: 1 addition & 1 deletion docs/Chainable-projections.md
@@ -1,6 +1,6 @@
*New in version 1.7.0*

Sometimes one might need to apply several transformations to the elements of a collection before comparing them. To support this use case, some projection functions in **cpp-sort** can be composed with `operator|`
Sometimes one needs to apply several transformations to the elements of a collection before comparing them. To support this use case, some projection functions in **cpp-sort** can be composed with `operator|`

```cpp
struct my_negate:
Expand Down
4 changes: 3 additions & 1 deletion docs/Changelog.md
Expand Up @@ -73,7 +73,7 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe

* When available, [`std::ranges::less`][std-ranges-less] and [`std::ranges::greater`][std-ranges-greater] benefit from dedicated support wherever [`std::less<>`][std-less-void] and [`std::greater<>`][std-greater-void] are supported, with equivalent semantics.

* [`utility::iter_swap`][utility-iter-move] can now be used in more `constexpr` functions thanks to [`std::swap`][std-swap] begin `constexpr`.
* [`utility::iter_swap`][utility-iter-move] can now be used in more `constexpr` functions thanks to [`std::swap`][std-swap] being `constexpr`.

The feature-test macro `__cpp_lib_constexpr_algorithms` can be used to check whether `std::swap` is `constexpr`.

Expand All @@ -89,6 +89,8 @@ When compiled with C++20, **cpp-sort** might gain a few additional features depe

* Assumptions: some algorithms use assumptions in select places to make the compiler generate more efficient code. Whether such assumptions are available depends on the compiler.

* Vectorized algorithms: when compiled against the Microsoft STL, **cpp-sort** tries to take advantage of their vectorized algorithms when possible. This improves some algorithms when sorting contiguous collections of trivially copyable types.

* When using libstdc++, libc++ or the Microsoft STL, the return type of [`std::mem_fn`][std-mem-fn] is considered ["probably branchless"][branchless-traits] when it wraps a pointer to data member, which can improve the speed of [`pdq_sorter`][pdq-sorter] and everything that relies on it in some scenarios.


Expand Down
6 changes: 6 additions & 0 deletions docs/Comparators.md
Expand Up @@ -29,6 +29,8 @@ Total order comparators are considered as [generating branchless code][branchles

*Changed in version 1.5.0:* `total_greater` and `total_less` are respectively of type `total_greater_t` and `total_less_t`.

*Changed in version 1.13.1:* support for `[un]signed __int128`.

### Weak order comparators

```cpp
Expand All @@ -51,6 +53,8 @@ Weak order comparators are considered as [generating branchless code][branchless

*Changed in version 1.5.0:* `weak_greater` and `weak_less` are respectively of type `weak_greater_t` and `weak_less_t`.

*Changed in version 1.13.1:* support for `[un]signed __int128`.

### Partial order comparators

```cpp
Expand All @@ -66,6 +70,8 @@ Partial order comparators are considered as [generating branchless code][branchl

*Changed in version 1.5.0:* `partial_greater` and `partial_less` are respectively of type `partial_greater_t` and `partial_less_t`.

*Changed in version 1.13.1:* support for `[un]signed __int128`.

### Natural order comparator

```cpp
Expand Down
14 changes: 11 additions & 3 deletions docs/Fixed-size-sorters.md
Expand Up @@ -105,7 +105,9 @@ template<typename DifferenceType=std::ptrdiff_t>
-> std::array<utility::index_pair<DifferenceType>, /* Number of CEs in the network */>;
```

### `merge_exchange_network_sorter`
*New in version 1.11.0*

### `odd_even_merge_network_sorter`

```cpp
#include <cpp-sort/fixed/odd_even_merge_network_sorter.h>
Expand All @@ -128,6 +130,8 @@ template<typename DifferenceType=std::ptrdiff_t>
-> std::array<utility::index_pair<DifferenceType>, /* Number of CEs in the network */>;
```

*New in version 1.11.0*

### `sorting_network_sorter`

```cpp
Expand All @@ -147,7 +151,7 @@ Size | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16
:-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-:
**CEs** | 0 | 1 | 3 | 5 | 9 | 12 | 16 | 19 | 25 | 29 | 35 | 39 | 45 | 51 | 56 | 60
**Size** | **17** | **18** | **19** | **20** | **21** | **22** | **23** | **24** | **25** | **26** | **27** | **28** | **29** | **30** | **31** | **32**
**CEs** | 71 | 77 | 85 | 91 | 99 | 106 | 114 | 120 | 131 | 139 | 148 | 155 | 164 | 172 | 180 | 185
**CEs** | 71 | 77 | 85 | 91 | 99 | 106 | 114 | 120 | 130 | 139 | 148 | 155 | 164 | 172 | 180 | 185

One of the main advantages of sorting networks is the fixed number of CEs required to sort a collection: this means that sorting networks are far more resistant to time and cache attacks since the number of performed comparisons does not depend on the contents of the collection. However, additional care (not provided by the library) is required to ensure that the algorithms always perform the same amount of memory loads and stores. For example, one could create a `constant_time_iterator` with a dedicated `iter_swap` tuned to perform a constant-time compare-exchange operation.

Expand All @@ -157,7 +161,7 @@ All specializations of `sorting_network_sorter` provide a `index_pairs() static`

```cpp
template<typename DifferenceType=std::ptrdiff_t>
static constexpr auto index_pairs()
[[nodiscard]] static constexpr auto index_pairs()
-> std::array<utility::index_pair<DifferenceType>, /* Number of CEs in the network */>;
```

Expand All @@ -171,6 +175,10 @@ static constexpr auto index_pairs()

*Changed in version 1.13.0:* sorting 21, 22, 23, 25, 27 and 29 inputs respectively require 99, 106, 114, 131, 149 and 164 CEs instead of 100, 107, 115, 132, 150 and 165.

*Changed in version 1.13.1:* sorting 25 inputs requires 130 CEs instead of 131.

*Changed in version 1.13.1:* `index_pair()` is now `[[nodiscard]]` when possible for all `sorting_network_sorter` specializations.


[double-insertion-sort]: Original-research.md#double-insertion-sort
[fixed-sorter-traits]: Sorter-traits.md#fixed_sorter_traits
Expand Down

0 comments on commit aeb535f

Please sign in to comment.