| # 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() |