Skip to content

Instantly share code, notes, and snippets.

@x42005e1f
Last active April 4, 2025 12:27
Show Gist options
  • Save x42005e1f/149d3994d5f7bd878def71d5404e6ea4 to your computer and use it in GitHub Desktop.
Save x42005e1f/149d3994d5f7bd878def71d5404e6ea4 to your computer and use it in GitHub Desktop.
One of aiologic's most impressive benchmarks
#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2024 Ilya Egorov <[email protected]>
# SPDX-License-Identifier: 0BSD
import sys
import time
import threading
from concurrent.futures import ThreadPoolExecutor, wait
import aiologic
def main():
value, threads = 1, 1
while threads != 17711:
print(f"threads = {threads}, value = {value}:")
ops = []
for cls in (aiologic.Semaphore, threading.Semaphore):
name = f"{cls.__module__.partition('.')[0]}.{cls.__name__}"
sem = cls(value)
num = [0] * threads
start = 0
stop = 0
barrier = aiologic.Latch(threads + 1)
def func(i):
barrier.wait()
n = 0
while not stop:
with sem:
n += 1
num[i] = n
with ThreadPoolExecutor(threads) as executor:
futures = [executor.submit(func, i) for i in range(threads)]
barrier.wait() # ensure that no threads are sleeping
start = time.monotonic()
try:
time.sleep(6)
finally:
stop = time.monotonic()
wait(futures)
total_num = sum(num)
average_num = total_num / len(num)
count = total_num / (stop - start)
if threads == 1:
fairness = 1
else:
fairness = 1 - (
sum(abs(average_num - i) for i in num)
/ (2 * (total_num - average_num))
)
print(end=f" {name + ':': <20} {count: >10.0f} ops")
print(end=f" {fairness * 100: >5.2f}% fairness")
if unused := num.count(0):
if unused == 1:
print(end=" 1 unused thread")
else:
print(end=f" {unused} unused threads")
print()
ops.append(count)
if abs(ops[0] - ops[1]) > 0.05:
print(" ")
if ops[0] >= ops[1]:
print(f" {ops[0] / ops[1]:.1f}x speedup!")
else:
print(f" {ops[1] / ops[0]:.1f}x slowdown!")
print()
value, threads = threads, value + threads
if __name__ == "__main__":
sys.exit(main())
@x42005e1f
Copy link
Author

This is a performance benchmark that compares aiologic.Semaphore and threading.Semaphore in terms of operations per second. It considers the case of value ≈ threads / 2, because this is the case where aiologic.Semaphore performs the best and threading.Semaphore performs the worst when running on PyPy:

threads = 1, value = 1:
    aiologic.Semaphore:   943246964 ops 100.00% fairness
    threading.Semaphore:    8507624 ops 100.00% fairness
    
    110.9x speedup!

threads = 2, value = 1:
    aiologic.Semaphore:   581026516 ops 99.99% fairness
    threading.Semaphore:    7664169 ops 99.87% fairness
    
    75.8x speedup!

threads = 3, value = 2:
    aiologic.Semaphore:   522027692 ops 99.97% fairness
    threading.Semaphore:      15161 ops 84.71% fairness
    
    34431.2x speedup!

threads = 5, value = 3:
    aiologic.Semaphore:   518826453 ops 99.89% fairness
    threading.Semaphore:       9075 ops 71.92% fairness
    
    57173.9x speedup!

...

threads = 233, value = 144:
    aiologic.Semaphore:   521016536 ops 99.24% fairness
    threading.Semaphore:       4872 ops 63.53% fairness
    
    106944.9x speedup!

threads = 377, value = 233:
    aiologic.Semaphore:   522805870 ops 99.04% fairness
    threading.Semaphore:       3567 ops 80.30% fairness
    
    146564.5x speedup!

...

Fun fact: even 15161 ops is slower than pinging localhost!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment