mirror of
https://github.com/python/cpython.git
synced 2026-04-13 23:31:02 +00:00
Remove PyThread_type_lock (now uses PyMutex internally). Add new benchmark options: - work_inside/work_outside: control work inside and outside the critical section to vary contention levels - num_locks: use multiple independent locks with threads assigned round-robin - total_iters: fixed iteration count per thread instead of time-based, useful for measuring fairness - num_acquisitions: lock acquisitions per loop iteration - random_locks: acquire random lock each iteration Also return elapsed time from benchmark_locks() and switch lockbench.py to use argparse.
92 lines
4.1 KiB
Python
92 lines
4.1 KiB
Python
# Measure the performance of PyMutex locks with short critical sections.
|
|
#
|
|
# Usage: python Tools/lockbench/lockbench.py [options]
|
|
#
|
|
# Options:
|
|
# --work-inside N Units of work inside the critical section (default: 1).
|
|
# --work-outside N Units of work outside the critical section (default: 0).
|
|
# Each unit of work is a dependent floating-point
|
|
# addition, which takes about 0.4 ns on modern
|
|
# Intel / AMD processors.
|
|
# --num-locks N Number of independent locks (default: 1). Threads are
|
|
# assigned to locks round-robin.
|
|
# --random-locks Each thread picks a random lock per acquisition instead
|
|
# of using a fixed assignment. Requires --num-locks > 1.
|
|
# --acquisitions N Lock acquisitions per loop iteration (default: 1).
|
|
# --total-iters N Fixed iterations per thread (default: 0 = time-based).
|
|
# Useful for measuring fairness: the benchmark runs until
|
|
# the slowest thread finishes.
|
|
#
|
|
# How to interpret the results:
|
|
#
|
|
# Acquisitions (kHz): Reports the total number of lock acquisitions in
|
|
# thousands of acquisitions per second. This is the most important metric,
|
|
# particularly for the 1 thread case because even in multithreaded programs,
|
|
# most lock acquisitions are not contended. Values for 2+ threads are
|
|
# only meaningful for `--disable-gil` builds, because the GIL prevents most
|
|
# situations where there is lock contention with short critical sections.
|
|
#
|
|
# Fairness: A measure of how evenly the lock acquisitions are distributed.
|
|
# A fairness of 1.0 means that all threads acquired the lock the same number
|
|
# of times. A fairness of 1/N means that only one thread ever acquired the
|
|
# lock.
|
|
# See https://en.wikipedia.org/wiki/Fairness_measure#Jain's_fairness_index
|
|
|
|
from _testinternalcapi import benchmark_locks
|
|
import argparse
|
|
|
|
|
|
def parse_threads(value):
|
|
if '-' in value:
|
|
lo, hi = value.split('-', 1)
|
|
lo, hi = int(lo), int(hi)
|
|
return range(lo, hi + 1)
|
|
return range(int(value), int(value) + 1)
|
|
|
|
def jains_fairness(values):
|
|
# Jain's fairness index
|
|
# See https://en.wikipedia.org/wiki/Fairness_measure
|
|
return (sum(values) ** 2) / (len(values) * sum(x ** 2 for x in values))
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Benchmark PyMutex locks")
|
|
parser.add_argument("--work-inside", type=int, default=1,
|
|
help="units of work inside the critical section")
|
|
parser.add_argument("--work-outside", type=int, default=0,
|
|
help="units of work outside the critical section")
|
|
parser.add_argument("--acquisitions", type=int, default=1,
|
|
help="lock acquisitions per loop iteration")
|
|
parser.add_argument("--total-iters", type=int, default=0,
|
|
help="fixed iterations per thread (0 = time-based)")
|
|
parser.add_argument("--num-locks", type=int, default=1,
|
|
help="number of independent locks (round-robin assignment)")
|
|
parser.add_argument("--random-locks", action="store_true",
|
|
help="pick a random lock per acquisition")
|
|
parser.add_argument("threads", type=parse_threads, nargs='?',
|
|
default=range(1, 11),
|
|
help="Number of threads: N or MIN-MAX (default: 1-10)")
|
|
args = parser.parse_args()
|
|
|
|
header = f"{'Threads': <10}{'Acq (kHz)': >12}{'Fairness': >10}"
|
|
if args.total_iters:
|
|
header += f"{'Wall (ms)': >12}"
|
|
print(header)
|
|
for num_threads in args.threads:
|
|
acquisitions, thread_iters, elapsed_ns = \
|
|
benchmark_locks(
|
|
num_threads, args.work_inside, args.work_outside,
|
|
1000, args.acquisitions, args.total_iters,
|
|
args.num_locks, args.random_locks)
|
|
|
|
wall_ms = elapsed_ns / 1e6
|
|
acquisitions /= 1000 # report in kHz for readability
|
|
fairness = jains_fairness(thread_iters)
|
|
|
|
line = f"{num_threads: <10}{acquisitions: >12.0f}{fairness: >10.2f}"
|
|
if args.total_iters:
|
|
line += f"{wall_ms: >12.1f}"
|
|
print(line)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|