PSWriteOffice Benchmarks

August 10, 2026 ยท View on GitHub

PSWriteOffice benchmarks use the PSPublishModule/PowerForge benchmark DSL. The suite is split by file format so workbook behavior is compared with workbook tools and CSV behavior is compared with CSV tools.

Excel

Compare-ExcelPerformance.ps1 measures OfficeIMO-backed PSWriteOffice Excel cmdlets against PowerShell-facing Excel alternatives:

  • PSWriteOffice
  • ImportExcel
  • ExcelFast for the workbook lanes it supports

Excel smoke, standard, and full suites use five measured iterations; large and super-large suites use three. The grouped rotation keeps comparable workbook engines adjacent and alternates which engine runs first. PowerForge performs an explicit managed-memory cleanup after setup and data creation, outside the timed operation, so allocations from one engine do not become another engine's GC bill.

Every read comparison uses the same PSWriteOffice-produced workbook shape for the selected row count. The competing readers do not benchmark files created by their own writers.

The current ExcelFast 0.0.1-alpha16 writer places an empty drawing element in an invalid worksheet position. Its write lane therefore includes ExcelFast's own Get-Workbook and Save-Workbook normalization inside the timed operation. This keeps ExcelFast in the comparison without reporting malformed-workbook speed as a valid result.

ExcelFast's mixed-object writer currently stores booleans, dates, and numbers as culture-formatted text cells. Mixed and wide typed write lanes are therefore not equivalent and remain excluded. The ExcelFast write comparison uses the text-only default shape, while read lanes use the same PSWriteOffice-produced typed workbook fixture as every other reader.

Runs that include PSWriteOffice build directly against a complete OfficeIMO source checkout. With sibling PSWriteOffice and OfficeIMO checkouts, set the source root once for the current shell:

$env:OfficeIMORoot = (Resolve-Path ..\OfficeIMO).Path
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-ExcelPerformance.ps1 -Suite Smoke
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-ExcelPerformance.ps1 -Suite Standard -Plan
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-ExcelPerformance.ps1 `
    -Suite Standard `
    -RowCount 1000,5000,10000 `
    -RepeatCount 2 `
    -Engine PSWriteOffice,ImportExcel,ExcelFast `
    -UpdateReadme

Streaming IDataReader tables

The datareader-table scenario measures the SQL-facing path: a forward-only IDataReader is written as a real XLSX table and then validated for typed values and Open XML package correctness. It is a PSWriteOffice capability lane, not a cross-library ranking, because the compared PowerShell modules do not expose an equivalent IDataReader workbook export.

pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-ExcelPerformance.ps1 `
    -Suite Standard `
    -RowCount 25000 `
    -RepeatCount 31 `
    -Scenario datareader-table `
    -Engine PSWriteOffice

A focused 2026-08-10 before/after run used 25,000 rows, twelve warmups, 31 measurements, High process priority, and affinities 0x1 and 0x10000 on an AMD Ryzen 9 9950X3D2. The package-native path reduced observed mean export time from 127.74 to 82.21 ms on 0x1 and from 84.95 to 47.31 ms on 0x10000. Managed allocation fell from about 12.37 MB to 3.00 MB in both processor domains. These are workstation observations; the paired processor runs are kept visible because the faster CPU domain can change by sub-operation.

ScenarioRowsPSWriteOfficeExcelFastImportExcelResult
append-existing-table1000100.2 ms (1.00x)Skipped340.0 ms (3.39x slower)PSWriteOffice fastest
append-existing-table5000323.2 ms (1.00x)Skipped1.14 s (3.52x slower)PSWriteOffice fastest
append-existing-table10000741.3 ms (1.00x)Skipped2.17 s (2.93x slower)PSWriteOffice fastest
chart-only-workbook100091.0 ms (1.00x)Skipped285.2 ms (3.13x slower)PSWriteOffice fastest
chart-only-workbook5000348.1 ms (1.00x)Skipped1.07 s (3.09x slower)PSWriteOffice fastest
chart-only-workbook10000949.7 ms (1.00x)Skipped2.01 s (2.12x slower)PSWriteOffice fastest
csv-to-excel100073.4 ms (1.00x)Skipped422.1 ms (5.75x slower)PSWriteOffice fastest
csv-to-excel5000270.0 ms (1.00x)Skipped1.65 s (6.12x slower)PSWriteOffice fastest
csv-to-excel10000571.4 ms (1.00x)Skipped3.40 s (5.95x slower)PSWriteOffice fastest
datatable-default100021.0 ms (1.00x)Skipped263.1 ms (12.52x slower)PSWriteOffice fastest
datatable-default500027.1 ms (1.00x)Skipped777.1 ms (28.62x slower)PSWriteOffice fastest
datatable-default1000036.1 ms (1.00x)Skipped1.50 s (41.72x slower)PSWriteOffice fastest
import-default-full100020.7 ms (1.00x)42.6 ms (2.06x slower)121.5 ms (5.86x slower)PSWriteOffice fastest
import-default-full500096.4 ms (1.00x)106.3 ms (1.10x slower)276.7 ms (2.87x slower)PSWriteOffice fastest
import-default-full10000189.6 ms (1.00x)226.6 ms (1.19x slower)520.5 ms (2.75x slower)PSWriteOffice fastest
import-default-range100018.0 ms (1.00x)48.2 ms (2.67x slower)125.0 ms (6.93x slower)PSWriteOffice fastest
import-default-range500074.1 ms (1.00x)115.6 ms (1.56x slower)276.7 ms (3.73x slower)PSWriteOffice fastest
import-default-range10000110.7 ms (1.00x)224.6 ms (2.03x slower)399.2 ms (3.61x slower)PSWriteOffice fastest
many-small-sheets1000129.5 ms (1.00x)Skipped373.1 ms (2.88x slower)PSWriteOffice fastest
many-small-sheets5000499.4 ms (1.00x)Skipped1.22 s (2.44x slower)PSWriteOffice fastest
many-small-sheets10000989.5 ms (1.00x)Skipped2.94 s (2.97x slower)PSWriteOffice fastest
multi-sheet-regions100091.8 ms (1.00x)Skipped284.5 ms (3.10x slower)PSWriteOffice fastest
multi-sheet-regions5000405.4 ms (1.00x)Skipped1.04 s (2.57x slower)PSWriteOffice fastest
multi-sheet-regions10000801.6 ms (1.00x)Skipped2.00 s (2.50x slower)PSWriteOffice fastest
named-range-workbook100085.7 ms (1.00x)Skipped289.6 ms (3.38x slower)PSWriteOffice fastest
named-range-workbook5000328.0 ms (1.00x)Skipped1.06 s (3.25x slower)PSWriteOffice fastest
named-range-workbook10000792.0 ms (1.00x)Skipped2.00 s (2.53x slower)PSWriteOffice fastest
objects-default100045.4 ms (1.00x)Skipped302.2 ms (6.65x slower)PSWriteOffice fastest
objects-default5000101.0 ms (1.00x)Skipped1.06 s (10.52x slower)PSWriteOffice fastest
objects-default10000226.3 ms (1.00x)Skipped2.11 s (9.33x slower)PSWriteOffice fastest
objects-no-table100025.6 ms (1.00x)Skipped280.9 ms (10.98x slower)PSWriteOffice fastest
objects-no-table500062.3 ms (1.00x)Skipped1.05 s (16.88x slower)PSWriteOffice fastest
objects-no-table10000238.4 ms (1.00x)Skipped2.29 s (9.60x slower)PSWriteOffice fastest
objects-table100029.4 ms (1.00x)Skipped318.9 ms (10.86x slower)PSWriteOffice fastest
objects-table500063.7 ms (1.00x)Skipped995.8 ms (15.63x slower)PSWriteOffice fastest
objects-table10000175.2 ms (1.00x)Skipped2.05 s (11.68x slower)PSWriteOffice fastest
objects-table-autofit100047.4 ms (1.00x)Skipped299.2 ms (6.31x slower)PSWriteOffice fastest
objects-table-autofit500070.7 ms (1.00x)Skipped1.11 s (15.67x slower)PSWriteOffice fastest
objects-table-autofit10000203.8 ms (1.00x)Skipped2.01 s (9.85x slower)PSWriteOffice fastest
objects-title-freeze100089.5 ms (1.00x)Skipped278.2 ms (3.11x slower)PSWriteOffice fastest
objects-title-freeze5000349.2 ms (1.00x)Skipped970.0 ms (2.78x slower)PSWriteOffice fastest
objects-title-freeze10000892.7 ms (1.00x)Skipped1.78 s (1.99x slower)PSWriteOffice fastest
pivot-only-workbook100059.7 ms (1.00x)Skipped288.2 ms (4.83x slower)PSWriteOffice fastest
pivot-only-workbook5000182.5 ms (1.00x)Skipped1.04 s (5.71x slower)PSWriteOffice fastest
pivot-only-workbook10000408.0 ms (1.00x)Skipped2.08 s (5.10x slower)PSWriteOffice fastest
read-named-range-metadata100017.4 ms (1.00x)SkippedSkippedPSWriteOffice fastest
read-named-range-metadata500019.1 ms (1.00x)SkippedSkippedPSWriteOffice fastest
read-named-range-metadata1000018.1 ms (1.00x)SkippedSkippedPSWriteOffice fastest
read-no-header-range100018.0 ms (1.00x)46.0 ms (2.56x slower)126.2 ms (7.02x slower)PSWriteOffice fastest
read-no-header-range500084.9 ms (1.00x)97.6 ms (1.15x slower)252.2 ms (2.97x slower)PSWriteOffice fastest
read-no-header-range10000110.7 ms (1.00x)220.1 ms (1.99x slower)427.8 ms (3.86x slower)PSWriteOffice fastest
read-table-metadata100015.8 ms (1.00x)SkippedSkippedPSWriteOffice fastest
read-table-metadata500045.7 ms (1.00x)SkippedSkippedPSWriteOffice fastest
read-table-metadata1000015.3 ms (1.00x)SkippedSkippedPSWriteOffice fastest
read-used-range-datatable100019.0 ms (1.00x)SkippedSkippedPSWriteOffice fastest
read-used-range-datatable500042.3 ms (1.00x)SkippedSkippedPSWriteOffice fastest
read-used-range-datatable1000075.3 ms (1.00x)SkippedSkippedPSWriteOffice fastest
report-workbook1000100.9 ms (1.00x)Skipped309.1 ms (3.06x slower)PSWriteOffice fastest
report-workbook5000374.6 ms (1.00x)Skipped1.07 s (2.85x slower)PSWriteOffice fastest
report-workbook10000889.0 ms (1.00x)Skipped2.09 s (2.35x slower)PSWriteOffice fastest
summary-formulas100086.4 ms (1.00x)Skipped272.5 ms (3.15x slower)PSWriteOffice fastest
summary-formulas5000298.0 ms (1.00x)Skipped1.05 s (3.53x slower)PSWriteOffice fastest
summary-formulas10000748.6 ms (1.00x)Skipped1.98 s (2.64x slower)PSWriteOffice fastest
text-objects-default100073.2 ms (1.00x)194.1 ms (2.65x slower)424.1 ms (5.79x slower)PSWriteOffice fastest
text-objects-default500099.5 ms (1.00x)427.8 ms (4.30x slower)1.71 s (17.14x slower)PSWriteOffice fastest
text-objects-default10000160.8 ms (1.00x)790.5 ms (4.92x slower)3.17 s (19.71x slower)PSWriteOffice fastest
update-existing-workbook1000170.8 ms (1.00x)Skipped333.1 ms (1.95x slower)PSWriteOffice fastest
update-existing-workbook5000913.2 ms (1.00x)Skipped1.38 s (1.51x slower)PSWriteOffice fastest
update-existing-workbook100002.39 s (1.00x)Skipped2.65 s (1.11x slower)PSWriteOffice fastest
wide-objects-default100073.7 ms (1.00x)Skipped227.7 ms (3.09x slower)PSWriteOffice fastest
wide-objects-default5000281.9 ms (1.00x)Skipped827.0 ms (2.93x slower)PSWriteOffice fastest
wide-objects-default10000298.3 ms (1.00x)Skipped1.52 s (5.09x slower)PSWriteOffice fastest
workbook-package-merge1000266.0 ms (1.00x)Skipped653.3 ms (2.46x slower)PSWriteOffice fastest
workbook-package-merge5000857.1 ms (1.00x)Skipped1.56 s (1.82x slower)PSWriteOffice fastest
workbook-package-merge100001.45 s (1.00x)Skipped2.66 s (1.83x slower)PSWriteOffice fastest

CSV

Compare-CsvPerformance.ps1 measures PSWriteOffice CSV cmdlets against native PowerShell CSV import/export:

  • PSWriteOffice
  • NativeCsv

The CSV release gate uses 25 measured iterations for suites that include the short 1,000-row lanes. Large and super-large suites use 11 and 7 iterations, respectively. PowerForge keeps each scenario comparison together and alternates engine order between iterations, so neither engine keeps the first or second position. An explicit managed-memory cleanup runs after setup and data creation, outside the timed operation, for every engine. Use -RepeatCount for an intentional diagnostic override; keep the defaults when recording release or README results.

The CSV suite also includes dbatools-shaped read scenarios over the same generated CSV shape used by dataplat/dbatools.library/benchmarks/CsvBenchmarks. csv-dbatools-quick-single-column and csv-dbatools-quick-all-columns cover the upstream QuickTest pattern and the small/medium/large reader shape by changing -RowCount. The explicit csv-dbatools-wide-* and csv-dbatools-quoted-* lanes cover the upstream wide 50-column and quote-all files.

The csv-read-datatable-* lanes compare Import-OfficeCsv -AsDataTable with a native PowerShell baseline that reads via Import-Csv and fills a DataTable. That keeps the database/table workflow visible instead of comparing only PowerShell object materialization.

The csv-write-gzip-* and csv-read-gzip-datatable-* lanes keep compressed CSV in the same benchmark matrix. The native baseline uses GZipStream with ConvertTo-Csv / ConvertFrom-Csv, while PSWriteOffice uses Export-OfficeCsv -CompressionType GZip and Import-OfficeCsv -CompressionType GZip.

CSV runs that include PSWriteOffice use the same direct OfficeIMO source root:

$env:OfficeIMORoot = (Resolve-Path ..\OfficeIMO).Path

CSV Capability Coverage

The CSV benchmarks sit next to feature coverage on purpose: the target is fast and correct, not a narrow parser trick. They measure the CSV reader and writer paths used by PSWriteOffice, including streaming, compression, typed values, and round-trip validation.

CapabilityCurrent PSWriteOffice / OfficeIMO.CSV supportBenchmark visibility
Streaming readsImport-OfficeCsv streams rows by default; -AsDataTable materializes one table when the next hop is tabularObject read lanes and DataTable read lanes
Streaming writesExport-OfficeCsv accepts objects, DataTable, DataView, and IDataReader inputObject/DataTable write lanes; DbaClientX export benchmark covers direct IDataReader SQL export
CompressionExport-OfficeCsv / Import-OfficeCsv expose -CompressionType; OfficeIMO.CSV supports none, auto, GZip, Deflate, and runtime-supported Brotli/ZLibGZip write/read DataTable lanes
CancellationOfficeIMO.CSV accepts a cancellation token; PSWriteOffice cancels it from StopProcessing, so Ctrl+C can stop long readsContract coverage, not timed by default
ProgressOfficeIMO.CSV exposes progress callbacks; PSWriteOffice exposes -ProgressInterval and writes PowerShell progress recordsContract coverage, not timed by default
Schema and typed tablesOfficeIMO.CSV supports explicit/inferred schema, DataTable, and IDataReader schema tables; PSWriteOffice exposes Import-OfficeCsv -AsDataTable -InferSchema and Import-OfficeCsv -AsDataReader -ColumnTypeDataTable read lanes; DbaClientX round-trip uses schema-aware reader handoff
CSV dialectsCulture/list separator, delimiter detection, multi-character delimiters, no-header/custom headers, comments, W3C headers, null values, date formats, quote modes, and selected quote fieldsDefault benchmark dialect plus focused command tests
Robust parsingDuplicate-header policy, row-length mismatch policy, strict/lenient quotes, parse-error collection/skip-row, max field length, decompression limits, smart-quote normalization, and string interningCommand/core tests; timed lanes stay on clean generated files
Platform shapePSWriteOffice targets Windows PowerShell/.NET Framework and PowerShell 7/.NET; Brotli/ZLib depend on runtime support, and comparison tools such as bcp/FastBCP/dbatools must be installed locallyBenchmarks skip unavailable optional engines
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-CsvPerformance.ps1 -Suite Smoke
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-CsvPerformance.ps1 -Suite Standard -Plan
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-CsvPerformance.ps1 `
    -Suite Standard `
    -RowCount 1000,5000,10000 `
    -RepeatCount 25 `
    -Engine PSWriteOffice,NativeCsv `
    -UpdateReadme
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-CsvPerformance.ps1 `
    -Suite Smoke `
    -RowCount 100000 `
    -Scenario csv-dbatools-quick-single-column,csv-dbatools-quick-all-columns `
    -Engine PSWriteOffice,NativeCsv
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-CsvPerformance.ps1 `
    -Suite Smoke `
    -RowCount 100000 `
    -Scenario csv-dbatools-wide-single-column,csv-dbatools-wide-all-columns,csv-dbatools-quoted-single-column,csv-dbatools-quoted-all-columns `
    -Engine PSWriteOffice,NativeCsv
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-CsvPerformance.ps1 `
    -Suite Smoke `
    -RowCount 10000,100000 `
    -Scenario csv-write-datatable `
    -Engine PSWriteOffice,NativeCsv
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-CsvPerformance.ps1 `
    -Suite Smoke `
    -RowCount 10000,100000 `
    -Scenario csv-read-datatable-mixed,csv-read-datatable-wide `
    -Engine PSWriteOffice,NativeCsv
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-CsvPerformance.ps1 `
    -Suite Smoke `
    -RowCount 10000,100000 `
    -Scenario csv-write-gzip-mixed,csv-read-gzip-datatable-mixed,csv-write-gzip-wide,csv-read-gzip-datatable-wide `
    -Engine PSWriteOffice,NativeCsv

Focused local dbatools QuickTest-shaped run, 20260708-053630-00691048, 100,000 rows, 10 columns, three measured iterations:

ScenarioPSWriteOfficeNativeCsvResult
First column read341.3 ms, 294,869 rows/s347.3 ms, 285,460 rows/sPSWriteOffice fastest
All columns read1.08 s, 92,385 rows/s1.12 s, 89,942 rows/sPSWriteOffice fastest

Focused local DataTable CSV write run, 20260708-060903-2e761e92, five measured iterations:

RowsPSWriteOffice medianNativeCsv medianResult
10,00059.8 ms42.2 msNativeCsv fastest
100,00043.9 ms301.5 msPSWriteOffice fastest

Focused local dbatools wide/quoted run, 20260708-072202-eb2e9470, 10,000 rows, three measured iterations:

ScenarioPSWriteOfficeNativeCsvResult
Quoted all columns119.8 ms, 81,573 rows/s127.0 ms, 76,753 rows/sPSWriteOffice fastest
Quoted first column44.8 ms, 222,418 rows/s48.7 ms, 205,266 rows/sPSWriteOffice fastest
Wide all columns643.9 ms, 15,344 rows/s651.9 ms, 15,291 rows/sPSWriteOffice fastest
Wide first column99.6 ms, 98,934 rows/s124.5 ms, 80,758 rows/sPSWriteOffice fastest

Focused local DataTable CSV read run, 20260708-100615-e9dbaa80, 100,000 rows, three measured iterations. The native baseline reads with Import-Csv and fills a DataTable, so this compares the table/database workflow rather than only PowerShell object output:

ScenarioRowsPSWriteOfficeNativeCsvResult
csv-read-datatable-mixed100000219.6 ms (1.00x)2.57 s (11.72x slower)PSWriteOffice fastest
csv-read-datatable-wide1000001.60 s (1.00x)10.85 s (6.78x slower)PSWriteOffice fastest

Focused local GZip CSV run, generated by the benchmark updater. The write lanes validate by decompressing and counting rows; the read lanes validate the returned DataTable row count.

ScenarioRowsPSWriteOfficeNativeCsvResult
csv-read-gzip-datatable-mixed1000077.5 ms (1.00x)205.2 ms (2.65x slower)PSWriteOffice fastest
csv-read-gzip-datatable-wide1000045.2 ms (1.00x)706.0 ms (15.63x slower)PSWriteOffice fastest
csv-write-gzip-mixed1000086.7 ms (1.00x)192.1 ms (2.22x slower)PSWriteOffice fastest
csv-write-gzip-wide10000118.8 ms (1.00x)397.7 ms (3.35x slower)PSWriteOffice fastest

The 100,000-row values pool two independent 20-iteration runs for each operation.

ScenarioRowsPSWriteOfficeNativeCsvResult
csv-read-source-mixed100000471.7 ms (1.00x)554.4 ms (1.18x slower)PSWriteOffice fastest
csv-write-mixed100000209.0 ms (1.00x)225.2 ms (1.08x slower)PSWriteOffice fastest

The same 100,000-row object import comparison across other file shapes:

ShapePSWriteOfficeNativeCsvResult
Multiline472.1 ms546.7 ms16% faster
Quoted502.5 ms594.2 ms18% faster
Wide, 40 columns1.84 s2.27 s24% faster

Options

The wrappers build PSWriteOffice in Release mode by default and import local development binaries when a selected run includes PSWriteOffice. Use -PSWriteOfficeConfiguration Debug for diagnostics or -SkipPSWriteOfficeBuild when reusing an existing local build. Quick and focused runs leave this README unchanged unless -UpdateReadme is specified.

PSWriteOffice benchmark lanes require the current OfficeIMO source tree. Pass -OfficeIMORoot, or set the OfficeIMORoot environment variable, so the build uses direct project references. The wrappers stop instead of falling back to a published OfficeIMO package when the source path is missing:

$evotecRoot = if ($env:EVOTEC_GITHUB_ROOT) { $env:EVOTEC_GITHUB_ROOT } else { 'C:\Support\GitHub' }
pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\Benchmarks\Compare-ExcelPerformance.ps1 `
    -Suite Standard `
    -OfficeIMORoot (Join-Path $evotecRoot 'OfficeIMO')

Output

Benchmark artifacts are written under Ignore\Benchmarks\ExcelPerformance and Ignore\Benchmarks\CsvPerformance:

  • samples.json / samples.csv
  • summary.json / summary.csv
  • comparison.json / comparison.md
  • metadata.json
  • run-report.json

Start with comparison.md or the generated README table for the comparison, then use samples.csv when diagnosing individual failures.