-
-
Notifications
You must be signed in to change notification settings - Fork 32.1k
gh-108303: Move Lib/test/sortperf.py
to Tools/scripts
#114687
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
6903baa
gh-108303: Remove `Lib/test/sortperf.py`
sobolevn 145f089
Revert "gh-108303: Remove `Lib/test/sortperf.py`"
sobolevn 3b38525
Move `Lib/test/sortperf.py` to `Tools/scripts`
sobolevn 979bca0
Use `pyperf` script
sobolevn 052ec30
Fix CI
sobolevn 273eed8
Merge branch 'main' into issue-108303-sortperf
sobolevn 6c549d6
Merge branch 'main' into issue-108303-sortperf
sobolevn 7c2dd81
Address review
sobolevn File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
""" | ||
List sort performance test. | ||
|
||
To install `pyperf` you would need to: | ||
|
||
python3 -m pip install pyperf | ||
|
||
To run: | ||
|
||
python3 Tools/scripts/sortperf | ||
|
||
Options: | ||
|
||
* `benchmark` name to run | ||
* `--rnd-seed` to set random seed | ||
* `--size` to set the sorted list size | ||
|
||
Based on https://github.com/python/cpython/blob/963904335e579bfe39101adf3fd6a0cf705975ff/Lib/test/sortperf.py | ||
""&q 8000 uot; | ||
|
||
from __future__ import annotations | ||
|
||
import argparse | ||
import time | ||
import random | ||
|
||
|
||
# =============== | ||
# Data generation | ||
# =============== | ||
|
||
def _random_data(size: int, rand: random.Random) -> list[float]: | ||
result = [rand.random() for _ in range(size)] | ||
# Shuffle it a bit... | ||
for i in range(10): | ||
i = rand.randrange(size) | ||
temp = result[:i] | ||
del result[:i] | ||
temp.reverse() | ||
result.extend(temp) | ||
del temp | ||
assert len(result) == size | ||
return result | ||
|
||
|
||
def list_sort(size: int, rand: random.Random) -> list[float]: | ||
return _random_data(size, rand) | ||
|
||
|
||
def list_sort_descending(size: int, rand: random.Random) -> list[float]: | ||
return list(reversed(list_sort_ascending(size, rand))) | ||
|
||
|
||
def list_sort_ascending(size: int, rand: random.Random) -> list[float]: | ||
return sorted(_random_data(size, rand)) | ||
|
||
|
||
def list_sort_ascending_exchanged(size: int, rand: random.Random) -> list[float]: | ||
result = list_sort_ascending(size, rand) | ||
# Do 3 random exchanges. | ||
for _ in range(3): | ||
i1 = rand.randrange(size) | ||
i2 = rand.randrange(size) | ||
result[i1], result[i2] = result[i2], result[i1] | ||
return result | ||
|
||
|
||
def list_sort_ascending_random(size: int, rand: random.Random) -> list[float]: | ||
assert size >= 10, "This benchmark requires size to be >= 10" | ||
result = list_sort_ascending(size, rand) | ||
# Replace the last 10 with random floats. | ||
result[-10:] = [rand.random() for _ in range(10)] | ||
return result | ||
|
||
|
||
def list_sort_ascending_one_percent(size: int, rand: random.Random) -> list[float]: | ||
result = list_sort_ascending(size, rand) | ||
# Replace 1% of the elements at random. | ||
for _ in range(size // 100): | ||
result[rand.randrange(size)] = rand.random() | ||
return result | ||
|
||
|
||
def list_sort_duplicates(size: int, rand: random.Random) -> list[float]: | ||
assert size >= 4 | ||
result = list_sort_ascending(4, rand) | ||
# Arrange for lots of duplicates. | ||
result = result * (size // 4) | ||
# Force the elements to be distinct objects, else timings can be | ||
# artificially low. | ||
return list(map(abs, result)) | ||
|
||
|
||
def list_sort_equal(size: int, rand: random.Random) -> list[float]: | ||
# All equal. Again, force the elements to be distinct objects. | ||
return list(map(abs, [-0.519012] * size)) | ||
|
||
|
||
def list_sort_worst_case(size: int, rand: random.Random) -> list[float]: | ||
# This one looks like [3, 2, 1, 0, 0, 1, 2, 3]. It was a bad case | ||
# for an older implementation of quicksort, which used the median | ||
# of the first, last and middle elements as the pivot. | ||
half = size // 2 | ||
result = list(range(half - 1, -1, -1)) | ||
result.extend(range(half)) | ||
# Force to float, so that the timings are comparable. This is | ||
# significantly faster if we leave them as ints. | ||
return list(map(float, result)) | ||
|
||
|
||
# ========= | ||
# Benchmark | ||
# ========= | ||
|
||
class Benchmark: | ||
def __init__(self, name: str, size: int, seed: int) -> None: | ||
self._name = name | ||
self._size = size | ||
self._seed = seed | ||
self._random = random.Random(self._seed) | ||
|
||
def run(self, loops: int) -> float: | ||
all_data = self._prepare_data(loops) | ||
start = time.perf_counter() | ||
|
||
for data in all_data: | ||
data.sort() # Benching this method! | ||
|
||
return time.perf_counter() - start | ||
|
||
def _prepare_data(self, loops: int) -> list[float]: | ||
bench = BENCHMARKS[self._name] | ||
return [ | ||
bench(self._size, self._random) | ||
for _ in range(loops) | ||
] | ||
|
||
|
||
def add_cmdline_args(cmd: list[str], args) -> None: | ||
cmd.append(args.benchmark) | ||
cmd.append(f"--size={args.size}") | ||
cmd.append(f"--rng-seed={args.rng_seed}") | ||
|
||
|
||
def add_parser_args(parser: argparse.ArgumentParser) -> None: | ||
parser.add_argument( | ||
"benchmark", | ||
choices=BENCHMARKS, | ||
nargs="?", | ||
default="list_sort", | ||
sobolevn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
help="Can be any of: {0}".format(", ".join(BENCHMARKS)), | ||
) | ||
parser.add_argument( | ||
"--size", | ||
type=int, | ||
default=DEFAULT_SIZE, | ||
help=f"Size of the lists to sort (default: {DEFAULT_SIZE})", | ||
) | ||
parser.add_argument( | ||
"--rng-seed", | ||
type=int, | ||
default=DEFAULT_RANDOM_SEED, | ||
help=f"Random number generator seed (default: {DEFAULT_RANDOM_SEED})", | ||
) | ||
|
||
|
||
DEFAULT_SIZE = 262144 # 1 << 18 | ||
DEFAULT_RANDOM_SEED = 0 | ||
BENCHMARKS = { | ||
"list_sort": list_sort, | ||
"list_sort_descending": list_sort_descending, | ||
"list_sort_ascending": list_sort_ascending, | ||
"list_sort_ascending_exchanged": list_sort_ascending_exchanged, | ||
"list_sort_ascending_random": list_sort_ascending_random, | ||
"list_sort_ascending_one_percent": list_sort_ascending_one_percent, | ||
"list_sort_duplicates": list_sort_duplicates, | ||
"list_sort_equal": list_sort_equal, | ||
"list_sort_worst_case": list_sort_worst_case, | ||
} | ||
6BBB |
|
|
if __name__ == "__main__": | ||
# This needs `pyperf` 3rd party library: | ||
import pyperf | ||
|
||
runner = pyperf.Runner(add_cmdline_args=add_cmdline_args) | ||
add_parser_args(runner.argparser) | ||
args = runner.parse_args() | ||
|
||
runner.metadata["description"] = "Test `list.sort()` with different data" | ||
runner.metadata["list_sort_size"] = args.size | ||
runner.metadata["list_sort_random_seed"] = args.rng_seed | ||
|
||
benchmark = Benchmark(args.benchmark, args.size, args.rng_seed) | ||
runner.bench_time_func(args.benchmark, benchmark.run) | ||
sobolevn marked this conversation as resolved.
Show resolved
Hide resolved
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.