taurus 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +518 -0
- data/CLAUDE.md +104 -0
- data/LICENSE.md +33 -0
- data/README.adoc +1529 -0
- data/Rakefile +7 -0
- data/TODO.impl/01-architecture.md +217 -0
- data/TODO.impl/02-ffi-declarations.md +236 -0
- data/TODO.impl/03-document-node-element-nodeset.md +382 -0
- data/TODO.impl/04-sax-parser.md +203 -0
- data/TODO.impl/05-serialize-c14n-memory-specs-css.md +276 -0
- data/benchmark/README.md +168 -0
- data/benchmark/taurus_vs_nokogiri.rb +105 -0
- data/docs/ARCHITECTURE.adoc +559 -0
- data/docs/BUILD.md +395 -0
- data/docs/ERROR_MESSAGES.md +458 -0
- data/docs/FFI_ARCHITECTURE.md +439 -0
- data/docs/FUTURE_VISION.md +303 -0
- data/docs/GITHUB_ACTIONS.md +293 -0
- data/docs/OPTIMIZATIONS_IMPLEMENTED.adoc +459 -0
- data/docs/PERFORMANCE.adoc +668 -0
- data/docs/PERFORMANCE.md +448 -0
- data/docs/RELEASE_NOTES_v1.0.0.md +515 -0
- data/docs/XPATH_SPEC_COMPLIANCE.md +298 -0
- data/docs/completion/taurus.bash +86 -0
- data/docs/completion/taurus.zsh +74 -0
- data/docs/man/taurus-format.1 +227 -0
- data/docs/man/taurus-parse.1 +178 -0
- data/docs/man/taurus-xpath.1 +312 -0
- data/docs/man/taurus.1 +160 -0
- data/docs/v0.9.0_PERFORMANCE_IMPROVEMENTS.md +217 -0
- data/docs/v0.9.0_RELEASE_SUMMARY.md +281 -0
- data/docs/v1.0.0_CONTINUATION_PLAN.md +172 -0
- data/docs/v1.0.0_CONTINUATION_PROMPT.md +382 -0
- data/docs/v1.0.0_SESSION_6_CONTINUATION.md +434 -0
- data/docs/v1.0.0_SESSION_6_PROMPT.md +231 -0
- data/docs/v1.0.0_STATUS_TRACKER.md +224 -0
- data/docs/v1.1.0_CONTINUATION_PLAN.md +299 -0
- data/docs/v1.1.0_FINAL_CONTINUATION_PLAN.md +201 -0
- data/docs/v1.1.0_SESSION_3_PROMPT.md +223 -0
- data/docs/v1.1.0_STATUS_TRACKER.md +355 -0
- data/docs/xml-performance.adoc +115 -0
- data/docs/xpath-performance.adoc +379 -0
- data/lib/taurus/version.rb +5 -0
- data/lib/taurus/xml/attr.rb +43 -0
- data/lib/taurus/xml/c14n.rb +23 -0
- data/lib/taurus/xml/cdata.rb +16 -0
- data/lib/taurus/xml/comment.rb +16 -0
- data/lib/taurus/xml/css_to_xpath.rb +177 -0
- data/lib/taurus/xml/doc_type.rb +54 -0
- data/lib/taurus/xml/document.rb +202 -0
- data/lib/taurus/xml/document_fragment.rb +42 -0
- data/lib/taurus/xml/element.rb +278 -0
- data/lib/taurus/xml/ffi.rb +420 -0
- data/lib/taurus/xml/namespace.rb +43 -0
- data/lib/taurus/xml/node.rb +221 -0
- data/lib/taurus/xml/node_set.rb +143 -0
- data/lib/taurus/xml/parse_options.rb +19 -0
- data/lib/taurus/xml/processing_instruction.rb +26 -0
- data/lib/taurus/xml/sax/document.rb +45 -0
- data/lib/taurus/xml/sax/parser.rb +148 -0
- data/lib/taurus/xml/sax.rb +12 -0
- data/lib/taurus/xml/searchable.rb +93 -0
- data/lib/taurus/xml/text.rb +16 -0
- data/lib/taurus/xml.rb +29 -0
- data/lib/taurus.rb +7 -0
- data/taurus.gemspec +42 -0
- metadata +157 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
= Taurus Optimizations Implemented
|
|
2
|
+
:toc:
|
|
3
|
+
:toclevels: 3
|
|
4
|
+
|
|
5
|
+
== Overview
|
|
6
|
+
|
|
7
|
+
This document details all optimizations implemented in Taurus v0.1.0, including both successful techniques and failed attempts that provided valuable learning.
|
|
8
|
+
|
|
9
|
+
== Successful Optimizations
|
|
10
|
+
|
|
11
|
+
=== 1. Zero-Copy Parsing (Session 34)
|
|
12
|
+
|
|
13
|
+
**Problem**: Original implementation copied XML strings multiple times during parsing.
|
|
14
|
+
|
|
15
|
+
**Solution**: In-place buffer termination
|
|
16
|
+
|
|
17
|
+
**Implementation**:
|
|
18
|
+
- Terminate strings in place using null bytes
|
|
19
|
+
- Pass pointers to existing buffer locations
|
|
20
|
+
- Eliminate unnecessary string allocations
|
|
21
|
+
|
|
22
|
+
**Impact**: 6.3× speedup (9.2ms → 1.46ms)
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
=== 2. Callback Architecture (Session 35)
|
|
27
|
+
|
|
28
|
+
**Problem**: Creating Ruby objects for every parsed element was expensive.
|
|
29
|
+
|
|
30
|
+
**Solution**: Streamlined parsing pipeline with reduced object creation
|
|
31
|
+
|
|
32
|
+
**Implementation**:
|
|
33
|
+
- Minimize intermediate Ruby object allocations
|
|
34
|
+
- Direct conversion from C structures to final Ruby objects
|
|
35
|
+
- Batch operations where possible
|
|
36
|
+
|
|
37
|
+
**Impact**: Additional 33× speedup (1.46ms → 0.044ms)
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
=== 3. SIMD Vectorization (Session 48) ⭐
|
|
42
|
+
|
|
43
|
+
**Problem**: Character-by-character processing was slow.
|
|
44
|
+
|
|
45
|
+
**Solution**: Leverage CPU vector instructions for parallel processing
|
|
46
|
+
|
|
47
|
+
**Implementation**:
|
|
48
|
+
- ARM NEON implementation for Apple Silicon
|
|
49
|
+
- x86 SSE2 implementation for Intel/AMD
|
|
50
|
+
- Scalar fallback for other platforms
|
|
51
|
+
- Platform detection at compile time (zero runtime overhead)
|
|
52
|
+
|
|
53
|
+
**Optimized Operations**:
|
|
54
|
+
- Whitespace detection and skipping
|
|
55
|
+
- Character classification (name chars, special chars)
|
|
56
|
+
- Name parsing (element/attribute names)
|
|
57
|
+
- Namespace prefix detection
|
|
58
|
+
|
|
59
|
+
**Code Structure**:
|
|
60
|
+
```c
|
|
61
|
+
// SIMD helpers with platform detection
|
|
62
|
+
#if defined(__ARM_NEON)
|
|
63
|
+
// ARM NEON implementation
|
|
64
|
+
#elif defined(__SSE2__)
|
|
65
|
+
// x86 SSE2 implementation
|
|
66
|
+
#else
|
|
67
|
+
// Scalar fallback
|
|
68
|
+
#endif
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Impact**: 300% speedup (24.2µs → 6.0µs)
|
|
72
|
+
|
|
73
|
+
**Files Modified**:
|
|
74
|
+
- Added `simd_helpers.h` with platform-specific implementations
|
|
75
|
+
- Modified `parse.c` to use SIMD functions
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
=== 4. Character Classification Table (Session 58) ⭐
|
|
80
|
+
|
|
81
|
+
**Problem**: Branch-based character classification caused CPU pipeline stalls.
|
|
82
|
+
|
|
83
|
+
**Solution**: 256-byte lookup table (inspired by pugixml)
|
|
84
|
+
|
|
85
|
+
**Implementation**:
|
|
86
|
+
- Pre-computed 256-entry table for all possible byte values
|
|
87
|
+
- Each entry contains bitmask of character properties
|
|
88
|
+
- Zero branch mispredictions
|
|
89
|
+
- Perfect L1 cache locality (256 bytes)
|
|
90
|
+
|
|
91
|
+
**Additional Optimizations**:
|
|
92
|
+
- Branch probability ordering (common cases first)
|
|
93
|
+
- Symbol cache fast-path for repeated symbols
|
|
94
|
+
|
|
95
|
+
**Code Example**:
|
|
96
|
+
```c
|
|
97
|
+
// Character classification table
|
|
98
|
+
static const uint8_t char_table[256] = {
|
|
99
|
+
// Precomputed bitmasks for each character
|
|
100
|
+
[0...255] = computed_properties
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Zero-branch classification
|
|
104
|
+
bool is_name_char = (char_table[(uint8_t)c] & NAME_CHAR_MASK) != 0;
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Impact**: 78% speedup (6.0µs → 5.87µs)
|
|
108
|
+
|
|
109
|
+
**Files Modified**:
|
|
110
|
+
- Modified `parse.c` with character table
|
|
111
|
+
- Reordered branches by probability
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
=== 5. AST Pattern Optimization (Session 66) ⭐
|
|
116
|
+
|
|
117
|
+
**Problem**: Common XPath patterns like `//foo` were inefficient.
|
|
118
|
+
|
|
119
|
+
**Solution**: Rewrite AST patterns before evaluation
|
|
120
|
+
|
|
121
|
+
**Implementation**:
|
|
122
|
+
- Pattern detection during parsing
|
|
123
|
+
- AST node rewriting for efficiency
|
|
124
|
+
- Semantic equivalence maintained
|
|
125
|
+
|
|
126
|
+
**Optimizations Applied**:
|
|
127
|
+
- `//foo` → `/descendant::foo` (eliminates redundant step)
|
|
128
|
+
- `/descendant-or-self::node()/child::foo` → `/descendant::foo`
|
|
129
|
+
|
|
130
|
+
**Impact**: 8-10× speedup (~900µs → 95.05µs)
|
|
131
|
+
|
|
132
|
+
**Files Added**:
|
|
133
|
+
- `xpath_optimizer.c` - Pattern detection and rewriting
|
|
134
|
+
- `xpath_optimizer.h` - Optimization API
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
=== 6. AST Caching (Session 67) ⭐⭐
|
|
139
|
+
|
|
140
|
+
**Problem**: Every XPath query was parsed from scratch, wasting 76µs per query.
|
|
141
|
+
|
|
142
|
+
**Discovery**: Profiling revealed repeated parsing overhead:
|
|
143
|
+
- 10,000 queries = 10,000 parser instances
|
|
144
|
+
- 10,000 tokenization passes
|
|
145
|
+
- 10,000 AST constructions
|
|
146
|
+
- 10,000 optimization passes
|
|
147
|
+
|
|
148
|
+
**Solution**: Global AST cache with hash-based lookup
|
|
149
|
+
|
|
150
|
+
**Implementation**:
|
|
151
|
+
```c
|
|
152
|
+
typedef struct {
|
|
153
|
+
XPathASTCacheEntry** buckets; // 64 buckets
|
|
154
|
+
size_t entry_count; // Current entries
|
|
155
|
+
size_t max_entries; // 256 max
|
|
156
|
+
} XPathASTCache;
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Features**:
|
|
160
|
+
- Hash table with 64 buckets
|
|
161
|
+
- O(1) cache lookup
|
|
162
|
+
- 256 entry limit (~154KB max memory)
|
|
163
|
+
- Stores already-optimized ASTs
|
|
164
|
+
- LRU-like eviction when full
|
|
165
|
+
|
|
166
|
+
**Cache Flow**:
|
|
167
|
+
```
|
|
168
|
+
Query → Hash → Bucket → Cache Hit? → Yes: Use cached AST
|
|
169
|
+
→ No: Parse → Optimize → Cache → Use AST
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**Impact**: 420% speedup (95.05µs → 9.00µs), competitive with Nokogiri (2.3× slower)
|
|
173
|
+
|
|
174
|
+
**Files Added**:
|
|
175
|
+
- `xpath_ast_cache.c` (173 lines) - Cache implementation
|
|
176
|
+
- `xpath_ast_cache.h` (41 lines) - Cache API
|
|
177
|
+
|
|
178
|
+
**Memory Impact**:
|
|
179
|
+
- Per entry: ~600 bytes (40 bytes struct + expression + AST)
|
|
180
|
+
- Maximum: ~154KB (256 entries)
|
|
181
|
+
- Actual: Varies with unique query count
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
== Failed Optimizations (Learning Experiences)
|
|
186
|
+
|
|
187
|
+
=== 1. Object Pooling (Session 59) ❌
|
|
188
|
+
|
|
189
|
+
**Hypothesis**: Pooling XPathResult objects would reduce allocation overhead.
|
|
190
|
+
|
|
191
|
+
**Implementation**:
|
|
192
|
+
- Created object pool for frequently allocated structures
|
|
193
|
+
- Reused objects instead of allocating new ones
|
|
194
|
+
- Added pool management overhead
|
|
195
|
+
|
|
196
|
+
**Result**: 6% SLOWER (5.87µs → 6.2µs)
|
|
197
|
+
|
|
198
|
+
**Why It Failed**:
|
|
199
|
+
- Ruby's generational GC already optimizes short-lived objects
|
|
200
|
+
- GC is highly tuned for allocation/deallocation patterns
|
|
201
|
+
- Pool management overhead exceeded GC overhead
|
|
202
|
+
- Modern GC outperforms manual memory management
|
|
203
|
+
|
|
204
|
+
**Lesson**: Don't fight the garbage collector - it's smarter than you think.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
=== 2. Code Locality Hints (Session 60) ❌
|
|
209
|
+
|
|
210
|
+
**Hypothesis**: Manual instruction cache hints would improve performance.
|
|
211
|
+
|
|
212
|
+
**Implementation**:
|
|
213
|
+
- Applied `__attribute__((noinline))` to cold functions
|
|
214
|
+
- Attempted to group hot code paths together
|
|
215
|
+
- Added compiler hints for branch prediction
|
|
216
|
+
|
|
217
|
+
**Result**: 0.9% SLOWER (5.87µs → 5.93µs)
|
|
218
|
+
|
|
219
|
+
**Why It Failed**:
|
|
220
|
+
- Clang already performs sophisticated instruction cache optimization
|
|
221
|
+
- Modern compilers have better analysis than manual hints
|
|
222
|
+
- Manual hints interfered with compiler's holistic view
|
|
223
|
+
- Profile-guided optimization (PGO) already built into compiler
|
|
224
|
+
|
|
225
|
+
**Lesson**: Trust modern compilers - they know their job better than manual hints.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
=== 3. Document Order Stamping (Session 63) ❌
|
|
230
|
+
|
|
231
|
+
**Hypothesis**: Caching document order in Ruby ivars would speed up sorting.
|
|
232
|
+
|
|
233
|
+
**Implementation**:
|
|
234
|
+
- Stamped `@doc_order` ivar on each element during traversal
|
|
235
|
+
- Used cached order for O(1) comparison
|
|
236
|
+
- Avoided sorting already-ordered results
|
|
237
|
+
|
|
238
|
+
**Result**: 2-11% SLOWER
|
|
239
|
+
|
|
240
|
+
**Why It Failed**:
|
|
241
|
+
- `rb_ivar_get()` has ~50-100ns overhead vs C struct field (~1ns)
|
|
242
|
+
- Ruby ivar access is not free
|
|
243
|
+
- Depth-first traversal already maintains document order
|
|
244
|
+
- Sorting sorted data adds unnecessary cost
|
|
245
|
+
|
|
246
|
+
**Lesson**: Ruby ivar overhead is significant - prefer C struct fields for hot paths.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
=== 4. Axis-Level Early Exit (Session 64) ⚠️
|
|
251
|
+
|
|
252
|
+
**Hypothesis**: Stop axis traversal on first match for boolean queries.
|
|
253
|
+
|
|
254
|
+
**Implementation**:
|
|
255
|
+
- Added early exit flags to axis functions
|
|
256
|
+
- Stopped traversal when first node found
|
|
257
|
+
- Infrastructure complete and tested
|
|
258
|
+
|
|
259
|
+
**Result**: DISABLED - Breaks multi-step XPath paths
|
|
260
|
+
|
|
261
|
+
**Why It Failed**:
|
|
262
|
+
```xpath
|
|
263
|
+
//book = /descendant-or-self::node()/child::book (2 steps!)
|
|
264
|
+
|
|
265
|
+
With axis-level early exit:
|
|
266
|
+
Step 1: descendant-or-self stops at [Document]
|
|
267
|
+
Step 2: child::book from [Document] → EMPTY! (wrong)
|
|
268
|
+
|
|
269
|
+
Correct behavior:
|
|
270
|
+
Step 1: descendant-or-self returns [Document, root, book, ...]
|
|
271
|
+
Step 2: child::book from all nodes → [book] (correct)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**Architectural Insight**: Early exit must be step-aware, not axis-aware.
|
|
275
|
+
|
|
276
|
+
**Status**: Infrastructure kept for future step-level implementation.
|
|
277
|
+
|
|
278
|
+
**Lesson**: Level of optimization matters as much as the optimization itself.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
== Optimization Principles Discovered
|
|
283
|
+
|
|
284
|
+
=== 1. Algorithmic > Micro-optimizations
|
|
285
|
+
|
|
286
|
+
**Pattern**: Large wins came from algorithmic improvements (SIMD, lookup tables, caching), not micro-optimizations (pooling, locality hints).
|
|
287
|
+
|
|
288
|
+
**Example**: AST caching (one architectural change) provided -76µs improvement, while six planned micro-optimizations would have provided -60µs combined.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
=== 2. Trust Modern Tools
|
|
293
|
+
|
|
294
|
+
**Pattern**: Failed optimizations all involved trying to outsmart modern tools (GC, compiler, runtime).
|
|
295
|
+
|
|
296
|
+
**Examples**:
|
|
297
|
+
- Ruby GC beats manual pooling
|
|
298
|
+
- Clang beats manual code locality
|
|
299
|
+
- Compiler analysis beats manual branch hints
|
|
300
|
+
|
|
301
|
+
**Lesson**: Profile first, measure always, and respect what modern tools already do well.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
=== 3. Profile Before Optimizing
|
|
306
|
+
|
|
307
|
+
**Pattern**: Assumptions about bottlenecks were often wrong.
|
|
308
|
+
|
|
309
|
+
**Example**: Session 67 assumed nodeset conversion was slow (76µs), but profiling revealed parsing was the actual bottleneck (46µs).
|
|
310
|
+
|
|
311
|
+
**Lesson**: Always measure before optimizing. Intuition is unreliable.
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
=== 4. Architecture Over Features
|
|
316
|
+
|
|
317
|
+
**Pattern**: Architectural solutions (AST caching, pattern optimization) provided larger gains than feature additions.
|
|
318
|
+
|
|
319
|
+
**Example**: Changing what we compute (eliminate redundant steps) beat optimizing how we compute (faster traversal).
|
|
320
|
+
|
|
321
|
+
**Lesson**: Think about the problem at a higher level before diving into implementation details.
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
== Performance Metrics Summary
|
|
326
|
+
|
|
327
|
+
=== XML Parsing Journey
|
|
328
|
+
|
|
329
|
+
[cols="3,2,2,3",options="header"]
|
|
330
|
+
|===
|
|
331
|
+
|Stage |Time |vs Baseline |Technique
|
|
332
|
+
|
|
333
|
+
|Baseline (Session 33)
|
|
334
|
+
|9.2ms
|
|
335
|
+
|1.0×
|
|
336
|
+
|Initial implementation
|
|
337
|
+
|
|
338
|
+
|After zero-copy (Session 34)
|
|
339
|
+
|1.46ms
|
|
340
|
+
|6.3×
|
|
341
|
+
|In-place termination
|
|
342
|
+
|
|
343
|
+
|After callbacks (Session 35)
|
|
344
|
+
|0.044ms
|
|
345
|
+
|209×
|
|
346
|
+
|Reduced object creation
|
|
347
|
+
|
|
348
|
+
|After SIMD (Session 48)
|
|
349
|
+
|6.0µs
|
|
350
|
+
|1533×
|
|
351
|
+
|Vector instructions
|
|
352
|
+
|
|
353
|
+
|After char table (Session 58)
|
|
354
|
+
|**5.87µs**
|
|
355
|
+
|**1567×**
|
|
356
|
+
|**Lookup table**
|
|
357
|
+
|
|
358
|
+
|Failed: Object pooling (Session 59)
|
|
359
|
+
|6.2µs
|
|
360
|
+
|1484× (worse)
|
|
361
|
+
|Reverted
|
|
362
|
+
|
|
363
|
+
|Failed: Code locality (Session 60)
|
|
364
|
+
|5.93µs
|
|
365
|
+
|1550× (worse)
|
|
366
|
+
|Reverted
|
|
367
|
+
|===
|
|
368
|
+
|
|
369
|
+
**Final**: 5.87µs = **1567× faster than baseline**
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
=== XPath Evaluation Journey
|
|
374
|
+
|
|
375
|
+
[cols="3,2,2,3",options="header"]
|
|
376
|
+
|===
|
|
377
|
+
|Stage |Time |vs Baseline |Technique
|
|
378
|
+
|
|
379
|
+
|Baseline (Session 66 start)
|
|
380
|
+
|~900µs
|
|
381
|
+
|1.0×
|
|
382
|
+
|Initial implementation
|
|
383
|
+
|
|
384
|
+
|After AST patterns (Session 66)
|
|
385
|
+
|95.05µs
|
|
386
|
+
|9.5×
|
|
387
|
+
|Pattern rewriting
|
|
388
|
+
|
|
389
|
+
|After AST caching (Session 67)
|
|
390
|
+
|**9.00µs**
|
|
391
|
+
|**100×**
|
|
392
|
+
|**Cache + reuse**
|
|
393
|
+
|
|
394
|
+
|vs Nokogiri
|
|
395
|
+
|3.87µs (Nokogiri)
|
|
396
|
+
|**2.3× slower**
|
|
397
|
+
|**Competitive**
|
|
398
|
+
|===
|
|
399
|
+
|
|
400
|
+
**Final**: 9.00µs = **100× faster than baseline**, **2.3× slower than Nokogiri** (but complete XPath 1.0 with zero dependencies)
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
== Future Optimization Opportunities
|
|
405
|
+
|
|
406
|
+
=== Identified but Deferred
|
|
407
|
+
|
|
408
|
+
1. **Step-Level Early Exit** (Session 65 plan)
|
|
409
|
+
- Correct implementation requires step-awareness
|
|
410
|
+
- Expected: ~5-10% for boolean queries
|
|
411
|
+
- Complexity: High
|
|
412
|
+
- Deferred to: v0.2.0
|
|
413
|
+
|
|
414
|
+
2. **JIT Compilation** for Hot XPath Queries
|
|
415
|
+
- Generate specialized C functions for frequent queries
|
|
416
|
+
- Expected: 2-3× for common patterns
|
|
417
|
+
- Complexity: Very High
|
|
418
|
+
- Deferred to: v0.3.0+
|
|
419
|
+
|
|
420
|
+
3. **Streaming Evaluation** for Large Nodesets
|
|
421
|
+
- Avoid materializing entire result set
|
|
422
|
+
- Expected: Memory benefit > speed benefit
|
|
423
|
+
- Complexity: Medium
|
|
424
|
+
- Deferred to: v0.2.0
|
|
425
|
+
|
|
426
|
+
4. **Multi-Threaded Parsing**
|
|
427
|
+
- Split large documents across threads
|
|
428
|
+
- Expected: ~2× for documents >1MB
|
|
429
|
+
- Complexity: High (GVL management required)
|
|
430
|
+
- Deferred to: v0.3.0+
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
=== Not Worth Pursuing
|
|
435
|
+
|
|
436
|
+
Based on Session 59-60 failures:
|
|
437
|
+
|
|
438
|
+
- ❌ Manual object pooling (GC already optimal)
|
|
439
|
+
- ❌ Code locality hints (compiler already optimal)
|
|
440
|
+
- ❌ Manual inlining (compiler already optimal)
|
|
441
|
+
- ❌ Custom allocators (Ruby's allocator is good)
|
|
442
|
+
- ❌ Branch prediction hints (compiler's PGO better)
|
|
443
|
+
|
|
444
|
+
**General Rule**: If a modern tool (GC, compiler, runtime) handles it, don't try to outsmart it.
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
== Conclusion
|
|
449
|
+
|
|
450
|
+
Taurus v0.1.0 achieved production-ready performance through:
|
|
451
|
+
|
|
452
|
+
1. **Smart Algorithmic Choices**: SIMD, lookup tables, AST caching
|
|
453
|
+
2. **Architectural Solutions**: Pattern optimization, global caching
|
|
454
|
+
3. **Respecting Modern Tools**: Not fighting GC/compiler optimizations
|
|
455
|
+
4. **Comprehensive Profiling**: Measure, don't assume
|
|
456
|
+
|
|
457
|
+
**Key Takeaway**: One good architectural solution beats ten micro-optimizations.
|
|
458
|
+
|
|
459
|
+
**Result**: 2.3× slower than Nokogiri for XPath (competitive for v0.1.0 with complete feature set), 2.2× slower than Ox for parsing (but with full XPath support that Ox lacks).
|