136 lines
4.3 KiB
Python
136 lines
4.3 KiB
Python
"""Test performance comparison: Lock-based vs Lock-free TID counter."""
|
|
|
|
import threading
|
|
import time
|
|
import itertools
|
|
|
|
|
|
class OldTIDCounter:
|
|
"""OLD approach: Lock-based counter."""
|
|
|
|
def __init__(self):
|
|
self._tid_counter = 0
|
|
self._send_lock = threading.Lock()
|
|
|
|
def get_next_tid(self):
|
|
with self._send_lock:
|
|
self._tid_counter = (self._tid_counter + 1) % 256
|
|
return self._tid_counter
|
|
|
|
|
|
class NewTIDCounter:
|
|
"""NEW approach: Lock-free counter using itertools.count."""
|
|
|
|
def __init__(self):
|
|
self._tid_counter = itertools.count(start=0, step=1)
|
|
|
|
def get_next_tid(self):
|
|
# GIL guarantees atomicity of next() on itertools.count
|
|
return next(self._tid_counter) % 256
|
|
|
|
|
|
def benchmark_counter(counter, num_operations: int, num_threads: int = 1):
|
|
"""Benchmark counter with optional multi-threading."""
|
|
|
|
def worker(operations_per_thread):
|
|
for _ in range(operations_per_thread):
|
|
counter.get_next_tid()
|
|
|
|
operations_per_thread = num_operations // num_threads
|
|
|
|
start = time.perf_counter()
|
|
|
|
if num_threads == 1:
|
|
worker(num_operations)
|
|
else:
|
|
threads = []
|
|
for _ in range(num_threads):
|
|
t = threading.Thread(target=worker, args=(operations_per_thread,))
|
|
threads.append(t)
|
|
t.start()
|
|
|
|
for t in threads:
|
|
t.join()
|
|
|
|
elapsed = time.perf_counter() - start
|
|
return elapsed
|
|
|
|
|
|
def main():
|
|
print("=" * 70)
|
|
print("TID Counter Performance Comparison: Lock-based vs Lock-free")
|
|
print("=" * 70)
|
|
|
|
num_operations = 100_000
|
|
|
|
# Single-threaded test
|
|
print(f"\n{'Test: SINGLE-THREADED':<40}")
|
|
print(f"Operations: {num_operations:,}")
|
|
print("-" * 70)
|
|
|
|
old_counter = OldTIDCounter()
|
|
old_time = benchmark_counter(old_counter, num_operations, num_threads=1)
|
|
print(f"{'OLD (Lock-based)':<40} {old_time*1000:>8.2f} ms")
|
|
|
|
new_counter = NewTIDCounter()
|
|
new_time = benchmark_counter(new_counter, num_operations, num_threads=1)
|
|
print(f"{'NEW (Lock-free itertools.count)':<40} {new_time*1000:>8.2f} ms")
|
|
|
|
speedup = old_time / new_time
|
|
print(f"\n{'Speedup:':<40} {speedup:.2f}x faster")
|
|
print(
|
|
f"{'Time per operation (OLD):':<40} {old_time/num_operations*1_000_000:.3f} µs"
|
|
)
|
|
print(
|
|
f"{'Time per operation (NEW):':<40} {new_time/num_operations*1_000_000:.3f} µs"
|
|
)
|
|
|
|
# Multi-threaded test (simulating contention)
|
|
print(f"\n{'='*70}")
|
|
print(f"{'Test: MULTI-THREADED (4 threads)':<40}")
|
|
print(f"Operations: {num_operations:,}")
|
|
print("-" * 70)
|
|
|
|
old_counter = OldTIDCounter()
|
|
old_time_mt = benchmark_counter(old_counter, num_operations, num_threads=4)
|
|
print(f"{'OLD (Lock-based with contention)':<40} {old_time_mt*1000:>8.2f} ms")
|
|
|
|
new_counter = NewTIDCounter()
|
|
new_time_mt = benchmark_counter(new_counter, num_operations, num_threads=4)
|
|
print(f"{'NEW (Lock-free itertools.count)':<40} {new_time_mt*1000:>8.2f} ms")
|
|
|
|
speedup_mt = old_time_mt / new_time_mt
|
|
print(f"\n{'Speedup:':<40} {speedup_mt:.2f}x faster")
|
|
print(f"{'Lock contention overhead:':<40} {(old_time_mt/old_time - 1)*100:.1f}%")
|
|
|
|
# Real-world simulation
|
|
print(f"\n{'='*70}")
|
|
print("Real-world impact at 20Hz with 32 targets:")
|
|
print("-" * 70)
|
|
|
|
# JSON protocol: 1 TID per frame = 20 ops/sec
|
|
json_ops_per_sec = 20
|
|
json_overhead_old = (old_time / num_operations) * json_ops_per_sec * 1000
|
|
json_overhead_new = (new_time / num_operations) * json_ops_per_sec * 1000
|
|
|
|
print(f"JSON protocol (1 packet/frame @ 20Hz):")
|
|
print(f" OLD overhead: {json_overhead_old:.3f} ms/sec")
|
|
print(f" NEW overhead: {json_overhead_new:.3f} ms/sec")
|
|
print(f" Saved: {json_overhead_old - json_overhead_new:.3f} ms/sec")
|
|
|
|
# Legacy protocol: 32 TID per frame = 640 ops/sec
|
|
legacy_ops_per_sec = 32 * 20
|
|
legacy_overhead_old = (old_time / num_operations) * legacy_ops_per_sec * 1000
|
|
legacy_overhead_new = (new_time / num_operations) * legacy_ops_per_sec * 1000
|
|
|
|
print(f"\nLegacy protocol (32 packets/frame @ 20Hz):")
|
|
print(f" OLD overhead: {legacy_overhead_old:.3f} ms/sec")
|
|
print(f" NEW overhead: {legacy_overhead_new:.3f} ms/sec")
|
|
print(f" Saved: {legacy_overhead_old - legacy_overhead_new:.3f} ms/sec")
|
|
|
|
print(f"{'='*70}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|