Skip to content

Performance

XLFill is designed to be fast enough that performance is never a concern for typical report generation. For large workloads, streaming and parallel modes push throughput even further.

Measured on Intel i5-9300H @ 2.40GHz:

ScenarioRowsTimeMemoryThroughput
Simple 3-column template1004.9ms1.7 MB~20,000 rows/sec
Simple 3-column template1,00027ms8.3 MB~37,000 rows/sec
Simple 3-column template10,000250ms75 MB~40,000 rows/sec
Nested loops (10 groups x 20 items)2002.0ms810 KB~100,000 rows/sec
Single expression evaluation1182ns48 B~5.5M evals/sec
ScenarioRowsTimeMemoryvs Sequential
Simple 3-column template1,0008.9ms3.3 MB3x faster, 60% less memory
ScenarioRowsGoroutinesTimevs Sequential
Simple 3-column template1,000238msOverhead-dominated at this scale
Simple 3-column template1,000437msOverhead-dominated at this scale

Parallel mode shines with CPU-bound expression evaluation (complex expressions, many columns). For I/O-dominated workloads like the simple benchmark above, sequential is faster due to zero mutex overhead.

Linear scaling — processing time grows linearly with the number of rows. 10x more rows = ~10x more time. No surprises.

~7.5 KB per row at scale in sequential mode — memory is dominated by the Excel file representation in excelize. Streaming mode cuts this to ~3.3 KB per row.

Expression caching — expressions are compiled once on first encounter and cached via sync.Map. Subsequent evaluations of the same expression hit the cache, giving ~5.5 million evaluations per second.

Differential context updates — loop variable changes use in-place map updates instead of full rebuilds. This eliminates ~30,000 map copies for a 10K-row loop — a 19% end-to-end speedup compared to the naive approach.

Pre-allocated slices — comment and formula cell lists are pre-sized during template loading, reducing GC pressure for large templates.

Report sizeSequentialStreamingRecommendation
Small (100 rows)< 5msSequential (default)
Medium (1,000 rows)~27ms~9msSequential or streaming
Large (10,000 rows)~250ms~80ms*Streaming if compatible
Very large (100,000 rows)~2.5s~0.8s*Streaming strongly recommended

*Streaming estimates based on measured 3x speedup ratio.

Or let XLFill decide: WithAutoMode(map[string]any{"itemCount": len(rows)}) analyzes your template and picks the optimal mode automatically. See the Performance Tuning guide.

  1. Try streaming first — if your template has no formulas, images, or hyperlinks, WithStreaming(true) gives the biggest win with zero code changes
  2. Use WithAutoMode — it picks the right mode based on your template structure and data size
  3. Compile for batchxlfill.Compile("template.xlsx") caches the template bytes; each fill avoids file I/O
  4. Set a context timeoutWithContext(ctx) prevents runaway fills on server-side endpoints
  5. Keep templates simple — fewer expressions per row means faster evaluation
  6. Filter early with selectjx:each(... select="e.Active") is faster than generating rows you’ll discard
  7. Avoid deep nesting — each nesting level multiplies the work. Three levels deep with large collections can add up
  • Go 1.24+
  • .xlsx files only (the .xls binary format is not supported)

For detailed streaming, parallel, and compiled template configuration:

Performance Tuning →

Having trouble with a template? XLFill ships with built-in tools for inspecting and validating templates:

Debugging & Troubleshooting →