SmarterCSV over the Years

May 14, 2026 · View on GitHub

Contents


SmarterCSV over the Years

Origin

SmarterCSV was born from a StackOverflow question in 2011 about importing CSV data into MongoDB. The answer involved processing CSV rows as hashes — which turned out to be so useful that it became a gem.

The original write-up is preserved at The original post.

The first gem release was v1.0.1 on 2012-07-30.


Key Milestones

VersionDateHighlight
1.0.12012-07-30First release: CSV → array of hashes, batch processing, key mapping
1.0.172014-01-13row_sep: :auto — automatic row separator detection
1.0.182014-10-27Multi-line / embedded-newline field support
1.1.02015-07-26value_converters — custom per-column type parsing (dates, money, …)
1.4.02022-02-11Experimental col_sep: :auto detection; switched to MIT-only licence
1.5.12022-04-27duplicate_header_suffix for CSV files with repeated headers
1.6.02022-05-03Complete rewrite of the pure-Ruby line parser
1.7.02022-06-26First C extension — >10× speedup over 1.6.x announced
1.8.02023-03-18col_sep: :auto and row_sep: :auto made the default
1.9.02023-09-04Structured error objects with programmatic key access
1.10.02023-12-31Performance & memory improvements; stricter user_provided_headers
1.11.02024-07-02SmarterCSV::Writer — CSV generation from hashes
1.12.02024-07-09Thread-safe SmarterCSV::Reader class; docs site added
1.13.02024-11-06Auto-generation of extra column names; improved quote robustness
1.14.02025-04-07Advanced Writer options; header_converter
1.14.32025-05-04C-extension fast path for unquoted fields; inline whitespace stripping
1.15.02026-02-04Major C-extension rewrite — ~5× faster than 1.14.4; 39% less memory
1.15.12026-02-17Fix for backslash in quoted fields (quote_escaping: option)
1.15.22026-02-20Further C-path optimisations; 5.4×–37.4× faster than 1.14.4
1.16.02026-03-12New each/each_chunk enumerator API; SmarterCSV.parse; bad row quarantine; column selection headers: { only: }; 1.8×–8.6× faster than Ruby CSV.read; new features for Reader and Writer; minor breaking: quote_boundary: :standard
1.16.12026-03-16SmarterCSV.errors class-level error access; fix col_sep in quoted headers (#325); fix quoted numeric conversion

Performance Journey

Measured on Apple M1, Ruby 3.4.7. Best of 2 sessions × 30 runs. All times are C-accelerated except the 1.6.1 column (no C extension existed). — = not measured for that version.

FileRows1.6.1 Rb (s)1.7.1 C (s)1.14.4 C (s)1.15.2 C (s)1.16.0 C (s)total gain
PEOPLE_IMPORT_B.csv50k3.7931.0831.6560.1010.08743.6×
PEOPLE_IMPORT_C.csv50k21.6122.7638.1720.2070.169127.8×
PEOPLE_IMPORT_NB.csv50k3.7461.0531.6050.0860.08046.9×
PEOPLE_IMPORT_NC.csv50k3.8311.0181.4950.0760.06360.8×
uscities.csv31k——1.0580.1130.108—
uszips.csv34k——1.2770.1110.102—
worldcities.csv48k——1.0700.1160.097—
fmap.csv50k2.1300.873————
zipcode.csv44k1.5720.797————
sample_10M.csv50k1.2910.6610.4590.0530.04628.0×
sensor_data_50krows_50cols.csv50k——3.9850.2720.264—
embedded_newlines_20k.csv80k0.7160.3660.5400.0560.05413.2×
embedded_separators_20k.csv20k0.7140.3330.2780.0320.02528.6×
heavy_quoting_20k.csv20k1.3090.4840.5220.0540.03636.5×
long_fields_20k.csv20k5.6981.1122.9600.1100.045126.6×
many_empty_fields_20k.csv20k1.1490.4200.3950.0310.02545.8×
multi_char_separator_20k.csv20k——0.5390.0330.026—
tab_separated_20k.tsv20k——0.4620.0340.025—
utf8_multibyte_20k.csv20k0.7090.3050.2280.0200.01741.7×
whitespace_heavy_20k.csv20k1.3350.3930.5360.0360.02847.5×
wide_500_cols_20k.csv20k39.7559.53217.6581.4191.35229.4×

total gain = v1.6.1 Ruby time / v1.16.0 C-accelerated time (files without 1.6.1 data show —)


Highlights:

  • long_fields_20k (long quoted fields): 126.6× — memchr-based field scanning makes long quoted fields essentially free to skip.
  • PEOPLE_IMPORT_C (116 columns): 127.8× — wide rows multiply every per-field saving across all columns.
  • PEOPLE_IMPORT_NC (17 columns): 60.8× — Ruby-path optimisations #10 & #11 provide an extra boost on moderately wide files.
  • wide_500_cols_20k went from 39.8 seconds → 1.35 seconds — and with headers: { only: } keeping just 2 of those 500 columns it drops further to ~0.1 seconds (an additional ~16× on top).
  • embedded_newlines shows the smallest gain (13.2×) — multi-line stitching is bounded by I/O and the line-counting loop, not field parsing.


PREVIOUS: Real-World CSV Files | NEXT: Release Notes | UP: README