Skip to content

Commit 004d32d

Browse files
saurabh500Copilot
andcommitted
Add concurrent connection A/B benchmark script
Standalone benchmark that measures serial vs concurrent connection throughput with 10 threads. Used to verify GIL release performance: Before (GIL held): 0.99x speedup (fully serialized) After (GIL released): 5.74x speedup (parallel I/O) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 83995e4 commit 004d32d

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"""
2+
A/B benchmark: measure concurrent connection throughput before and after GIL release.
3+
4+
This script is meant to be run twice — once with the baseline .so (GIL held) and
5+
once with the new .so (GIL released during connect/disconnect). It prints timing
6+
results that can be compared.
7+
"""
8+
import os
9+
import sys
10+
import time
11+
import threading
12+
import statistics
13+
14+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
15+
import mssql_python
16+
from mssql_python import connect
17+
18+
CONN_STR = os.environ["DB_CONNECTION_STRING"]
19+
NUM_THREADS = 10
20+
WARMUP = 3
21+
SERIAL_ROUNDS = 5
22+
23+
24+
def connect_and_close():
25+
conn = connect(CONN_STR)
26+
conn.close()
27+
28+
29+
def benchmark_serial():
30+
"""Measure serial connect/close time."""
31+
times = []
32+
for _ in range(SERIAL_ROUNDS):
33+
start = time.perf_counter()
34+
connect_and_close()
35+
times.append(time.perf_counter() - start)
36+
return times
37+
38+
39+
def benchmark_concurrent(n_threads):
40+
"""Measure wall-clock time for n_threads concurrent connect/close."""
41+
barrier = threading.Barrier(n_threads)
42+
thread_times = [None] * n_threads
43+
errors = []
44+
45+
def worker(idx):
46+
try:
47+
barrier.wait(timeout=30)
48+
start = time.perf_counter()
49+
connect_and_close()
50+
thread_times[idx] = time.perf_counter() - start
51+
except Exception as e:
52+
errors.append((idx, str(e)))
53+
54+
threads = [threading.Thread(target=worker, args=(i,), daemon=True) for i in range(n_threads)]
55+
wall_start = time.perf_counter()
56+
for t in threads:
57+
t.start()
58+
for t in threads:
59+
t.join(timeout=120)
60+
wall_time = time.perf_counter() - wall_start
61+
return wall_time, thread_times, errors
62+
63+
64+
def main():
65+
mssql_python.pooling(enabled=False)
66+
67+
# Warm up
68+
print(f"Warming up ({WARMUP} rounds)...")
69+
for _ in range(WARMUP):
70+
connect_and_close()
71+
72+
# Serial baseline
73+
print(f"\n--- Serial Baseline ({SERIAL_ROUNDS} rounds) ---")
74+
serial_times = benchmark_serial()
75+
serial_median = statistics.median(serial_times)
76+
print(f" Individual: {[f'{t*1000:.1f}ms' for t in serial_times]}")
77+
print(f" Median: {serial_median*1000:.1f} ms")
78+
79+
# Concurrent
80+
print(f"\n--- Concurrent ({NUM_THREADS} threads) ---")
81+
results = []
82+
for trial in range(3):
83+
wall, thread_times, errors = benchmark_concurrent(NUM_THREADS)
84+
if errors:
85+
print(f" Trial {trial+1}: ERRORS {errors}")
86+
continue
87+
results.append(wall)
88+
print(f" Trial {trial+1}: wall={wall*1000:.1f}ms "
89+
f"per-thread=[{', '.join(f'{t*1000:.0f}' for t in thread_times if t)}]ms")
90+
91+
if not results:
92+
print("All trials failed!")
93+
return
94+
95+
concurrent_median = statistics.median(results)
96+
serial_estimate = NUM_THREADS * serial_median
97+
speedup = serial_estimate / concurrent_median
98+
99+
print(f"\n{'='*60}")
100+
print(f"RESULTS")
101+
print(f"{'='*60}")
102+
print(f" Serial median (1 conn): {serial_median*1000:.1f} ms")
103+
print(f" Serial estimate ({NUM_THREADS} × serial): {serial_estimate*1000:.1f} ms")
104+
print(f" Concurrent median ({NUM_THREADS} threads): {concurrent_median*1000:.1f} ms")
105+
print(f" Speedup: {speedup:.2f}x")
106+
print(f"{'='*60}")
107+
108+
if speedup > 2.0:
109+
print(f" ✅ GIL is released — {speedup:.1f}x parallel speedup")
110+
else:
111+
print(f" ❌ GIL appears to be held — only {speedup:.1f}x speedup (expected >2x)")
112+
113+
114+
if __name__ == "__main__":
115+
main()

0 commit comments

Comments
 (0)