cmd/benchstat: add -csv option for CSV output

The CSV output can more easily be imported into programs
like Sheets, Excel, LibreOffice, SPSS, etc. without any
ad hoc processing of the formatted output.

Includes tests.

Change-Id: I29e2fe6e4707932f1dee0a035d7b2d4a699567f8
Reviewed-on: https://go-review.googlesource.com/c/perf/+/158017
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
diff --git a/benchstat/text.go b/benchstat/text.go
index 99ec736..55b7a26 100644
--- a/benchstat/text.go
+++ b/benchstat/text.go
@@ -5,8 +5,11 @@
 package benchstat
 
 import (
+	"encoding/csv"
 	"fmt"
 	"io"
+	"path/filepath"
+	"strings"
 	"unicode/utf8"
 )
 
@@ -77,6 +80,90 @@
 	}
 }
 
+func must(e error) {
+	if e != nil {
+		panic(e)
+	}
+}
+
+// FormatCSV appends a CSV formatting of the tables to w.
+// norange suppresses the range columns.
+func FormatCSV(w io.Writer, tables []*Table, norange bool) {
+	var textTables [][]*textRow
+	for _, t := range tables {
+		textTables = append(textTables, toCSV(t, norange))
+	}
+
+	for i, table := range textTables {
+		if i > 0 {
+			fmt.Fprintf(w, "\n")
+		}
+		csvw := csv.NewWriter(w)
+
+		// headings
+		row := table[0]
+		must(csvw.Write(row.cols))
+
+		// data
+		for _, row := range table[1:] {
+			must(csvw.Write(row.cols))
+		}
+		csvw.Flush()
+	}
+}
+
+// trimCommonPathPrefix returns a string slice with common
+// path-separator-terminated prefixes removed.  Empty strings
+// are ignored and a singleton non-empty string is left unchanged
+func trimCommonPathPrefix(ss []string) []string {
+	commonPrefixLen := -1
+	commonPrefix := ""
+	trimCommonPrefix := false // true if more than one data row w/ non-empty title
+
+	// begin finding common byte prefix (could end on partial rune)
+	for _, s := range ss {
+		if s == "" {
+			continue
+		}
+		if commonPrefixLen == -1 {
+			commonPrefix = s
+			commonPrefixLen = len(s)
+			continue
+		}
+		trimCommonPrefix = true // More than one not-empty
+		if commonPrefixLen > len(s) {
+			commonPrefixLen = len(s)
+			commonPrefix = commonPrefix[0:commonPrefixLen]
+		}
+		for j := 0; j < commonPrefixLen; j++ {
+			if commonPrefix[j] != s[j] {
+				commonPrefixLen = j
+				commonPrefix = commonPrefix[0:commonPrefixLen]
+				break
+			}
+		}
+	}
+	if !trimCommonPrefix || commonPrefixLen == 0 {
+		return ss
+	}
+	// trim to include last path separator.
+	commonPrefixLen = 1 + strings.LastIndex(commonPrefix, string(filepath.Separator))
+	if commonPrefixLen == 0 {
+		// No separator found means commonPrefixLen = 1 + (-1) == 0
+		return ss
+	}
+
+	rs := []string{}
+	for _, s := range ss {
+		if s == "" {
+			rs = append(rs, "")
+		} else {
+			rs = append(rs, s[commonPrefixLen:])
+		}
+	}
+	return rs
+}
+
 // A textRow is a row of printed text columns.
 type textRow struct {
 	cols []string
@@ -86,6 +173,20 @@
 	return &textRow{cols: cols}
 }
 
+// newTextRowDelta returns a labeled row of text, with "±" inserted after
+// each member of "cols" unless norange is true.
+func newTextRowDelta(norange bool, label string, cols ...string) *textRow {
+	newcols := []string{}
+	newcols = append(newcols, label)
+	for _, s := range cols {
+		newcols = append(newcols, s)
+		if !norange {
+			newcols = append(newcols, "±")
+		}
+	}
+	return &textRow{cols: newcols}
+}
+
 func (r *textRow) add(col string) {
 	r.cols = append(r.cols, col)
 }
@@ -107,7 +208,7 @@
 		textRows = append(textRows, newTextRow("name", "old "+t.Metric, "new "+t.Metric, "delta"))
 	default:
 		row := newTextRow("name \\ " + t.Metric)
-		row.cols = append(row.cols, t.Configs...)
+		row.cols = append(row.cols, t.Configs...) // TODO Should this also trim common path prefix? (see toCSV)
 		textRows = append(textRows, row)
 	}
 
@@ -137,3 +238,56 @@
 	}
 	return textRows
 }
+
+// toCSV converts the Table to a slice of rows containing CSV-separated data
+// If norange is true, suppress the range information for each data item.
+// If norange is false, insert a "±" in the appropriate columns of the header row.
+func toCSV(t *Table, norange bool) []*textRow {
+	var textRows []*textRow
+	units := ""
+	if len(t.Rows) > 0 && len(t.Rows[0].Metrics) > 0 {
+		units = " (" + t.Rows[0].Metrics[0].Unit + ")"
+	}
+	switch len(t.Configs) {
+	case 1:
+		textRows = append(textRows, newTextRowDelta(norange, "name", t.Metric+units))
+	case 2:
+		textRows = append(textRows, newTextRowDelta(norange, "name", "old "+t.Metric+units, "new "+t.Metric+units, "delta"))
+	default:
+		rowname := "name \\ " + t.Metric
+		row := newTextRowDelta(norange, rowname+units, trimCommonPathPrefix(t.Configs)...)
+		textRows = append(textRows, row)
+	}
+
+	var group string
+
+	for _, row := range t.Rows {
+		if row.Group != group {
+			group = row.Group
+			textRows = append(textRows, newTextRow(group))
+		}
+		text := newTextRow(row.Benchmark)
+		for _, m := range row.Metrics {
+			mean := fmt.Sprintf("%.5E", m.Mean)
+			diff := m.FormatDiff()
+			if m.Unit == "" {
+				mean = ""
+				diff = ""
+			}
+			text.cols = append(text.cols, mean)
+			if !norange {
+				text.cols = append(text.cols, diff)
+			}
+		}
+		if len(t.Configs) == 2 {
+			delta := row.Delta
+			text.cols = append(text.cols, delta)
+			text.cols = append(text.cols, row.Note)
+		}
+		textRows = append(textRows, text)
+	}
+	for _, r := range textRows {
+		r.trim()
+	}
+	return textRows
+}
diff --git a/cmd/benchstat/main.go b/cmd/benchstat/main.go
index 4b27d7f..b882c06 100644
--- a/cmd/benchstat/main.go
+++ b/cmd/benchstat/main.go
@@ -119,6 +119,8 @@
 	flagAlpha     = flag.Float64("alpha", 0.05, "consider change significant if p < `α`")
 	flagGeomean   = flag.Bool("geomean", false, "print the geometric mean of each file")
 	flagHTML      = flag.Bool("html", false, "print results as an HTML table")
+	flagCSV       = flag.Bool("csv", false, "print results in CSV form")
+	flagNoRange   = flag.Bool("norange", false, "suppress range columns (CSV only)")
 	flagSplit     = flag.String("split", "pkg,goos,goarch", "split benchmarks by `labels`")
 	flagSort      = flag.String("sort", "none", "sort by `order`: [-]delta, [-]name, none")
 )
@@ -187,6 +189,8 @@
 		buf.WriteString(htmlHeader)
 		benchstat.FormatHTML(&buf, tables)
 		buf.WriteString(htmlFooter)
+	} else if *flagCSV {
+		benchstat.FormatCSV(&buf, tables, *flagNoRange)
 	} else {
 		benchstat.FormatText(&buf, tables)
 	}
diff --git a/cmd/benchstat/main_test.go b/cmd/benchstat/main_test.go
index 29419ee..ed8aba9 100644
--- a/cmd/benchstat/main_test.go
+++ b/cmd/benchstat/main_test.go
@@ -10,9 +10,14 @@
 	"io/ioutil"
 	"os"
 	"os/exec"
+	"path/filepath"
 	"testing"
 )
 
+func dotSlash(s string) string {
+	return "." + string(filepath.Separator) + s
+}
+
 func TestGolden(t *testing.T) {
 	if err := os.Chdir("testdata"); err != nil {
 		t.Fatal(err)
@@ -43,6 +48,15 @@
 	check(t, "namesort", "-sort=name", "old.txt", "new.txt")
 	check(t, "deltasort", "-sort=delta", "old.txt", "new.txt")
 	check(t, "rdeltasort", "-sort=-delta", "old.txt", "new.txt")
+
+	check(t, "oldcsv", "-geomean", "-csv", "old.txt")
+	check(t, "allcsv", "-geomean", "-csv", "old.txt", "new.txt", "slashslash4.txt", "x386.txt")
+	check(t, "allnosplitcsv", "-geomean", "-csv", "-split", "", dotSlash("new.txt"), dotSlash("old.txt"), dotSlash("slashslash4.txt"), dotSlash("x386.txt")) // note order: new old slashslash4 x386; dotSlash tests common prefix removal
+
+	if dotSlash("x") == "./x" { // Golden files have hardcoded "/" path separators in them
+		check(t, "allnorangecsv", "-geomean", "-csv", "-norange", dotSlash("old.txt"), dotSlash("new.txt"), "slashslash4.txt", "x386.txt") // Mixed ./ tests common prefix non-removal
+	}
+
 }
 
 func check(t *testing.T, name string, files ...string) {
@@ -68,6 +82,7 @@
 		exit = func(code int) { t.Fatalf("exit %d during main", code) }
 		*flagGeomean = false
 		*flagHTML = false
+		*flagNoRange = false
 		*flagDeltaTest = "utest"
 		*flagSplit = flag.Lookup("split").DefValue
 
@@ -95,7 +110,8 @@
 	if len(data) > 0 {
 		return string(data)
 	}
-	return "ERROR: " + err.Error()
+	// Most likely, "diff not found" so print the bad output so there is something.
+	return "ERROR: " + err.Error() + ": test output = \n" + string(old)
 }
 
 func writeTemp(t *testing.T, data []byte) string {
diff --git a/cmd/benchstat/testdata/allcsv.golden b/cmd/benchstat/testdata/allcsv.golden
new file mode 100644
index 0000000..7480ef2
--- /dev/null
+++ b/cmd/benchstat/testdata/allcsv.golden
@@ -0,0 +1,155 @@
+name \ time/op (ns/op),old.txt,±,new.txt,±,slashslash4.txt,±,x386.txt,±
+pkg:hash/crc32 goos:darwin goarch:amd64
+CRC32/poly=IEEE/size=15/align=0-8,4.68700E+01,8%,4.45200E+01,3%
+CRC32/poly=IEEE/size=15/align=1-8,4.47100E+01,5%,4.45000E+01,4%
+CRC32/poly=IEEE/size=40/align=0-8,4.10375E+01,1%,4.25000E+01,6%,4.20800E+01,3%
+CRC32/poly=IEEE/size=40/align=1-8,4.10778E+01,1%,4.20400E+01,3%,4.16800E+01,5%
+CRC32/poly=IEEE/size=512/align=0-8,2.38000E+02,5%,5.71200E+01,3%
+CRC32/poly=IEEE/size=512/align=1-8,2.35500E+02,3%,5.71700E+01,3%
+CRC32/poly=IEEE/size=1kB/align=0-8,4.52500E+02,4%,9.41125E+01,2%
+CRC32/poly=IEEE/size=1kB/align=1-8,4.43600E+02,2%,9.32875E+01,2%
+CRC32/poly=IEEE/size=4kB/align=0-8,1.74000E+03,8%,2.98111E+02,1%,1.68200E+03,2%
+CRC32/poly=IEEE/size=4kB/align=1-8,1.76430E+03,6%,2.99100E+02,3%,1.68980E+03,4%
+CRC32/poly=IEEE/size=32kB/align=0-8,1.49529E+04,7%,2.15800E+03,3%
+CRC32/poly=IEEE/size=32kB/align=1-8,1.41888E+04,7%,2.17830E+03,3%
+CRC32/poly=Castagnoli/size=15/align=0-8,1.63778E+01,3%,1.63000E+01,2%
+CRC32/poly=Castagnoli/size=15/align=1-8,1.72222E+01,2%,1.72900E+01,2%
+CRC32/poly=Castagnoli/size=40/align=0-8,1.74300E+01,2%,1.75300E+01,4%,1.85800E+01,11%
+CRC32/poly=Castagnoli/size=40/align=1-8,1.97100E+01,3%,1.93900E+01,2%,1.96375E+01,2%
+CRC32/poly=Castagnoli/size=512/align=0-8,4.01700E+01,2%,4.01300E+01,4%
+CRC32/poly=Castagnoli/size=512/align=1-8,4.21400E+01,3%,4.19444E+01,2%
+CRC32/poly=Castagnoli/size=1kB/align=0-8,6.55000E+01,1%,6.61625E+01,1%
+CRC32/poly=Castagnoli/size=1kB/align=1-8,7.00900E+01,6%,6.84667E+01,2%
+CRC32/poly=Castagnoli/size=4kB/align=0-8,1.62800E+02,5%,1.58800E+02,3%,1.61200E+02,8%
+CRC32/poly=Castagnoli/size=4kB/align=1-8,1.69400E+02,6%,1.61600E+02,3%,1.69600E+02,8%
+CRC32/poly=Castagnoli/size=32kB/align=0-8,1.21822E+03,4%,1.21433E+03,3%
+CRC32/poly=Castagnoli/size=32kB/align=1-8,1.26478E+03,3%,1.22080E+03,4%
+CRC32/poly=Koopman/size=15/align=0-8,3.65100E+01,11%,3.56000E+01,3%
+CRC32/poly=Koopman/size=15/align=1-8,3.51500E+01,5%,3.55111E+01,1%
+CRC32/poly=Koopman/size=40/align=0-8,9.16400E+01,9%,8.76500E+01,2%,9.38100E+01,13%
+CRC32/poly=Koopman/size=40/align=1-8,9.10800E+01,6%,8.80300E+01,3%,8.68600E+01,3%
+CRC32/poly=Koopman/size=512/align=0-8,1.13170E+03,5%,1.07590E+03,3%
+CRC32/poly=Koopman/size=512/align=1-8,1.12680E+03,6%,1.16660E+03,8%
+CRC32/poly=Koopman/size=1kB/align=0-8,2.24333E+03,6%,2.34070E+03,4%
+CRC32/poly=Koopman/size=1kB/align=1-8,2.14867E+03,2%,2.36010E+03,5%
+CRC32/poly=Koopman/size=4kB/align=0-8,9.03150E+03,6%,9.00320E+03,6%,9.08160E+03,8%
+CRC32/poly=Koopman/size=4kB/align=1-8,8.94020E+03,10%,9.04630E+03,12%,9.45520E+03,8%
+CRC32/poly=Koopman/size=32kB/align=0-8,7.24280E+04,9%,7.29005E+04,4%
+CRC32/poly=Koopman/size=32kB/align=1-8,6.96194E+04,3%,7.42809E+04,3%
+pkg:hash/crc32 goos:darwin goarch:386
+CRC32/poly=IEEE/size=15/align=0-8,,,,,,,6.23500E+01,9%
+CRC32/poly=IEEE/size=15/align=1-8,,,,,,,6.34900E+01,8%
+CRC32/poly=IEEE/size=40/align=0-8,,,,,,,5.74400E+01,3%
+CRC32/poly=IEEE/size=40/align=1-8,,,,,,,5.73300E+01,3%
+CRC32/poly=IEEE/size=512/align=0-8,,,,,,,3.32333E+02,2%
+CRC32/poly=IEEE/size=512/align=1-8,,,,,,,3.35300E+02,3%
+CRC32/poly=IEEE/size=1kB/align=0-8,,,,,,,6.26300E+02,2%
+CRC32/poly=IEEE/size=1kB/align=1-8,,,,,,,6.35300E+02,6%
+CRC32/poly=IEEE/size=4kB/align=0-8,,,,,,,2.45670E+03,7%
+CRC32/poly=IEEE/size=4kB/align=1-8,,,,,,,2.43450E+03,5%
+CRC32/poly=IEEE/size=32kB/align=0-8,,,,,,,1.91499E+04,4%
+CRC32/poly=IEEE/size=32kB/align=1-8,,,,,,,1.94203E+04,5%
+CRC32/poly=Castagnoli/size=15/align=0-8,,,,,,,5.93625E+01,1%
+CRC32/poly=Castagnoli/size=15/align=1-8,,,,,,,5.93500E+01,2%
+CRC32/poly=Castagnoli/size=40/align=0-8,,,,,,,5.96600E+01,6%
+CRC32/poly=Castagnoli/size=40/align=1-8,,,,,,,5.81100E+01,7%
+CRC32/poly=Castagnoli/size=512/align=0-8,,,,,,,3.50800E+02,5%
+CRC32/poly=Castagnoli/size=512/align=1-8,,,,,,,3.49800E+02,7%
+CRC32/poly=Castagnoli/size=1kB/align=0-8,,,,,,,6.56700E+02,4%
+CRC32/poly=Castagnoli/size=1kB/align=1-8,,,,,,,6.56800E+02,6%
+CRC32/poly=Castagnoli/size=4kB/align=0-8,,,,,,,2.45180E+03,4%
+CRC32/poly=Castagnoli/size=4kB/align=1-8,,,,,,,2.44800E+03,5%
+CRC32/poly=Castagnoli/size=32kB/align=0-8,,,,,,,2.05261E+04,5%
+CRC32/poly=Castagnoli/size=32kB/align=1-8,,,,,,,2.01794E+04,9%
+CRC32/poly=Koopman/size=15/align=0-8,,,,,,,5.82100E+01,4%
+CRC32/poly=Koopman/size=15/align=1-8,,,,,,,5.64000E+01,7%
+CRC32/poly=Koopman/size=40/align=0-8,,,,,,,1.42300E+02,8%
+CRC32/poly=Koopman/size=40/align=1-8,,,,,,,1.36222E+02,5%
+CRC32/poly=Koopman/size=512/align=0-8,,,,,,,1.68440E+03,5%
+CRC32/poly=Koopman/size=512/align=1-8,,,,,,,1.64290E+03,4%
+CRC32/poly=Koopman/size=1kB/align=0-8,,,,,,,3.30890E+03,4%
+CRC32/poly=Koopman/size=1kB/align=1-8,,,,,,,3.27920E+03,3%
+CRC32/poly=Koopman/size=4kB/align=0-8,,,,,,,1.33243E+04,3%
+CRC32/poly=Koopman/size=4kB/align=1-8,,,,,,,1.31612E+04,3%
+CRC32/poly=Koopman/size=32kB/align=0-8,,,,,,,1.06891E+05,6%
+CRC32/poly=Koopman/size=32kB/align=1-8,,,,,,,1.06069E+05,4%
+
+[Geo mean],3.44668E+02,,2.37855E+02,,2.39033E+02,,8.74025E+02
+
+name \ speed (MB/s),old.txt,±,new.txt,±,slashslash4.txt,±,x386.txt,±
+pkg:hash/crc32 goos:darwin goarch:amd64
+CRC32/poly=IEEE/size=15/align=0-8,3.20711E+02,8%,3.36950E+02,3%
+CRC32/poly=IEEE/size=15/align=1-8,3.35516E+02,4%,3.37066E+02,4%
+CRC32/poly=IEEE/size=40/align=0-8,9.74718E+02,1%,9.41823E+02,5%,9.50988E+02,3%
+CRC32/poly=IEEE/size=40/align=1-8,9.73636E+02,1%,9.51759E+02,3%,9.59978E+02,4%
+CRC32/poly=IEEE/size=512/align=0-8,2.14703E+03,4%,8.96715E+03,3%
+CRC32/poly=IEEE/size=512/align=1-8,2.16913E+03,3%,8.95606E+03,3%
+CRC32/poly=IEEE/size=1kB/align=0-8,2.26152E+03,4%,1.08807E+04,2%
+CRC32/poly=IEEE/size=1kB/align=1-8,2.30619E+03,2%,1.09768E+04,2%
+CRC32/poly=IEEE/size=4kB/align=0-8,2.35732E+03,7%,1.37258E+04,1%,2.43489E+03,2%
+CRC32/poly=IEEE/size=4kB/align=1-8,2.32511E+03,6%,1.36770E+04,3%,2.42393E+03,4%
+CRC32/poly=IEEE/size=32kB/align=0-8,2.19443E+03,7%,1.51852E+04,3%
+CRC32/poly=IEEE/size=32kB/align=1-8,2.31415E+03,8%,1.50437E+04,3%
+CRC32/poly=Castagnoli/size=15/align=0-8,9.15799E+02,2%,9.20433E+02,2%
+CRC32/poly=Castagnoli/size=15/align=1-8,8.70312E+02,2%,8.67298E+02,2%
+CRC32/poly=Castagnoli/size=40/align=0-8,2.29560E+03,2%,2.28265E+03,4%,2.16074E+03,11%
+CRC32/poly=Castagnoli/size=40/align=1-8,2.03023E+03,3%,2.06346E+03,2%,2.03587E+03,2%
+CRC32/poly=Castagnoli/size=512/align=0-8,1.27437E+04,2%,1.27578E+04,4%
+CRC32/poly=Castagnoli/size=512/align=1-8,1.21445E+04,3%,1.22049E+04,1%
+CRC32/poly=Castagnoli/size=1kB/align=0-8,1.56355E+04,1%,1.54766E+04,1%
+CRC32/poly=Castagnoli/size=1kB/align=1-8,1.46273E+04,6%,1.49597E+04,2%
+CRC32/poly=Castagnoli/size=4kB/align=0-8,2.50862E+04,5%,2.56897E+04,3%,2.53717E+04,7%
+CRC32/poly=Castagnoli/size=4kB/align=1-8,2.41378E+04,6%,2.52736E+04,3%,2.41249E+04,8%
+CRC32/poly=Castagnoli/size=32kB/align=0-8,2.68975E+04,4%,2.68232E+04,5%
+CRC32/poly=Castagnoli/size=32kB/align=1-8,2.59038E+04,3%,2.68422E+04,4%
+CRC32/poly=Koopman/size=15/align=0-8,4.11932E+02,10%,4.21452E+02,3%
+CRC32/poly=Koopman/size=15/align=1-8,4.27408E+02,5%,4.22362E+02,1%
+CRC32/poly=Koopman/size=40/align=0-8,4.36831E+02,9%,4.56472E+02,2%,4.28261E+02,12%
+CRC32/poly=Koopman/size=40/align=1-8,4.39731E+02,6%,4.54515E+02,3%,4.60660E+02,3%
+CRC32/poly=Koopman/size=512/align=0-8,4.52693E+02,5%,4.75749E+02,3%
+CRC32/poly=Koopman/size=512/align=1-8,4.54579E+02,6%,4.39685E+02,8%
+CRC32/poly=Koopman/size=1kB/align=0-8,4.52443E+02,9%,4.37629E+02,4%
+CRC32/poly=Koopman/size=1kB/align=1-8,4.76558E+02,2%,4.34042E+02,5%
+CRC32/poly=Koopman/size=4kB/align=0-8,4.54022E+02,5%,4.55492E+02,6%,4.52301E+02,8%
+CRC32/poly=Koopman/size=4kB/align=1-8,4.59394E+02,9%,4.54627E+02,11%,4.33984E+02,9%
+CRC32/poly=Koopman/size=32kB/align=0-8,4.53471E+02,8%,4.49828E+02,4%
+CRC32/poly=Koopman/size=32kB/align=1-8,4.70784E+02,3%,4.41379E+02,3%
+pkg:hash/crc32 goos:darwin goarch:386
+CRC32/poly=IEEE/size=15/align=0-8,,,,,,,2.41180E+02,8%
+CRC32/poly=IEEE/size=15/align=1-8,,,,,,,2.36728E+02,8%
+CRC32/poly=IEEE/size=40/align=0-8,,,,,,,6.96493E+02,3%
+CRC32/poly=IEEE/size=40/align=1-8,,,,,,,6.97741E+02,3%
+CRC32/poly=IEEE/size=512/align=0-8,,,,,,,1.53793E+03,2%
+CRC32/poly=IEEE/size=512/align=1-8,,,,,,,1.52435E+03,3%
+CRC32/poly=IEEE/size=1kB/align=0-8,,,,,,,1.63354E+03,2%
+CRC32/poly=IEEE/size=1kB/align=1-8,,,,,,,1.61191E+03,6%
+CRC32/poly=IEEE/size=4kB/align=0-8,,,,,,,1.66842E+03,7%
+CRC32/poly=IEEE/size=4kB/align=1-8,,,,,,,1.68370E+03,5%
+CRC32/poly=IEEE/size=32kB/align=0-8,,,,,,,1.71179E+03,4%
+CRC32/poly=IEEE/size=32kB/align=1-8,,,,,,,1.68829E+03,5%
+CRC32/poly=Castagnoli/size=15/align=0-8,,,,,,,2.52761E+02,1%
+CRC32/poly=Castagnoli/size=15/align=1-8,,,,,,,2.52790E+02,2%
+CRC32/poly=Castagnoli/size=40/align=0-8,,,,,,,6.71281E+02,6%
+CRC32/poly=Castagnoli/size=40/align=1-8,,,,,,,6.89073E+02,6%
+CRC32/poly=Castagnoli/size=512/align=0-8,,,,,,,1.45840E+03,5%
+CRC32/poly=Castagnoli/size=512/align=1-8,,,,,,,1.46342E+03,7%
+CRC32/poly=Castagnoli/size=1kB/align=0-8,,,,,,,1.55922E+03,4%
+CRC32/poly=Castagnoli/size=1kB/align=1-8,,,,,,,1.55926E+03,5%
+CRC32/poly=Castagnoli/size=4kB/align=0-8,,,,,,,1.67078E+03,4%
+CRC32/poly=Castagnoli/size=4kB/align=1-8,,,,,,,1.67399E+03,5%
+CRC32/poly=Castagnoli/size=32kB/align=0-8,,,,,,,1.59801E+03,5%
+CRC32/poly=Castagnoli/size=32kB/align=1-8,,,,,,,1.62604E+03,8%
+CRC32/poly=Koopman/size=15/align=0-8,,,,,,,2.57893E+02,5%
+CRC32/poly=Koopman/size=15/align=1-8,,,,,,,2.66301E+02,6%
+CRC32/poly=Koopman/size=40/align=0-8,,,,,,,2.80521E+02,7%
+CRC32/poly=Koopman/size=40/align=1-8,,,,,,,2.89866E+02,8%
+CRC32/poly=Koopman/size=512/align=0-8,,,,,,,3.04127E+02,5%
+CRC32/poly=Koopman/size=512/align=1-8,,,,,,,3.11609E+02,3%
+CRC32/poly=Koopman/size=1kB/align=0-8,,,,,,,3.09621E+02,4%
+CRC32/poly=Koopman/size=1kB/align=1-8,,,,,,,3.12318E+02,3%
+CRC32/poly=Koopman/size=4kB/align=0-8,,,,,,,3.07516E+02,3%
+CRC32/poly=Koopman/size=4kB/align=1-8,,,,,,,3.11292E+02,3%
+CRC32/poly=Koopman/size=32kB/align=0-8,,,,,,,3.06816E+02,6%
+CRC32/poly=Koopman/size=32kB/align=1-8,,,,,,,3.09085E+02,4%
+
+[Geo mean],1.71221E+03,,2.48066E+03,,1.69486E+03,,6.75103E+02
diff --git a/cmd/benchstat/testdata/allnorangecsv.golden b/cmd/benchstat/testdata/allnorangecsv.golden
new file mode 100644
index 0000000..3916a37
--- /dev/null
+++ b/cmd/benchstat/testdata/allnorangecsv.golden
@@ -0,0 +1,155 @@
+name \ time/op (ns/op),./old.txt,./new.txt,slashslash4.txt,x386.txt
+pkg:hash/crc32 goos:darwin goarch:amd64
+CRC32/poly=IEEE/size=15/align=0-8,4.68700E+01,4.45200E+01
+CRC32/poly=IEEE/size=15/align=1-8,4.47100E+01,4.45000E+01
+CRC32/poly=IEEE/size=40/align=0-8,4.10375E+01,4.25000E+01,4.20800E+01
+CRC32/poly=IEEE/size=40/align=1-8,4.10778E+01,4.20400E+01,4.16800E+01
+CRC32/poly=IEEE/size=512/align=0-8,2.38000E+02,5.71200E+01
+CRC32/poly=IEEE/size=512/align=1-8,2.35500E+02,5.71700E+01
+CRC32/poly=IEEE/size=1kB/align=0-8,4.52500E+02,9.41125E+01
+CRC32/poly=IEEE/size=1kB/align=1-8,4.43600E+02,9.32875E+01
+CRC32/poly=IEEE/size=4kB/align=0-8,1.74000E+03,2.98111E+02,1.68200E+03
+CRC32/poly=IEEE/size=4kB/align=1-8,1.76430E+03,2.99100E+02,1.68980E+03
+CRC32/poly=IEEE/size=32kB/align=0-8,1.49529E+04,2.15800E+03
+CRC32/poly=IEEE/size=32kB/align=1-8,1.41888E+04,2.17830E+03
+CRC32/poly=Castagnoli/size=15/align=0-8,1.63778E+01,1.63000E+01
+CRC32/poly=Castagnoli/size=15/align=1-8,1.72222E+01,1.72900E+01
+CRC32/poly=Castagnoli/size=40/align=0-8,1.74300E+01,1.75300E+01,1.85800E+01
+CRC32/poly=Castagnoli/size=40/align=1-8,1.97100E+01,1.93900E+01,1.96375E+01
+CRC32/poly=Castagnoli/size=512/align=0-8,4.01700E+01,4.01300E+01
+CRC32/poly=Castagnoli/size=512/align=1-8,4.21400E+01,4.19444E+01
+CRC32/poly=Castagnoli/size=1kB/align=0-8,6.55000E+01,6.61625E+01
+CRC32/poly=Castagnoli/size=1kB/align=1-8,7.00900E+01,6.84667E+01
+CRC32/poly=Castagnoli/size=4kB/align=0-8,1.62800E+02,1.58800E+02,1.61200E+02
+CRC32/poly=Castagnoli/size=4kB/align=1-8,1.69400E+02,1.61600E+02,1.69600E+02
+CRC32/poly=Castagnoli/size=32kB/align=0-8,1.21822E+03,1.21433E+03
+CRC32/poly=Castagnoli/size=32kB/align=1-8,1.26478E+03,1.22080E+03
+CRC32/poly=Koopman/size=15/align=0-8,3.65100E+01,3.56000E+01
+CRC32/poly=Koopman/size=15/align=1-8,3.51500E+01,3.55111E+01
+CRC32/poly=Koopman/size=40/align=0-8,9.16400E+01,8.76500E+01,9.38100E+01
+CRC32/poly=Koopman/size=40/align=1-8,9.10800E+01,8.80300E+01,8.68600E+01
+CRC32/poly=Koopman/size=512/align=0-8,1.13170E+03,1.07590E+03
+CRC32/poly=Koopman/size=512/align=1-8,1.12680E+03,1.16660E+03
+CRC32/poly=Koopman/size=1kB/align=0-8,2.24333E+03,2.34070E+03
+CRC32/poly=Koopman/size=1kB/align=1-8,2.14867E+03,2.36010E+03
+CRC32/poly=Koopman/size=4kB/align=0-8,9.03150E+03,9.00320E+03,9.08160E+03
+CRC32/poly=Koopman/size=4kB/align=1-8,8.94020E+03,9.04630E+03,9.45520E+03
+CRC32/poly=Koopman/size=32kB/align=0-8,7.24280E+04,7.29005E+04
+CRC32/poly=Koopman/size=32kB/align=1-8,6.96194E+04,7.42809E+04
+pkg:hash/crc32 goos:darwin goarch:386
+CRC32/poly=IEEE/size=15/align=0-8,,,,6.23500E+01
+CRC32/poly=IEEE/size=15/align=1-8,,,,6.34900E+01
+CRC32/poly=IEEE/size=40/align=0-8,,,,5.74400E+01
+CRC32/poly=IEEE/size=40/align=1-8,,,,5.73300E+01
+CRC32/poly=IEEE/size=512/align=0-8,,,,3.32333E+02
+CRC32/poly=IEEE/size=512/align=1-8,,,,3.35300E+02
+CRC32/poly=IEEE/size=1kB/align=0-8,,,,6.26300E+02
+CRC32/poly=IEEE/size=1kB/align=1-8,,,,6.35300E+02
+CRC32/poly=IEEE/size=4kB/align=0-8,,,,2.45670E+03
+CRC32/poly=IEEE/size=4kB/align=1-8,,,,2.43450E+03
+CRC32/poly=IEEE/size=32kB/align=0-8,,,,1.91499E+04
+CRC32/poly=IEEE/size=32kB/align=1-8,,,,1.94203E+04
+CRC32/poly=Castagnoli/size=15/align=0-8,,,,5.93625E+01
+CRC32/poly=Castagnoli/size=15/align=1-8,,,,5.93500E+01
+CRC32/poly=Castagnoli/size=40/align=0-8,,,,5.96600E+01
+CRC32/poly=Castagnoli/size=40/align=1-8,,,,5.81100E+01
+CRC32/poly=Castagnoli/size=512/align=0-8,,,,3.50800E+02
+CRC32/poly=Castagnoli/size=512/align=1-8,,,,3.49800E+02
+CRC32/poly=Castagnoli/size=1kB/align=0-8,,,,6.56700E+02
+CRC32/poly=Castagnoli/size=1kB/align=1-8,,,,6.56800E+02
+CRC32/poly=Castagnoli/size=4kB/align=0-8,,,,2.45180E+03
+CRC32/poly=Castagnoli/size=4kB/align=1-8,,,,2.44800E+03
+CRC32/poly=Castagnoli/size=32kB/align=0-8,,,,2.05261E+04
+CRC32/poly=Castagnoli/size=32kB/align=1-8,,,,2.01794E+04
+CRC32/poly=Koopman/size=15/align=0-8,,,,5.82100E+01
+CRC32/poly=Koopman/size=15/align=1-8,,,,5.64000E+01
+CRC32/poly=Koopman/size=40/align=0-8,,,,1.42300E+02
+CRC32/poly=Koopman/size=40/align=1-8,,,,1.36222E+02
+CRC32/poly=Koopman/size=512/align=0-8,,,,1.68440E+03
+CRC32/poly=Koopman/size=512/align=1-8,,,,1.64290E+03
+CRC32/poly=Koopman/size=1kB/align=0-8,,,,3.30890E+03
+CRC32/poly=Koopman/size=1kB/align=1-8,,,,3.27920E+03
+CRC32/poly=Koopman/size=4kB/align=0-8,,,,1.33243E+04
+CRC32/poly=Koopman/size=4kB/align=1-8,,,,1.31612E+04
+CRC32/poly=Koopman/size=32kB/align=0-8,,,,1.06891E+05
+CRC32/poly=Koopman/size=32kB/align=1-8,,,,1.06069E+05
+
+[Geo mean],3.44668E+02,2.37855E+02,2.39033E+02,8.74025E+02
+
+name \ speed (MB/s),./old.txt,./new.txt,slashslash4.txt,x386.txt
+pkg:hash/crc32 goos:darwin goarch:amd64
+CRC32/poly=IEEE/size=15/align=0-8,3.20711E+02,3.36950E+02
+CRC32/poly=IEEE/size=15/align=1-8,3.35516E+02,3.37066E+02
+CRC32/poly=IEEE/size=40/align=0-8,9.74718E+02,9.41823E+02,9.50988E+02
+CRC32/poly=IEEE/size=40/align=1-8,9.73636E+02,9.51759E+02,9.59978E+02
+CRC32/poly=IEEE/size=512/align=0-8,2.14703E+03,8.96715E+03
+CRC32/poly=IEEE/size=512/align=1-8,2.16913E+03,8.95606E+03
+CRC32/poly=IEEE/size=1kB/align=0-8,2.26152E+03,1.08807E+04
+CRC32/poly=IEEE/size=1kB/align=1-8,2.30619E+03,1.09768E+04
+CRC32/poly=IEEE/size=4kB/align=0-8,2.35732E+03,1.37258E+04,2.43489E+03
+CRC32/poly=IEEE/size=4kB/align=1-8,2.32511E+03,1.36770E+04,2.42393E+03
+CRC32/poly=IEEE/size=32kB/align=0-8,2.19443E+03,1.51852E+04
+CRC32/poly=IEEE/size=32kB/align=1-8,2.31415E+03,1.50437E+04
+CRC32/poly=Castagnoli/size=15/align=0-8,9.15799E+02,9.20433E+02
+CRC32/poly=Castagnoli/size=15/align=1-8,8.70312E+02,8.67298E+02
+CRC32/poly=Castagnoli/size=40/align=0-8,2.29560E+03,2.28265E+03,2.16074E+03
+CRC32/poly=Castagnoli/size=40/align=1-8,2.03023E+03,2.06346E+03,2.03587E+03
+CRC32/poly=Castagnoli/size=512/align=0-8,1.27437E+04,1.27578E+04
+CRC32/poly=Castagnoli/size=512/align=1-8,1.21445E+04,1.22049E+04
+CRC32/poly=Castagnoli/size=1kB/align=0-8,1.56355E+04,1.54766E+04
+CRC32/poly=Castagnoli/size=1kB/align=1-8,1.46273E+04,1.49597E+04
+CRC32/poly=Castagnoli/size=4kB/align=0-8,2.50862E+04,2.56897E+04,2.53717E+04
+CRC32/poly=Castagnoli/size=4kB/align=1-8,2.41378E+04,2.52736E+04,2.41249E+04
+CRC32/poly=Castagnoli/size=32kB/align=0-8,2.68975E+04,2.68232E+04
+CRC32/poly=Castagnoli/size=32kB/align=1-8,2.59038E+04,2.68422E+04
+CRC32/poly=Koopman/size=15/align=0-8,4.11932E+02,4.21452E+02
+CRC32/poly=Koopman/size=15/align=1-8,4.27408E+02,4.22362E+02
+CRC32/poly=Koopman/size=40/align=0-8,4.36831E+02,4.56472E+02,4.28261E+02
+CRC32/poly=Koopman/size=40/align=1-8,4.39731E+02,4.54515E+02,4.60660E+02
+CRC32/poly=Koopman/size=512/align=0-8,4.52693E+02,4.75749E+02
+CRC32/poly=Koopman/size=512/align=1-8,4.54579E+02,4.39685E+02
+CRC32/poly=Koopman/size=1kB/align=0-8,4.52443E+02,4.37629E+02
+CRC32/poly=Koopman/size=1kB/align=1-8,4.76558E+02,4.34042E+02
+CRC32/poly=Koopman/size=4kB/align=0-8,4.54022E+02,4.55492E+02,4.52301E+02
+CRC32/poly=Koopman/size=4kB/align=1-8,4.59394E+02,4.54627E+02,4.33984E+02
+CRC32/poly=Koopman/size=32kB/align=0-8,4.53471E+02,4.49828E+02
+CRC32/poly=Koopman/size=32kB/align=1-8,4.70784E+02,4.41379E+02
+pkg:hash/crc32 goos:darwin goarch:386
+CRC32/poly=IEEE/size=15/align=0-8,,,,2.41180E+02
+CRC32/poly=IEEE/size=15/align=1-8,,,,2.36728E+02
+CRC32/poly=IEEE/size=40/align=0-8,,,,6.96493E+02
+CRC32/poly=IEEE/size=40/align=1-8,,,,6.97741E+02
+CRC32/poly=IEEE/size=512/align=0-8,,,,1.53793E+03
+CRC32/poly=IEEE/size=512/align=1-8,,,,1.52435E+03
+CRC32/poly=IEEE/size=1kB/align=0-8,,,,1.63354E+03
+CRC32/poly=IEEE/size=1kB/align=1-8,,,,1.61191E+03
+CRC32/poly=IEEE/size=4kB/align=0-8,,,,1.66842E+03
+CRC32/poly=IEEE/size=4kB/align=1-8,,,,1.68370E+03
+CRC32/poly=IEEE/size=32kB/align=0-8,,,,1.71179E+03
+CRC32/poly=IEEE/size=32kB/align=1-8,,,,1.68829E+03
+CRC32/poly=Castagnoli/size=15/align=0-8,,,,2.52761E+02
+CRC32/poly=Castagnoli/size=15/align=1-8,,,,2.52790E+02
+CRC32/poly=Castagnoli/size=40/align=0-8,,,,6.71281E+02
+CRC32/poly=Castagnoli/size=40/align=1-8,,,,6.89073E+02
+CRC32/poly=Castagnoli/size=512/align=0-8,,,,1.45840E+03
+CRC32/poly=Castagnoli/size=512/align=1-8,,,,1.46342E+03
+CRC32/poly=Castagnoli/size=1kB/align=0-8,,,,1.55922E+03
+CRC32/poly=Castagnoli/size=1kB/align=1-8,,,,1.55926E+03
+CRC32/poly=Castagnoli/size=4kB/align=0-8,,,,1.67078E+03
+CRC32/poly=Castagnoli/size=4kB/align=1-8,,,,1.67399E+03
+CRC32/poly=Castagnoli/size=32kB/align=0-8,,,,1.59801E+03
+CRC32/poly=Castagnoli/size=32kB/align=1-8,,,,1.62604E+03
+CRC32/poly=Koopman/size=15/align=0-8,,,,2.57893E+02
+CRC32/poly=Koopman/size=15/align=1-8,,,,2.66301E+02
+CRC32/poly=Koopman/size=40/align=0-8,,,,2.80521E+02
+CRC32/poly=Koopman/size=40/align=1-8,,,,2.89866E+02
+CRC32/poly=Koopman/size=512/align=0-8,,,,3.04127E+02
+CRC32/poly=Koopman/size=512/align=1-8,,,,3.11609E+02
+CRC32/poly=Koopman/size=1kB/align=0-8,,,,3.09621E+02
+CRC32/poly=Koopman/size=1kB/align=1-8,,,,3.12318E+02
+CRC32/poly=Koopman/size=4kB/align=0-8,,,,3.07516E+02
+CRC32/poly=Koopman/size=4kB/align=1-8,,,,3.11292E+02
+CRC32/poly=Koopman/size=32kB/align=0-8,,,,3.06816E+02
+CRC32/poly=Koopman/size=32kB/align=1-8,,,,3.09085E+02
+
+[Geo mean],1.71221E+03,2.48066E+03,1.69486E+03,6.75103E+02
diff --git a/cmd/benchstat/testdata/allnosplitcsv.golden b/cmd/benchstat/testdata/allnosplitcsv.golden
new file mode 100644
index 0000000..b3513a4
--- /dev/null
+++ b/cmd/benchstat/testdata/allnosplitcsv.golden
@@ -0,0 +1,77 @@
+name \ time/op (ns/op),new.txt,±,old.txt,±,slashslash4.txt,±,x386.txt,±
+CRC32/poly=IEEE/size=15/align=0-8,4.45200E+01,3%,4.68700E+01,8%,,,6.23500E+01,9%
+CRC32/poly=IEEE/size=15/align=1-8,4.45000E+01,4%,4.47100E+01,5%,,,6.34900E+01,8%
+CRC32/poly=IEEE/size=40/align=0-8,4.25000E+01,6%,4.10375E+01,1%,4.20800E+01,3%,5.74400E+01,3%
+CRC32/poly=IEEE/size=40/align=1-8,4.20400E+01,3%,4.10778E+01,1%,4.16800E+01,5%,5.73300E+01,3%
+CRC32/poly=IEEE/size=512/align=0-8,5.71200E+01,3%,2.38000E+02,5%,,,3.32333E+02,2%
+CRC32/poly=IEEE/size=512/align=1-8,5.71700E+01,3%,2.35500E+02,3%,,,3.35300E+02,3%
+CRC32/poly=IEEE/size=1kB/align=0-8,9.41125E+01,2%,4.52500E+02,4%,,,6.26300E+02,2%
+CRC32/poly=IEEE/size=1kB/align=1-8,9.32875E+01,2%,4.43600E+02,2%,,,6.35300E+02,6%
+CRC32/poly=IEEE/size=4kB/align=0-8,2.98111E+02,1%,1.74000E+03,8%,1.68200E+03,2%,2.45670E+03,7%
+CRC32/poly=IEEE/size=4kB/align=1-8,2.99100E+02,3%,1.76430E+03,6%,1.68980E+03,4%,2.43450E+03,5%
+CRC32/poly=IEEE/size=32kB/align=0-8,2.15800E+03,3%,1.49529E+04,7%,,,1.91499E+04,4%
+CRC32/poly=IEEE/size=32kB/align=1-8,2.17830E+03,3%,1.41888E+04,7%,,,1.94203E+04,5%
+CRC32/poly=Castagnoli/size=15/align=0-8,1.63000E+01,2%,1.63778E+01,3%,,,5.93625E+01,1%
+CRC32/poly=Castagnoli/size=15/align=1-8,1.72900E+01,2%,1.72222E+01,2%,,,5.93500E+01,2%
+CRC32/poly=Castagnoli/size=40/align=0-8,1.75300E+01,4%,1.74300E+01,2%,1.85800E+01,11%,5.96600E+01,6%
+CRC32/poly=Castagnoli/size=40/align=1-8,1.93900E+01,2%,1.97100E+01,3%,1.96375E+01,2%,5.81100E+01,7%
+CRC32/poly=Castagnoli/size=512/align=0-8,4.01300E+01,4%,4.01700E+01,2%,,,3.50800E+02,5%
+CRC32/poly=Castagnoli/size=512/align=1-8,4.19444E+01,2%,4.21400E+01,3%,,,3.49800E+02,7%
+CRC32/poly=Castagnoli/size=1kB/align=0-8,6.61625E+01,1%,6.55000E+01,1%,,,6.56700E+02,4%
+CRC32/poly=Castagnoli/size=1kB/align=1-8,6.84667E+01,2%,7.00900E+01,6%,,,6.56800E+02,6%
+CRC32/poly=Castagnoli/size=4kB/align=0-8,1.58800E+02,3%,1.62800E+02,5%,1.61200E+02,8%,2.45180E+03,4%
+CRC32/poly=Castagnoli/size=4kB/align=1-8,1.61600E+02,3%,1.69400E+02,6%,1.69600E+02,8%,2.44800E+03,5%
+CRC32/poly=Castagnoli/size=32kB/align=0-8,1.21433E+03,3%,1.21822E+03,4%,,,2.05261E+04,5%
+CRC32/poly=Castagnoli/size=32kB/align=1-8,1.22080E+03,4%,1.26478E+03,3%,,,2.01794E+04,9%
+CRC32/poly=Koopman/size=15/align=0-8,3.56000E+01,3%,3.65100E+01,11%,,,5.82100E+01,4%
+CRC32/poly=Koopman/size=15/align=1-8,3.55111E+01,1%,3.51500E+01,5%,,,5.64000E+01,7%
+CRC32/poly=Koopman/size=40/align=0-8,8.76500E+01,2%,9.16400E+01,9%,9.38100E+01,13%,1.42300E+02,8%
+CRC32/poly=Koopman/size=40/align=1-8,8.80300E+01,3%,9.10800E+01,6%,8.68600E+01,3%,1.36222E+02,5%
+CRC32/poly=Koopman/size=512/align=0-8,1.07590E+03,3%,1.13170E+03,5%,,,1.68440E+03,5%
+CRC32/poly=Koopman/size=512/align=1-8,1.16660E+03,8%,1.12680E+03,6%,,,1.64290E+03,4%
+CRC32/poly=Koopman/size=1kB/align=0-8,2.34070E+03,4%,2.24333E+03,6%,,,3.30890E+03,4%
+CRC32/poly=Koopman/size=1kB/align=1-8,2.36010E+03,5%,2.14867E+03,2%,,,3.27920E+03,3%
+CRC32/poly=Koopman/size=4kB/align=0-8,9.00320E+03,6%,9.03150E+03,6%,9.08160E+03,8%,1.33243E+04,3%
+CRC32/poly=Koopman/size=4kB/align=1-8,9.04630E+03,12%,8.94020E+03,10%,9.45520E+03,8%,1.31612E+04,3%
+CRC32/poly=Koopman/size=32kB/align=0-8,7.29005E+04,4%,7.24280E+04,9%,,,1.06891E+05,6%
+CRC32/poly=Koopman/size=32kB/align=1-8,7.42809E+04,3%,6.96194E+04,3%,,,1.06069E+05,4%
+[Geo mean],2.37855E+02,,3.44668E+02,,2.39033E+02,,8.74025E+02
+
+name \ speed (MB/s),new.txt,±,old.txt,±,slashslash4.txt,±,x386.txt,±
+CRC32/poly=IEEE/size=15/align=0-8,3.36950E+02,3%,3.20711E+02,8%,,,2.41180E+02,8%
+CRC32/poly=IEEE/size=15/align=1-8,3.37066E+02,4%,3.35516E+02,4%,,,2.36728E+02,8%
+CRC32/poly=IEEE/size=40/align=0-8,9.41823E+02,5%,9.74718E+02,1%,9.50988E+02,3%,6.96493E+02,3%
+CRC32/poly=IEEE/size=40/align=1-8,9.51759E+02,3%,9.73636E+02,1%,9.59978E+02,4%,6.97741E+02,3%
+CRC32/poly=IEEE/size=512/align=0-8,8.96715E+03,3%,2.14703E+03,4%,,,1.53793E+03,2%
+CRC32/poly=IEEE/size=512/align=1-8,8.95606E+03,3%,2.16913E+03,3%,,,1.52435E+03,3%
+CRC32/poly=IEEE/size=1kB/align=0-8,1.08807E+04,2%,2.26152E+03,4%,,,1.63354E+03,2%
+CRC32/poly=IEEE/size=1kB/align=1-8,1.09768E+04,2%,2.30619E+03,2%,,,1.61191E+03,6%
+CRC32/poly=IEEE/size=4kB/align=0-8,1.37258E+04,1%,2.35732E+03,7%,2.43489E+03,2%,1.66842E+03,7%
+CRC32/poly=IEEE/size=4kB/align=1-8,1.36770E+04,3%,2.32511E+03,6%,2.42393E+03,4%,1.68370E+03,5%
+CRC32/poly=IEEE/size=32kB/align=0-8,1.51852E+04,3%,2.19443E+03,7%,,,1.71179E+03,4%
+CRC32/poly=IEEE/size=32kB/align=1-8,1.50437E+04,3%,2.31415E+03,8%,,,1.68829E+03,5%
+CRC32/poly=Castagnoli/size=15/align=0-8,9.20433E+02,2%,9.15799E+02,2%,,,2.52761E+02,1%
+CRC32/poly=Castagnoli/size=15/align=1-8,8.67298E+02,2%,8.70312E+02,2%,,,2.52790E+02,2%
+CRC32/poly=Castagnoli/size=40/align=0-8,2.28265E+03,4%,2.29560E+03,2%,2.16074E+03,11%,6.71281E+02,6%
+CRC32/poly=Castagnoli/size=40/align=1-8,2.06346E+03,2%,2.03023E+03,3%,2.03587E+03,2%,6.89073E+02,6%
+CRC32/poly=Castagnoli/size=512/align=0-8,1.27578E+04,4%,1.27437E+04,2%,,,1.45840E+03,5%
+CRC32/poly=Castagnoli/size=512/align=1-8,1.22049E+04,1%,1.21445E+04,3%,,,1.46342E+03,7%
+CRC32/poly=Castagnoli/size=1kB/align=0-8,1.54766E+04,1%,1.56355E+04,1%,,,1.55922E+03,4%
+CRC32/poly=Castagnoli/size=1kB/align=1-8,1.49597E+04,2%,1.46273E+04,6%,,,1.55926E+03,5%
+CRC32/poly=Castagnoli/size=4kB/align=0-8,2.56897E+04,3%,2.50862E+04,5%,2.53717E+04,7%,1.67078E+03,4%
+CRC32/poly=Castagnoli/size=4kB/align=1-8,2.52736E+04,3%,2.41378E+04,6%,2.41249E+04,8%,1.67399E+03,5%
+CRC32/poly=Castagnoli/size=32kB/align=0-8,2.68232E+04,5%,2.68975E+04,4%,,,1.59801E+03,5%
+CRC32/poly=Castagnoli/size=32kB/align=1-8,2.68422E+04,4%,2.59038E+04,3%,,,1.62604E+03,8%
+CRC32/poly=Koopman/size=15/align=0-8,4.21452E+02,3%,4.11932E+02,10%,,,2.57893E+02,5%
+CRC32/poly=Koopman/size=15/align=1-8,4.22362E+02,1%,4.27408E+02,5%,,,2.66301E+02,6%
+CRC32/poly=Koopman/size=40/align=0-8,4.56472E+02,2%,4.36831E+02,9%,4.28261E+02,12%,2.80521E+02,7%
+CRC32/poly=Koopman/size=40/align=1-8,4.54515E+02,3%,4.39731E+02,6%,4.60660E+02,3%,2.89866E+02,8%
+CRC32/poly=Koopman/size=512/align=0-8,4.75749E+02,3%,4.52693E+02,5%,,,3.04127E+02,5%
+CRC32/poly=Koopman/size=512/align=1-8,4.39685E+02,8%,4.54579E+02,6%,,,3.11609E+02,3%
+CRC32/poly=Koopman/size=1kB/align=0-8,4.37629E+02,4%,4.52443E+02,9%,,,3.09621E+02,4%
+CRC32/poly=Koopman/size=1kB/align=1-8,4.34042E+02,5%,4.76558E+02,2%,,,3.12318E+02,3%
+CRC32/poly=Koopman/size=4kB/align=0-8,4.55492E+02,6%,4.54022E+02,5%,4.52301E+02,8%,3.07516E+02,3%
+CRC32/poly=Koopman/size=4kB/align=1-8,4.54627E+02,11%,4.59394E+02,9%,4.33984E+02,9%,3.11292E+02,3%
+CRC32/poly=Koopman/size=32kB/align=0-8,4.49828E+02,4%,4.53471E+02,8%,,,3.06816E+02,6%
+CRC32/poly=Koopman/size=32kB/align=1-8,4.41379E+02,3%,4.70784E+02,3%,,,3.09085E+02,4%
+[Geo mean],2.48066E+03,,1.71221E+03,,1.69486E+03,,6.75103E+02
diff --git a/cmd/benchstat/testdata/oldcsv.golden b/cmd/benchstat/testdata/oldcsv.golden
new file mode 100644
index 0000000..3e271a2
--- /dev/null
+++ b/cmd/benchstat/testdata/oldcsv.golden
@@ -0,0 +1,77 @@
+name,time/op (ns/op),±
+CRC32/poly=IEEE/size=15/align=0-8,4.68700E+01,8%
+CRC32/poly=IEEE/size=15/align=1-8,4.47100E+01,5%
+CRC32/poly=IEEE/size=40/align=0-8,4.10375E+01,1%
+CRC32/poly=IEEE/size=40/align=1-8,4.10778E+01,1%
+CRC32/poly=IEEE/size=512/align=0-8,2.38000E+02,5%
+CRC32/poly=IEEE/size=512/align=1-8,2.35500E+02,3%
+CRC32/poly=IEEE/size=1kB/align=0-8,4.52500E+02,4%
+CRC32/poly=IEEE/size=1kB/align=1-8,4.43600E+02,2%
+CRC32/poly=IEEE/size=4kB/align=0-8,1.74000E+03,8%
+CRC32/poly=IEEE/size=4kB/align=1-8,1.76430E+03,6%
+CRC32/poly=IEEE/size=32kB/align=0-8,1.49529E+04,7%
+CRC32/poly=IEEE/size=32kB/align=1-8,1.41888E+04,7%
+CRC32/poly=Castagnoli/size=15/align=0-8,1.63778E+01,3%
+CRC32/poly=Castagnoli/size=15/align=1-8,1.72222E+01,2%
+CRC32/poly=Castagnoli/size=40/align=0-8,1.74300E+01,2%
+CRC32/poly=Castagnoli/size=40/align=1-8,1.97100E+01,3%
+CRC32/poly=Castagnoli/size=512/align=0-8,4.01700E+01,2%
+CRC32/poly=Castagnoli/size=512/align=1-8,4.21400E+01,3%
+CRC32/poly=Castagnoli/size=1kB/align=0-8,6.55000E+01,1%
+CRC32/poly=Castagnoli/size=1kB/align=1-8,7.00900E+01,6%
+CRC32/poly=Castagnoli/size=4kB/align=0-8,1.62800E+02,5%
+CRC32/poly=Castagnoli/size=4kB/align=1-8,1.69400E+02,6%
+CRC32/poly=Castagnoli/size=32kB/align=0-8,1.21822E+03,4%
+CRC32/poly=Castagnoli/size=32kB/align=1-8,1.26478E+03,3%
+CRC32/poly=Koopman/size=15/align=0-8,3.65100E+01,11%
+CRC32/poly=Koopman/size=15/align=1-8,3.51500E+01,5%
+CRC32/poly=Koopman/size=40/align=0-8,9.16400E+01,9%
+CRC32/poly=Koopman/size=40/align=1-8,9.10800E+01,6%
+CRC32/poly=Koopman/size=512/align=0-8,1.13170E+03,5%
+CRC32/poly=Koopman/size=512/align=1-8,1.12680E+03,6%
+CRC32/poly=Koopman/size=1kB/align=0-8,2.24333E+03,6%
+CRC32/poly=Koopman/size=1kB/align=1-8,2.14867E+03,2%
+CRC32/poly=Koopman/size=4kB/align=0-8,9.03150E+03,6%
+CRC32/poly=Koopman/size=4kB/align=1-8,8.94020E+03,10%
+CRC32/poly=Koopman/size=32kB/align=0-8,7.24280E+04,9%
+CRC32/poly=Koopman/size=32kB/align=1-8,6.96194E+04,3%
+[Geo mean],3.44668E+02
+
+name,speed (MB/s),±
+CRC32/poly=IEEE/size=15/align=0-8,3.20711E+02,8%
+CRC32/poly=IEEE/size=15/align=1-8,3.35516E+02,4%
+CRC32/poly=IEEE/size=40/align=0-8,9.74718E+02,1%
+CRC32/poly=IEEE/size=40/align=1-8,9.73636E+02,1%
+CRC32/poly=IEEE/size=512/align=0-8,2.14703E+03,4%
+CRC32/poly=IEEE/size=512/align=1-8,2.16913E+03,3%
+CRC32/poly=IEEE/size=1kB/align=0-8,2.26152E+03,4%
+CRC32/poly=IEEE/size=1kB/align=1-8,2.30619E+03,2%
+CRC32/poly=IEEE/size=4kB/align=0-8,2.35732E+03,7%
+CRC32/poly=IEEE/size=4kB/align=1-8,2.32511E+03,6%
+CRC32/poly=IEEE/size=32kB/align=0-8,2.19443E+03,7%
+CRC32/poly=IEEE/size=32kB/align=1-8,2.31415E+03,8%
+CRC32/poly=Castagnoli/size=15/align=0-8,9.15799E+02,2%
+CRC32/poly=Castagnoli/size=15/align=1-8,8.70312E+02,2%
+CRC32/poly=Castagnoli/size=40/align=0-8,2.29560E+03,2%
+CRC32/poly=Castagnoli/size=40/align=1-8,2.03023E+03,3%
+CRC32/poly=Castagnoli/size=512/align=0-8,1.27437E+04,2%
+CRC32/poly=Castagnoli/size=512/align=1-8,1.21445E+04,3%
+CRC32/poly=Castagnoli/size=1kB/align=0-8,1.56355E+04,1%
+CRC32/poly=Castagnoli/size=1kB/align=1-8,1.46273E+04,6%
+CRC32/poly=Castagnoli/size=4kB/align=0-8,2.50862E+04,5%
+CRC32/poly=Castagnoli/size=4kB/align=1-8,2.41378E+04,6%
+CRC32/poly=Castagnoli/size=32kB/align=0-8,2.68975E+04,4%
+CRC32/poly=Castagnoli/size=32kB/align=1-8,2.59038E+04,3%
+CRC32/poly=Koopman/size=15/align=0-8,4.11932E+02,10%
+CRC32/poly=Koopman/size=15/align=1-8,4.27408E+02,5%
+CRC32/poly=Koopman/size=40/align=0-8,4.36831E+02,9%
+CRC32/poly=Koopman/size=40/align=1-8,4.39731E+02,6%
+CRC32/poly=Koopman/size=512/align=0-8,4.52693E+02,5%
+CRC32/poly=Koopman/size=512/align=1-8,4.54579E+02,6%
+CRC32/poly=Koopman/size=1kB/align=0-8,4.52443E+02,9%
+CRC32/poly=Koopman/size=1kB/align=1-8,4.76558E+02,2%
+CRC32/poly=Koopman/size=4kB/align=0-8,4.54022E+02,5%
+CRC32/poly=Koopman/size=4kB/align=1-8,4.59394E+02,9%
+CRC32/poly=Koopman/size=32kB/align=0-8,4.53471E+02,8%
+CRC32/poly=Koopman/size=32kB/align=1-8,4.70784E+02,3%
+[Geo mean],1.71221E+03