Skip to content

Reduce reflect.Chan/reflect.Select overhead in table import fan-out #69

@BrianLeishman

Description

@BrianLeishman

Context

Follow-up from #66 / #68 and cool-mysql#154. After cool-mysql's INSERT hot path got ~53% faster, the row-build benchmark with a reflect.Chan source still retains ~half its baseline cost compared to the slice-source variant:

InsertRowBuild       9.25ms → 4.37ms   (-53%)     ← purely cool-mysql's row-build
InsertRowBuildChan   9.11ms → 5.03ms   (-45%)     ← same rows, but through a reflect.Chan

Delta between the two variants after cool-mysql#154 is ~660µs per 5000-row op. Alloc profile attributes the gap to reflect.Value.recv and reflect.Select on the srcChRef fan-out path.

Where

main.go around line 849 and 880-925 — the channel is typed dynamically:

srcChRef := reflect.MakeChan(reflect.ChanOf(reflect.BothDir, structType), *rowBufferSize)

…and the multi-dest fan-out uses reflect.Select with a SelectCase slice. Every row goes through reflect machinery on the receive AND on each send, with per-row boxing via reflect.Value.Interface().

This lets cool-mysql's Inserter.InsertContext accept the dynamic struct channel directly, but it costs real CPU.

Ideas

Rough — haven't scoped fully. Each has tradeoffs:

  1. Typed []byte channel instead of typed struct channel. Have swoof build the row bytes directly in the producer goroutine (it already knows the column types from INFORMATION_SCHEMA) and hand cool-mysql a chan []byte of pre-marshaled rows. Bypasses both the reflect channel and cool-mysql's per-field marshal work entirely on the INSERT side. Biggest win, biggest change — needs a new cool-mysql API that accepts pre-built row bytes.

  2. Batched channel: chan []RowStruct instead of chan RowStruct. Amortizes the reflect.Select overhead by a factor of N. Smaller change, partial win.

  3. Swap dynamic-struct for a generic [][]any row. Avoids FieldByIndex per column on cool-mysql's side but keeps the reflect.Chan overhead.

  4. Post-go-1.25 typed generics path. Reflect is the way today because we don't know the column shape at compile time. Anything that threads the column shape through a type parameter (probably via codegen into a temp file at swoof start) could in theory let cool-mysql see a concrete struct type, but that's a meaningful architectural shift.

Option 1 is the most ambitious and likely highest-yield, and it also offloads work from cool-mysql (good for any other cool-mysql user doing streaming inserts). Probably worth prototyping first, benchmarking with the existing BenchmarkInsertRowBuildChan as the A/B target.

Non-goals

Rough priority

Low-medium. cool-mysql#154 was the big CPU win; this would tighten things further but isn't blocking anything. Worth doing next time someone profiles swoof or hits a CPU-bound import again.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions