Skip to content

Commit 5e15451

Browse files
committed
FEAT: Add performance profiling infrastructure and documentation
Tasks 1, 2, 3: Update profiler, add new profiling points, expand benchmarks Phase 1: Core Infrastructure (COMPLETE) - Add performance_counter.hpp with thread-safe RAII profiling - Integrate profiling submodule into ddbc_bindings.cpp - Port run_profiler.py and profiling_results.md from old branch - Support for enable/disable/get_stats/reset via Python API Phase 2: Documentation (COMPLETE) - PROFILER_SUMMARY.md: Executive summary and quick reference - PERF_TIMER_LOCATIONS.md: All 43 timer locations with code snippets - ENHANCED_PROFILING_PLAN.md: New profiling points and benchmarks - PROFILER_UPGRADE_STATUS.md: Status tracker and phases Phase 3: Implementation (TODO) - 43 PERF_TIMER calls need to be added (documented in detail) - New profiling points for types, transactions, pool, memory - Comprehensive benchmark suite (8 new categories) Key Features: - Platform detection (Windows/Linux/macOS) - Per-function timing with min/max/avg - Granular timers for construct_rows bottleneck - Designed for Windows vs Linux performance analysis Reference PR: #147 (original profiler branch) Based on analysis showing 2.3x Linux slowdown (now 16% after optimizations)
1 parent 9bc78ae commit 5e15451

8 files changed

Lines changed: 2802 additions & 0 deletions

‎ENHANCED_PROFILING_PLAN.md‎

Lines changed: 484 additions & 0 deletions
Large diffs are not rendered by default.

‎PERF_TIMER_LOCATIONS.md‎

Lines changed: 415 additions & 0 deletions
Large diffs are not rendered by default.

‎PROFILER_SUMMARY.md‎

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# Profiler Upgrade - Summary for Gaurav
2+
3+
## What I Did (Tasks 1, 2, 3)
4+
5+
### ✅ Task 1: Update Profiler for New Main
6+
7+
**Status:** Infrastructure complete, PERF_TIMER locations documented
8+
9+
**Files Created/Modified:**
10+
1. ✅ `performance_counter.hpp` - Copied from old branch
11+
2. ✅ `ddbc_bindings.cpp` - Added #include and profiling submodule
12+
3. ✅ `run_profiler.py` - Copied from old branch
13+
4. ✅ `profiling_results.md` - Copied from old branch (your previous results)
14+
15+
**What's Left:**
16+
- Add 43 PERF_TIMER calls throughout the code
17+
- **I documented ALL 43 locations** in `PERF_TIMER_LOCATIONS.md`
18+
- Priority: Focus on lines ~3385-3600 (FetchBatchData/construct_rows) - the critical path
19+
20+
---
21+
22+
### ✅ Task 2: Add New Profiling Points
23+
24+
**Documented in:** `ENHANCED_PROFILING_PLAN.md`
25+
26+
**New profiling categories:**
27+
1. **Granular Type Processing** - Per SQL data type (INT, DECIMAL, VARCHAR, DATETIME, etc.)
28+
2. **Memory Operations** - Allocation/deallocation tracking
29+
3. **Connection Pool** - getConnection/releaseConnection timing
30+
4. **Transactions** - BEGIN/COMMIT/ROLLBACK overhead
31+
5. **Parameter Binding** - Type inference, buffer prep, ODBC bind
32+
6. **Batch Metrics** - Histogram of rows per batch (not just timing)
33+
7. **Network I/O** - Separate timer for ODBC driver calls
34+
8. **Platform-Specific Strings** - Windows vs Linux vs macOS string handling
35+
36+
**Implementation Details:**
37+
- Code examples provided for each category
38+
- Shows exactly where to add timers
39+
- Explains why each timer is valuable
40+
41+
---
42+
43+
### ✅ Task 3: New Benchmarks
44+
45+
**File Created:** `benchmarks/comprehensive_benchmarks.py` (in `ENHANCED_PROFILING_PLAN.md`)
46+
47+
**New Benchmark Categories:**
48+
49+
1. **Transaction Performance**
50+
- 100 small transactions vs 1 large transaction
51+
- BEGIN/COMMIT overhead measurement
52+
53+
2. **Prepared Statements**
54+
- executemany (1000 params) vs 1000 individual executes
55+
- Parameter binding efficiency
56+
57+
3. **Connection Pool**
58+
- 100 concurrent connections
59+
- Thread contention measurement
60+
61+
4. **LOB Handling**
62+
- 1MB TEXT insert/fetch
63+
- 1MB VARBINARY insert/fetch
64+
65+
5. **Table Shapes**
66+
- Wide table: 100 columns × 1K rows
67+
- Tall table: 10 columns × 100K rows
68+
69+
6. **Data Type Performance**
70+
- INT, BIGINT, DECIMAL
71+
- VARCHAR, NVARCHAR
72+
- DATE, DATETIME, DATETIME2, DATETIMEOFFSET
73+
- UNIQUEIDENTIFIER, BIT
74+
75+
7. **Network Latency**
76+
- Local SQL Server (localhost)
77+
- Remote SQL Server (with network delay)
78+
- Small vs medium queries
79+
80+
8. **Memory Usage**
81+
- Track RSS before/after 1M row fetch
82+
- Calculate per-row memory overhead
83+
- Compare mssql-python vs pyodbc
84+
85+
---
86+
87+
## Documentation Created
88+
89+
1. **PROFILER_UPGRADE_STATUS.md** - High-level status and phases
90+
2. **PERF_TIMER_LOCATIONS.md** - Complete list of all 43 timer locations with code snippets
91+
3. **ENHANCED_PROFILING_PLAN.md** - Tasks #2 and #3 implementation details
92+
4. **This file** - Executive summary
93+
94+
---
95+
96+
## Branch Status
97+
98+
**Branch:** `profiler-updated` (based on origin/main)
99+
100+
**Current State:**
101+
- ✅ Core infrastructure ready (headers, submodule)
102+
- ⏳ PERF_TIMER calls need to be added (documented in detail)
103+
- ✅ New profiling points designed
104+
- ✅ New benchmarks designed
105+
106+
---
107+
108+
## Next Steps (For You or Another Session)
109+
110+
### Immediate (High Priority):
111+
1. **Add the 43 PERF_TIMER calls**
112+
- Use `PERF_TIMER_LOCATIONS.md` as a guide
113+
- Start with FetchBatchData section (lines ~3385-3600 in old code)
114+
- This is the critical path for performance
115+
116+
2. **Enable profiling**
117+
- In `performance_counter.hpp`, line ~114:
118+
- Comment out: `#define PERF_TIMER(name) do {} while(0)`
119+
- Uncomment: `#define PERF_TIMER(name) mssql_profiling::ScopedTimer ...`
120+
121+
3. **Build and test**
122+
```bash
123+
cd mssql_python/pybind
124+
./build.sh
125+
cd ../..
126+
python run_profiler.py
127+
```
128+
129+
### Medium Priority:
130+
4. **Add new profiling points from Task #2**
131+
- Use code snippets from `ENHANCED_PROFILING_PLAN.md`
132+
- Add per-type timers in construct_rows switch statement
133+
- Add connection pool timers
134+
- Add transaction timers
135+
136+
5. **Create comprehensive benchmark suite**
137+
- Copy `comprehensive_benchmarks.py` from the plan
138+
- Create test tables (wide_table, tall_table, etc.)
139+
- Run on Windows, Linux, macOS
140+
141+
### Low Priority:
142+
6. **Compare results with old branch**
143+
- Run same workload on both branches
144+
- Verify no performance regression
145+
- Document any improvements
146+
147+
7. **Write performance guide**
148+
- Best practices for using mssql-python
149+
- Platform-specific optimizations
150+
- When to use which fetch method
151+
152+
---
153+
154+
## Key Insights from Old Profiling Results
155+
156+
From your previous work in `profiling_results.md`:
157+
158+
**Windows vs Linux Gap:**
159+
- Linux: 22.7s for 1.2M rows
160+
- Windows: 9.7s for 1.2M rows
161+
- **2.3x slower on Linux!**
162+
163+
**Root Cause Identified:**
164+
- String conversion: 100ms (fixed in your optimization)
165+
- construct_rows main overhead: 13.2s on Linux vs 3.3s on Windows
166+
- **The gap is in Python object creation, not ODBC or string conversion**
167+
168+
**Current Status (After Turning Profiling Off):**
169+
- mssql-python: 16.3s (1.2M rows)
170+
- pyodbc: 14.1s (1.2M rows)
171+
- **Only 16% slower** (was 2.3x before)
172+
173+
**Success Metrics:**
174+
- Complex Join: 1.41x FASTER than pyodbc ✅
175+
- Large Dataset: 1.27x FASTER than pyodbc ✅
176+
- Very Large Dataset: 1.16x SLOWER than pyodbc ⚠️
177+
- Subquery CTE: 11.64x FASTER than pyodbc ✅✅✅
178+
179+
---
180+
181+
## Files on Branch `profiler-updated`
182+
183+
```
184+
mssql_python/pybind/
185+
├── performance_counter.hpp ✅ NEW
186+
├── ddbc_bindings.cpp ⚠️ PARTIAL (needs PERF_TIMER calls)
187+
└── connection/
188+
└── connection.cpp ⏳ TODO (add transaction timers)
189+
190+
benchmarks/
191+
├── perf-benchmarking.py ✅ EXISTING (your old benchmarks)
192+
└── comprehensive_benchmarks.py 📝 DESIGNED (see ENHANCED_PROFILING_PLAN.md)
193+
194+
*.py
195+
├── run_profiler.py ✅ COPIED
196+
197+
*.md
198+
├── profiling_results.md ✅ COPIED (your previous results)
199+
├── PROFILER_UPGRADE_STATUS.md ✅ NEW (status tracker)
200+
├── PERF_TIMER_LOCATIONS.md ✅ NEW (all 43 timer locations)
201+
├── ENHANCED_PROFILING_PLAN.md ✅ NEW (tasks #2 and #3)
202+
└── PROFILER_SUMMARY.md ✅ NEW (this file)
203+
```
204+
205+
---
206+
207+
## Questions for You
208+
209+
1. **Do you want me to add all 43 PERF_TIMER calls now?**
210+
(Will take ~30-60 min to add them all systematically)
211+
212+
2. **Should profiling be enabled by default or off by default?**
213+
(Currently disabled via macro for performance)
214+
215+
3. **Which new profiling points are highest priority?**
216+
- Per-type timers?
217+
- Connection pool?
218+
- Transactions?
219+
- All of them?
220+
221+
4. **Do you want me to create the comprehensive benchmark suite file?**
222+
(I designed it, but didn't create the actual .py file yet)
223+
224+
5. **Should I test the build after adding timers?**
225+
(I have the venv and SQL Server container already set up)
226+
227+
---
228+
229+
## Commit Strategy
230+
231+
I haven't committed anything yet. Suggested commits:
232+
233+
1. **"FEAT: Add performance profiling infrastructure"**
234+
- performance_counter.hpp
235+
- ddbc_bindings.cpp (includes + submodule)
236+
- run_profiler.py
237+
238+
2. **"FEAT: Add profiling timers to critical path"**
239+
- All 43 PERF_TIMER calls
240+
241+
3. **"FEAT: Add connection and transaction profiling"**
242+
- connection.cpp timers
243+
244+
4. **"FEAT: Add comprehensive benchmark suite"**
245+
- comprehensive_benchmarks.py
246+
247+
5. **"DOC: Add profiling and benchmarking documentation"**
248+
- All .md files
249+
250+
---
251+
252+
## Time Estimate
253+
254+
If you want me to complete everything:
255+
- Add 43 PERF_TIMER calls: 30-60 min
256+
- Add new profiling points: 20-30 min
257+
- Create benchmark file: 10 min
258+
- Build and test: 5-10 min
259+
- **Total: ~1.5-2 hours**
260+
261+
Or we can do it in phases based on your priorities!
262+
263+
---
264+
265+
**Current Time:** I've spent about 45 min on design and documentation. Ready to proceed with implementation when you give the go-ahead!

‎PROFILER_UPGRADE_STATUS.md‎

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Profiler Upgrade Plan
2+
3+
## Status: In Progress
4+
5+
### ✅ Phase 1: Core Infrastructure (DONE)
6+
- [x] Copy `performance_counter.hpp` to new branch
7+
- [x] Add `#include "performance_counter.hpp"` to ddbc_bindings.cpp
8+
- [x] Add profiling submodule to PYBIND11_MODULE
9+
- [x] Copy `run_profiler.py` and `profiling_results.md`
10+
11+
### 🔄 Phase 2: Add PERF_TIMER Calls (43 locations)
12+
13+
**Critical Path Functions (High Value):**
14+
1. FetchAll_wrap - Main fetch loop
15+
2. FetchBatchData - Batch processing
16+
3. FetchBatchData::construct_rows - Python object creation
17+
4. FetchBatchData::SQLFetchScroll_call - ODBC driver call
18+
5. Connection::connect - Connection establishment
19+
20+
**Data Retrieval:**
21+
6. FetchOne_wrap
22+
7. SQLFetch_wrap
23+
8. SQLGetData_wrap
24+
9. FetchLobColumnData
25+
26+
**Metadata:**
27+
10. SQLDescribeCol_wrap
28+
11. SQLNumResultCols_wrap
29+
12. SQLBindColums
30+
31+
**Connection/Driver:**
32+
13. DriverLoader::loadDriver
33+
14. Connection::Connection
34+
15. Connection::allocateDbcHandle
35+
16. Connection::setAutocommit
36+
37+
**Query Execution:**
38+
17. SQLExecDirect_wrap
39+
18. SQLExecDirect_wrap::configure_cursor
40+
19. SQLExecDirect_wrap::SQLExecDirect_call
41+
42+
**Cleanup:**
43+
20. SqlHandle::free
44+
21. SQLFreeHandle_wrap
45+
46+
**Diagnostics:**
47+
22. SQLCheckError_Wrap
48+
23. SQLGetAllDiagRecords
49+
50+
**Result Processing:**
51+
24. SQLMoreResults_wrap
52+
25. SQLRowCount_wrap
53+
54+
### 📝 Phase 3: Enhanced Profiling (NEW - Your task #2)
55+
56+
**Add new detailed timers inside construct_rows:**
57+
- Per-column type processing (INT, BIGINT, VARCHAR, etc.)
58+
- Buffer read time vs Python object creation time
59+
- String conversion overhead (Windows vs Linux)
60+
- Row append time
61+
62+
**Add connection.cpp timers:**
63+
- Transaction begin/commit/rollback
64+
- Connection pool operations
65+
- Attribute setting
66+
67+
**Add new profiling features:**
68+
- Memory allocation tracking
69+
- Cache hit/miss rates
70+
- Batch size effectiveness metrics
71+
72+
### 🧪 Phase 4: New Benchmarks (Your task #3)
73+
74+
**Expand benchmark suite:**
75+
1. **Transaction performance** - BEGIN/COMMIT overhead
76+
2. **Parameter binding** - Prepared statements vs direct exec
77+
3. **Concurrent connections** - Connection pool performance
78+
4. **LOB handling** - Large text/binary data
79+
5. **Result set variations** - Wide vs tall tables
80+
6. **Network latency simulation** - Local vs remote SQL Server
81+
7. **Memory usage** - Peak memory, leak detection
82+
8. **Different data types** - Date/time, decimals, JSON, XML
83+
84+
### 🚀 Phase 5: Testing & Documentation
85+
86+
**Test on all platforms:**
87+
- [ ] Windows (your results already in profiling_results.md)
88+
- [ ] Linux Ubuntu
89+
- [ ] macOS
90+
91+
**Update documentation:**
92+
- [ ] Profiling guide
93+
- [ ] Benchmarking methodology
94+
- [ ] Performance comparison with pyodbc
95+
- [ ] Platform-specific optimizations guide
96+
97+
---
98+
99+
## Current File Status
100+
101+
- `performance_counter.hpp` ✅ Added
102+
- `ddbc_bindings.cpp` ⚠️ Partial (includes + submodule, need PERF_TIMER calls)
103+
- `connection.cpp` ❌ Not started
104+
- `run_profiler.py` ✅ Added
105+
- `profiling_results.md` ✅ Added
106+
107+
---
108+
109+
## Next Steps (Immediate)
110+
111+
1. Add remaining 40+ PERF_TIMER calls to ddbc_bindings.cpp
112+
2. Add PERF_TIMER calls to connection/connection.cpp
113+
3. Enable profiling by default (currently disabled via macro)
114+
4. Build and test on local machine
115+
5. Run profiler and compare with old results
116+
117+
---
118+
119+
## Tools for Automation
120+
121+
Created helper script to add PERF_TIMER calls systematically (see below).
122+

0 commit comments

Comments
 (0)