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