Skip to content

Commit 8be31bf

Browse files
authored
PYTHON-4350 Faster and more consistent performance benchmark execution times (#1575)
1 parent 48d5a46 commit 8be31bf

File tree

1 file changed

+51
-12
lines changed

1 file changed

+51
-12
lines changed

test/performance/perf_test.py

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright 2015 MongoDB, Inc.
1+
# Copyright 2015-present MongoDB, Inc.
22
#
33
# Licensed under the Apache License, Version 2.0 (the "License");
44
# you may not use this file except in compliance with the License.
@@ -12,7 +12,31 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
"""Tests for the MongoDB Driver Performance Benchmarking Spec."""
15+
"""Tests for the MongoDB Driver Performance Benchmarking Spec.
16+
17+
See https://github.com/mongodb/specifications/blob/master/source/benchmarking/benchmarking.md
18+
19+
20+
To set up the benchmarks locally::
21+
22+
python -m pip install simplejson
23+
git clone --depth 1 https://github.com/mongodb/specifications.git
24+
pushd specifications/source/benchmarking/data
25+
tar xf extended_bson.tgz
26+
tar xf parallel.tgz
27+
tar xf single_and_multi_document.tgz
28+
popd
29+
export TEST_PATH="specifications/source/benchmarking/data"
30+
export OUTPUT_FILE="results.json"
31+
32+
Then to run all benchmarks quickly::
33+
34+
FASTBENCH=1 python test/performance/perf_test.py -v
35+
36+
To run individual benchmarks quickly::
37+
38+
FASTBENCH=1 python test/performance/perf_test.py -v TestRunCommand TestFindManyAndEmptyCursor
39+
"""
1640
from __future__ import annotations
1741

1842
import multiprocessing as mp
@@ -36,9 +60,18 @@
3660
from gridfs import GridFSBucket
3761
from pymongo import MongoClient
3862

63+
# Spec says to use at least 1 minute cumulative execution time and up to 100 iterations or 5 minutes but that
64+
# makes the benchmarks too slow. Instead, we use at least 30 seconds and at most 60 seconds.
3965
NUM_ITERATIONS = 100
40-
MAX_ITERATION_TIME = 300
66+
MIN_ITERATION_TIME = 30
67+
MAX_ITERATION_TIME = 60
4168
NUM_DOCS = 10000
69+
# When debugging or prototyping it's often useful to run the benchmarks locally, set FASTBENCH=1 to run quickly.
70+
if bool(os.getenv("FASTBENCH")):
71+
NUM_ITERATIONS = 2
72+
MIN_ITERATION_TIME = 0.1
73+
MAX_ITERATION_TIME = 0.5
74+
NUM_DOCS = 1000
4275

4376
TEST_PATH = os.environ.get(
4477
"TEST_PATH", os.path.join(os.path.dirname(os.path.realpath(__file__)), os.path.join("data"))
@@ -88,7 +121,7 @@ def tearDown(self):
88121
megabytes_per_sec = self.data_size / median / 1000000
89122
print(
90123
f"Completed {self.__class__.__name__} {megabytes_per_sec:.3f} MB/s, MEDIAN={self.percentile(50):.3f}s, "
91-
f"total time={duration:.3f}s"
124+
f"total time={duration:.3f}s, iterations={len(self.results)}"
92125
)
93126
result_data.append(
94127
{
@@ -125,19 +158,25 @@ def percentile(self, percentile):
125158
def runTest(self):
126159
results = []
127160
start = time.monotonic()
128-
for i in range(NUM_ITERATIONS):
129-
if time.monotonic() - start > MAX_ITERATION_TIME:
130-
with warnings.catch_warnings():
131-
warnings.simplefilter("default")
132-
warnings.warn(
133-
f"Test timed out after {MAX_ITERATION_TIME}s, completed {i}/{NUM_ITERATIONS} iterations."
134-
)
135-
break
161+
i = 0
162+
while True:
163+
i += 1
136164
self.before()
137165
with Timer() as timer:
138166
self.do_task()
139167
self.after()
140168
results.append(timer.interval)
169+
duration = time.monotonic() - start
170+
if duration > MIN_ITERATION_TIME and i >= NUM_ITERATIONS:
171+
break
172+
if duration > MAX_ITERATION_TIME:
173+
with warnings.catch_warnings():
174+
warnings.simplefilter("default")
175+
warnings.warn(
176+
f"{self.__class__.__name__} timed out after {MAX_ITERATION_TIME}s, completed {i}/{NUM_ITERATIONS} iterations."
177+
)
178+
179+
break
141180

142181
self.results = results
143182

0 commit comments

Comments
 (0)