Skip to content

Commit 7303bc1

Browse files
authored
perf: optimize SIMD mask processing with prefix XOR (#62)
* perf: optimize SIMD mask processing with prefix XOR * chore: fix lint
1 parent bce9b97 commit 7303bc1

2 files changed

Lines changed: 69 additions & 94 deletions

File tree

quote.go

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -128,36 +128,29 @@ func findClosingQuoteSIMD(data []byte, startAfterOpenQuote int) int {
128128
func processQuoteMask(data []byte, chunkStart int, mask uint32) (int, int, bool) {
129129
for mask != 0 {
130130
pos := bits.TrailingZeros32(mask)
131-
absPos := chunkStart + pos
132131

133-
if !isEscapedQuote(data, absPos) {
134-
return absPos, chunkStart, false
135-
}
136-
137-
// Clear both bits of the escaped quote pair
138-
mask = clearBitU32(mask, pos)
139-
if pos+1 < simdHalfChunk {
140-
mask = clearBitU32(mask, pos+1)
141-
}
142-
143-
// Handle boundary case: escaped quote spans chunk boundary
132+
// Boundary case: quote at last position of chunk
144133
if pos == simdHalfChunk-1 {
145134
newPos := chunkStart + simdHalfChunk
146135
if newPos < len(data) && data[newPos] == '"' {
147-
newPos++
136+
// Boundary double quote → skip both
137+
return -1, newPos + 1, false
148138
}
149-
return -1, newPos, false
139+
// Closing quote at boundary
140+
return chunkStart + pos, chunkStart, false
150141
}
151-
}
152142

153-
return -1, chunkStart + simdHalfChunk, false
154-
}
143+
// Check if next bit is also set (double quote "")
144+
nextBit := uint32(1) << (pos + 1)
145+
if mask&nextBit != 0 {
146+
// Double quote → clear both bits and continue
147+
mask &^= (uint32(1) << pos) | nextBit
148+
continue
149+
}
155150

156-
// =============================================================================
157-
// Bit Manipulation Utilities
158-
// =============================================================================
151+
// Single quote = closing quote
152+
return chunkStart + pos, chunkStart, false
153+
}
159154

160-
// clearBitU32 clears the bit at position pos in a 32-bit mask.
161-
func clearBitU32(mask uint32, pos int) uint32 {
162-
return mask &^ (uint32(1) << pos)
155+
return -1, chunkStart + simdHalfChunk, false
163156
}

simd_scanner.go

Lines changed: 53 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -279,95 +279,77 @@ func normalizeCRLF(crMask, nlMask, nextNlMask uint64, validBits int) uint64 {
279279
// processQuotesAndSeparators processes masks to track quote state and invalidate
280280
// separators inside quoted regions. Detects escaped double quotes ("") including
281281
// those spanning chunk boundaries.
282-
func processQuotesAndSeparators(quoteMask, sepMask, newlineMask, nextQuoteMask uint64, state *scanState) (quoteMaskOut, sepMaskOut uint64, hasDoubleQuote, boundaryDoubleQuote bool) {
282+
func processQuotesAndSeparators(quoteMask, sepMask, nextQuoteMask uint64, state *scanState) (quoteMaskOut, sepMaskOut uint64, hasDoubleQuote, boundaryDoubleQuote bool) {
283283
quoteMaskOut = quoteMask
284-
sepMaskOut = sepMask
285284
workQuote := quoteMask
286-
workSep := sepMask
287-
workNewline := newlineMask
288285
quoted := state.quoted
286+
initialQuoted := quoted
289287

290-
for {
291-
combined := workQuote | workSep | workNewline
292-
if combined == 0 {
293-
break
294-
}
295-
296-
pos := bits.TrailingZeros64(combined)
288+
// Step 1: Process quotes to detect and remove double quotes
289+
for workQuote != 0 {
290+
pos := bits.TrailingZeros64(workQuote)
297291
bit := uint64(1) << pos
298292

299-
switch {
300-
case workQuote&bit != 0:
301-
if quoted != 0 {
302-
// Inside quotes: check for escaped double quote
303-
if pos == simdChunkSize-1 && nextQuoteMask&1 != 0 {
304-
// Boundary double quote
305-
quoteMaskOut &= ^(uint64(1) << (simdChunkSize - 1))
306-
hasDoubleQuote = true
307-
boundaryDoubleQuote = true
308-
} else if pos < simdChunkSize-1 && workQuote&(uint64(1)<<(pos+1)) != 0 {
309-
// Adjacent double quote
310-
quoteMaskOut &= ^(uint64(3) << pos)
311-
hasDoubleQuote = true
312-
workQuote &= ^(uint64(1) << (pos + 1))
313-
} else {
314-
// Closing quote
315-
quoted = 0
316-
}
293+
if quoted != 0 {
294+
// Inside quotes: check for escaped double quote
295+
if pos == simdChunkSize-1 && nextQuoteMask&1 != 0 {
296+
// Boundary double quote
297+
quoteMaskOut &^= uint64(1) << (simdChunkSize - 1)
298+
hasDoubleQuote = true
299+
boundaryDoubleQuote = true
300+
} else if pos < simdChunkSize-1 && workQuote&(uint64(1)<<(pos+1)) != 0 {
301+
// Adjacent double quote
302+
quoteMaskOut &^= uint64(3) << pos
303+
hasDoubleQuote = true
304+
workQuote &^= uint64(1) << (pos + 1)
317305
} else {
318-
// Opening quote
319-
quoted = ^uint64(0)
320-
}
321-
workQuote &= ^bit
322-
323-
case workSep&bit != 0:
324-
if quoted != 0 {
325-
sepMaskOut &= ^bit
306+
// Closing quote
307+
quoted = 0
326308
}
327-
workSep &= ^bit
328-
329-
default:
330-
workNewline &= ^bit
309+
} else {
310+
// Opening quote
311+
quoted = ^uint64(0)
331312
}
313+
workQuote &^= bit
332314
}
333315

334316
state.quoted = quoted
317+
318+
// Step 2: Invalidate separators using prefix XOR on clean quote mask
319+
inQuote := quoteMaskOut
320+
inQuote ^= inQuote << 1
321+
inQuote ^= inQuote << 2
322+
inQuote ^= inQuote << 4
323+
inQuote ^= inQuote << 8
324+
inQuote ^= inQuote << 16
325+
inQuote ^= inQuote << 32
326+
327+
if initialQuoted != 0 {
328+
inQuote = ^inQuote
329+
}
330+
331+
sepMaskOut = sepMask &^ inQuote
335332
return
336333
}
337334

338335
// invalidateNewlinesInQuotes removes newline bits that are inside quoted regions.
339336
func invalidateNewlinesInQuotes(quoteMask, newlineMask uint64, state *scanState) uint64 {
340-
quoted := state.quoted
341-
result := newlineMask
342-
workQuote := quoteMask
343-
workNewline := newlineMask
344-
345-
for {
346-
combined := workQuote | workNewline
347-
if combined == 0 {
348-
break
349-
}
350-
351-
pos := bits.TrailingZeros64(combined)
352-
bit := uint64(1) << pos
353-
354-
if workQuote&bit != 0 {
355-
if quoted != 0 {
356-
quoted = 0
357-
} else {
358-
quoted = ^uint64(0)
359-
}
360-
workQuote &= ^bit
361-
continue
362-
}
363-
364-
if quoted != 0 {
365-
result &= ^bit
366-
}
367-
workNewline &= ^bit
337+
// Prefix XOR: inQuote[i] = 1 iff positions 0..i have odd number of quotes
338+
inQuote := quoteMask
339+
inQuote ^= inQuote << 1
340+
inQuote ^= inQuote << 2
341+
inQuote ^= inQuote << 4
342+
inQuote ^= inQuote << 8
343+
inQuote ^= inQuote << 16
344+
inQuote ^= inQuote << 32
345+
346+
// If we started inside a quoted region, invert the mask
347+
if state.quoted != 0 {
348+
inQuote = ^inQuote
368349
}
369350

370-
return result
351+
// Clear newline bits that are inside quoted regions
352+
return newlineMask &^ inQuote
371353
}
372354

373355
// =============================================================================
@@ -614,7 +596,7 @@ func processChunkWithQuotes(chunkIdx int, quoteMask, sepMask, newlineMask, nextQ
614596
initialQuoted := state.quoted
615597

616598
quoteMaskOut, sepMaskOut, hasDoubleQuote, boundaryDoubleQuote := processQuotesAndSeparators(
617-
quoteMask, sepMask, newlineMask, nextQuoteMask, state,
599+
quoteMask, sepMask, nextQuoteMask, state,
618600
)
619601

620602
if boundaryDoubleQuote {

0 commit comments

Comments
 (0)