S1005403_RisCC/tools/test_tid_counter_performance.py
2025-11-13 10:57:10 +01:00

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