@@ -94,15 +94,18 @@ type scanResult struct {
9494 separatorMasks []uint64 // Separator masks per chunk
9595 newlineMasks []uint64 // Newline masks per chunk (CRLF normalized)
9696 chunkHasDQ []bool // Per-chunk flag: true if chunk contains escaped double quotes
97+ chunkHasQuote []bool // Per-chunk flag: true if chunk contains any quote
9798 hasQuotes bool // True if any quote characters exist in input
9899 finalQuoted uint64 // Final quote state
99100 chunkCount int // Number of processed chunks
100101 lastChunkBits int // Valid bits in last chunk (if < 64)
102+ separatorCount int // Total separators (after quote invalidation)
103+ newlineCount int // Total newlines (after quote invalidation)
101104}
102105
103106// scanResultPoolCapacity is the pre-allocated slice capacity for pooled scanResult objects.
104- // 1024 chunks = ~64KB input (1024 * 64 bytes per chunk).
105- const scanResultPoolCapacity = 1024
107+ // 64 chunks = ~4KB input (64 * 64 bytes per chunk) - small default, grows as needed .
108+ const scanResultPoolCapacity = 64
106109
107110// scanResultPool provides reusable scanResult objects to reduce allocations.
108111var scanResultPool = sync.Pool {
@@ -112,6 +115,7 @@ var scanResultPool = sync.Pool{
112115 separatorMasks : make ([]uint64 , 0 , scanResultPoolCapacity ),
113116 newlineMasks : make ([]uint64 , 0 , scanResultPoolCapacity ),
114117 chunkHasDQ : make ([]bool , 0 , scanResultPoolCapacity ),
118+ chunkHasQuote : make ([]bool , 0 , scanResultPoolCapacity ),
115119 }
116120 },
117121}
@@ -124,10 +128,15 @@ func (sr *scanResult) reset() {
124128 if cap (sr .chunkHasDQ ) > 0 {
125129 sr .chunkHasDQ = sr .chunkHasDQ [:0 ]
126130 }
131+ if cap (sr .chunkHasQuote ) > 0 {
132+ sr .chunkHasQuote = sr .chunkHasQuote [:0 ]
133+ }
127134 sr .hasQuotes = false
128135 sr .finalQuoted = 0
129136 sr .chunkCount = 0
130137 sr .lastChunkBits = 0
138+ sr .separatorCount = 0
139+ sr .newlineCount = 0
131140}
132141
133142// releaseScanResult returns a scanResult to the pool for reuse.
@@ -170,6 +179,11 @@ func generateMasksAVX512(data []byte, separator byte) (quote, sep, cr, nl uint64
170179 crCmp := archsimd .BroadcastInt8x32 ('\r' )
171180 nlCmp := archsimd .BroadcastInt8x32 ('\n' )
172181
182+ return generateMasksAVX512WithCmp (data , quoteCmp , sepCmp , crCmp , nlCmp )
183+ }
184+
185+ // generateMasksAVX512WithCmp is an AVX-512 mask generator that reuses pre-broadcasted comparators.
186+ func generateMasksAVX512WithCmp (data []byte , quoteCmp , sepCmp , crCmp , nlCmp archsimd.Int8x32 ) (quote , sep , cr , nl uint64 ) {
173187 // Process low simdHalfChunk bytes (positions 0-31)
174188 // Precondition: data is at least simdChunkSize bytes (guaranteed by caller)
175189 low := archsimd .LoadInt8x32 ((* [simdHalfChunk ]int8 )(unsafe .Pointer (& data [0 ])))
@@ -350,6 +364,10 @@ func scanBuffer(buf []byte, separatorChar byte) *scanResult {
350364 result .reset ()
351365 result .chunkCount = chunkCount
352366
367+ // NOTE: Pre-broadcasting AVX-512 comparators was attempted but removed because
368+ // declaring archsimd.Int8x32 variables causes Go to emit AVX zeroing instructions
369+ // even before the conditional check, causing SIGILL on CPUs without AVX support.
370+
353371 // Pre-size all mask slices to chunkCount for index-based assignment (avoids append overhead)
354372 // When capacity is insufficient, grow by 2x to reduce future reallocations
355373 if cap (result .quoteMasks ) < chunkCount {
@@ -392,6 +410,18 @@ func scanBuffer(buf []byte, separatorChar byte) *scanResult {
392410 result .chunkHasDQ [i ] = false
393411 }
394412 }
413+ if cap (result .chunkHasQuote ) < chunkCount {
414+ newCap := chunkCount
415+ if newCap < cap (result .chunkHasQuote )* 2 {
416+ newCap = cap (result .chunkHasQuote ) * 2
417+ }
418+ result .chunkHasQuote = make ([]bool , chunkCount , newCap )
419+ } else {
420+ result .chunkHasQuote = result .chunkHasQuote [:chunkCount ]
421+ for i := range result .chunkHasQuote {
422+ result .chunkHasQuote [i ] = false
423+ }
424+ }
395425
396426 state := scanState {}
397427
@@ -500,13 +530,22 @@ func scanBuffer(buf []byte, separatorChar byte) *scanResult {
500530 // Track if any quotes exist in the input (for fast path optimization)
501531 if quoteMaskOut != 0 {
502532 result .hasQuotes = true
533+ result .chunkHasQuote [chunkIdx ] = true
503534 }
504535
505536 // Record chunks that have double quotes (using bool array instead of []int)
506537 if hasDoubleQuote {
507538 result .chunkHasDQ [chunkIdx ] = true
508539 }
509540
541+ // Accumulate counts for preallocation sizing
542+ if sepMaskOut != 0 {
543+ result .separatorCount += bits .OnesCount64 (sepMaskOut )
544+ }
545+ if newlineMaskOut != 0 {
546+ result .newlineCount += bits .OnesCount64 (newlineMaskOut )
547+ }
548+
510549 // Slide masks: current = next, compute new next for chunkIdx+2
511550 curMasks = nextMasks
512551 curValidBits = simdChunkSize // next chunk was full unless it's the last
0 commit comments