From a30a500564610b451dff53b2297fc0b71cbeb973 Mon Sep 17 00:00:00 2001 From: antonl Date: Mon, 16 Mar 2026 19:08:31 +0100 Subject: [PATCH] first whole bench --- cSparseResults.json | 8 +-- goSparseResults.json | 58 +++++++++++++++++ plot.py | 111 ++++++++++++++++++++++++++++++++ src/main.c | 2 +- src/main.go | 147 ++++++++++++++++++++++++++++++++++++------- 5 files changed, 298 insertions(+), 28 deletions(-) create mode 100644 goSparseResults.json create mode 100644 plot.py diff --git a/cSparseResults.json b/cSparseResults.json index 6dcef2e..4e4d6ce 100644 --- a/cSparseResults.json +++ b/cSparseResults.json @@ -1,6 +1,6 @@ [ - {"label":"FEM_3D_thermal2","rows":147900,"cols":147900,"nnz":3489300,"spmv_runs":32,"spmv_total_ns":3866100,"spmv_avg_ns":120815,"dense_rows":2048,"dense_cols":2048,"dense_runs":32,"dense_total_ns":957945100,"dense_avg_ns":29935784}, - {"label":"ldoor","rows":952203,"cols":952203,"nnz":46522475,"spmv_runs":32,"spmv_total_ns":286909100,"spmv_avg_ns":8965909,"dense_rows":4096,"dense_cols":4096,"dense_runs":32,"dense_total_ns":7653100100,"dense_avg_ns":239159378}, - {"label":"Cube_Coup_dt0","rows":2164760,"cols":2164760,"nnz":127206144,"spmv_runs":32,"spmv_total_ns":864947600,"spmv_avg_ns":27029612,"dense_rows":8192,"dense_cols":8192,"dense_runs":32,"dense_total_ns":59799139700,"dense_avg_ns":1868723115}, - {"label":"nlpkkt200","rows":16240000,"cols":16240000,"nnz":448225632,"spmv_runs":32,"spmv_total_ns":2383754600,"spmv_avg_ns":74492331,"dense_rows":16384,"dense_cols":16384,"dense_runs":32,"dense_total_ns":471942950199,"dense_avg_ns":14748217193} + {"label":"FEM_3D_thermal2","rows":147900,"cols":147900,"nnz":3489300,"spmv_runs":32,"spmv_total_ns":9411500,"spmv_avg_ns":294109,"dense_rows":1024,"dense_cols":1024,"dense_runs":32,"dense_total_ns":129353000,"dense_avg_ns":4042281}, + {"label":"ldoor","rows":952203,"cols":952203,"nnz":46522475,"spmv_runs":32,"spmv_total_ns":284244500,"spmv_avg_ns":8882640,"dense_rows":2048,"dense_cols":2048,"dense_runs":32,"dense_total_ns":1979492200,"dense_avg_ns":61859131}, + {"label":"Cube_Coup_dt0","rows":2164760,"cols":2164760,"nnz":127206144,"spmv_runs":32,"spmv_total_ns":894006900,"spmv_avg_ns":27937715,"dense_rows":4096,"dense_cols":4096,"dense_runs":32,"dense_total_ns":8725314200,"dense_avg_ns":272666068}, + {"label":"nlpkkt200","rows":16240000,"cols":16240000,"nnz":448225632,"spmv_runs":32,"spmv_total_ns":2437591700,"spmv_avg_ns":76174740,"dense_rows":8192,"dense_cols":8192,"dense_runs":32,"dense_total_ns":63669801300,"dense_avg_ns":1989681290} ] diff --git a/goSparseResults.json b/goSparseResults.json new file mode 100644 index 0000000..5c9fe15 --- /dev/null +++ b/goSparseResults.json @@ -0,0 +1,58 @@ +[ + { + "label": "FEM_3D_thermal2", + "rows": 147900, + "cols": 147900, + "nnz": 3489300, + "dense_rows": 1024, + "dense_cols": 1024, + "dense_runs": 32, + "dense_total_ns": 678975700, + "dense_avg_ns": 21217990, + "spmv_runs": 32, + "spmv_total_ns": 44127200, + "spmv_avg_ns": 1378975 + }, + { + "label": "ldoor", + "rows": 952203, + "cols": 952203, + "nnz": 46522475, + "dense_rows": 2048, + "dense_cols": 2048, + "dense_runs": 32, + "dense_total_ns": 5421343500, + "dense_avg_ns": 169416984, + "spmv_runs": 32, + "spmv_total_ns": 515245900, + "spmv_avg_ns": 16101434 + }, + { + "label": "Cube_Coup_dt0", + "rows": 2164760, + "cols": 2164760, + "nnz": 127206144, + "dense_rows": 4096, + "dense_cols": 4096, + "dense_runs": 32, + "dense_total_ns": 43612138700, + "dense_avg_ns": 1362879334, + "spmv_runs": 32, + "spmv_total_ns": 1300820500, + "spmv_avg_ns": 40650640 + }, + { + "label": "nlpkkt200", + "rows": 16240000, + "cols": 16240000, + "nnz": 448225632, + "dense_rows": 8192, + "dense_cols": 8192, + "dense_runs": 32, + "dense_total_ns": 350072230900, + "dense_avg_ns": 10939757215, + "spmv_runs": 32, + "spmv_total_ns": 6186462100, + "spmv_avg_ns": 193326940 + } +] diff --git a/plot.py b/plot.py new file mode 100644 index 0000000..f5c2f45 --- /dev/null +++ b/plot.py @@ -0,0 +1,111 @@ +import json +import numpy as np +import matplotlib.pyplot as plt + +#font sizes +plt.rcParams.update({ + "font.size": 18, + "axes.titlesize": 22, + "axes.labelsize": 20, + "xtick.labelsize": 16, + "ytick.labelsize": 16, + "legend.fontsize": 16, +}) + +def getResults(filename): + + with open(filename) as f: + data = json.load(f) + + # Prepare lists + spmv_sizes = [] + spmv_times = [] + + dense_sizes = [] + dense_times = [] + + labels = [] + + for d in data: + spmv_sizes.append(d["nnz"]) + spmv_times.append(d["spmv_avg_ns"]) + + dense_sizes.append(d["dense_rows"]) + dense_times.append(d["dense_avg_ns"]) + + labels.append(d["label"]) + + return spmv_sizes, spmv_times, dense_sizes, dense_times, labels + + +c_spmv_sizes, c_spmv_times, c_dense_sizes, c_dense_times, c_labels = getResults("cSparseResults.json") +go_spmv_sizes, go_spmv_times, go_dense_sizes, go_dense_times, go_labels = getResults("goSparseResults.json") + +# make sure data aligns on x axis +for i in range(len(go_spmv_sizes)): + assert(c_spmv_sizes[i] == go_spmv_sizes[i]) + assert(go_dense_sizes[i] == c_dense_sizes[i]) + + + +#spmv plot +x_spmv = c_spmv_sizes +x_spmv = np.float64(x_spmv)/1e6 +go_spmv_float = np.float64(go_spmv_times) +c_spmv_float = np.float64(c_spmv_times) +ratio_spmv = go_spmv_float/c_spmv_float +plt.figure("spmv") +plt.plot(x_spmv, ratio_spmv, 'kd-') +plt.title("Sparse Matrix-Vector multiplication ratio Go native / C MKL") +plt.xlabel("SuiteSparse matrix millions of number of non-zeros") +plt.ylabel("Average time ratio Go/C for SpMV-kernel") +xtick_string = [f"{x:.1f}\n{label}" for x, label in zip(x_spmv, c_labels)] +plt.xticks(x_spmv, xtick_string) +plt.grid() + +# dense plot +x_dense = c_dense_sizes +go_dense_float = np.float64(go_dense_times) +c_dense_float = np.float64(c_dense_times) +ratio_dense = go_dense_float/c_dense_float + +plt.figure("dense") +plt.plot(x_dense, ratio_dense, 'kd-') +plt.title("Dense Matrix-Matrix multiplication ratio Go native / C MKL") +plt.xlabel("Dense matrix size") +plt.ylabel("Average time ratio Go/C for matmul kernel") +xtick_string = [f"{x}^2" for x in x_dense] +plt.xticks(x_dense, xtick_string) +plt.grid() + +plt.show() + +## ---- SpMV Plot ---- +#plt.figure() +#plt.plot(spmv_sizes, spmv_times, marker='o') +# +#for i, label in enumerate(labels): +# plt.annotate(label, (spmv_sizes[i], spmv_times[i])) +# +#plt.xlabel("Number of Nonzeros (nnz)") +#plt.ylabel("Average Time (ns)") +#plt.title("SpMV: Avg Time vs Size") +#plt.xscale("log") +#plt.yscale("log") +#plt.grid(True) +# +## ---- Dense Plot ---- +#plt.figure() +#plt.plot(dense_sizes, dense_times, marker='o') +# +#for i, label in enumerate(labels): +# plt.annotate(label, (dense_sizes[i], dense_times[i])) +# +#plt.xlabel("Matrix Size (rows × cols)") +#plt.ylabel("Average Time (ns)") +#plt.title("Dense: Avg Time vs Size") +#plt.xscale("log") +#plt.yscale("log") +#plt.grid(True) +# +#plt.show() diff --git a/src/main.c b/src/main.c index b6b7f56..505e081 100644 --- a/src/main.c +++ b/src/main.c @@ -629,7 +629,7 @@ Timing doSpMVTimings(const char *path) { int main() { S32 numPaths = 4; - int denseRows[] = {2048, 4096, 4096*2, 4096*4}; + int denseRows[] = {1024, 2048, 4096, 4096*2}; const char *paths[] = { "E:\\dev\\go_matmul_perf\\suitesparse_test_matrices\\FEM_3D_thermal2.mtx", diff --git a/src/main.go b/src/main.go index 8df258b..11076d5 100644 --- a/src/main.go +++ b/src/main.go @@ -14,6 +14,7 @@ import ( "time" "github.com/james-bowman/sparse" + "gonum.org/v1/gonum/blas/blas64" "gonum.org/v1/gonum/mat" ) @@ -57,8 +58,6 @@ func writeTimingJSON(all []Timing, outPath string) { } } -// The "SuiteSparse" collection of matrices come in a format called -// "market matrix" and so we parse and load them to a sparse.COO format here. func matrixLoadMarket(path string) *sparse.CSR { f, err := os.Open(path) if err != nil { @@ -68,44 +67,142 @@ func matrixLoadMarket(path string) *sparse.CSR { scanner := bufio.NewScanner(f) - // skip header - scanner.Scan() + // Read header + if !scanner.Scan() { + panic("failed to read Matrix Market header") + } + header := strings.Fields(scanner.Text()) + if len(header) < 5 { + panic("invalid Matrix Market header") + } - // skip comments + field := header[3] + symmetry := header[4] + + isPattern := field == "pattern" + isInteger := field == "integer" + isReal := field == "real" + isSymmetric := symmetry == "symmetric" + + if !isPattern && !isInteger && !isReal { + panic("unsupported Matrix Market field type") + } + if symmetry != "general" && symmetry != "symmetric" { + panic("unsupported Matrix Market symmetry") + } + + // Skip comments, then read size line for scanner.Scan() { line := scanner.Text() - if !strings.HasPrefix(line, "%") { + if !strings.HasPrefix(strings.TrimSpace(line), "%") { break } } fields := strings.Fields(scanner.Text()) + if len(fields) != 3 { + panic("invalid Matrix Market size line") + } - rows, _ := strconv.Atoi(fields[0]) - cols, _ := strconv.Atoi(fields[1]) - nnz, _ := strconv.Atoi(fields[2]) + rows, err := strconv.Atoi(fields[0]) + if err != nil { + panic(err) + } + cols, err := strconv.Atoi(fields[1]) + if err != nil { + panic(err) + } + nnzInFile, err := strconv.Atoi(fields[2]) + if err != nil { + panic(err) + } - r := make([]int, nnz) - c := make([]int, nnz) - v := make([]float64, nnz) + capacity := nnzInFile + if isSymmetric { + capacity = 2 * nnzInFile + } - i := 0 + r := make([]int, 0, capacity) + c := make([]int, 0, capacity) + v := make([]float64, 0, capacity) for scanner.Scan() { - f := strings.Fields(scanner.Text()) + line := strings.TrimSpace(scanner.Text()) + if line == "" || strings.HasPrefix(line, "%") { + continue + } - ri, _ := strconv.Atoi(f[0]) - ci, _ := strconv.Atoi(f[1]) - val, _ := strconv.ParseFloat(f[2], 64) + flds := strings.Fields(line) - r[i] = ri - 1 - c[i] = ci - 1 - v[i] = val - i++ + var ri, ci int + var val float64 = 1.0 + + if isPattern { + if len(flds) != 2 { + panic("bad pattern entry") + } + ri, err = strconv.Atoi(flds[0]) + if err != nil { + panic(err) + } + ci, err = strconv.Atoi(flds[1]) + if err != nil { + panic(err) + } + } else if isInteger { + if len(flds) != 3 { + panic("bad integer entry") + } + ri, err = strconv.Atoi(flds[0]) + if err != nil { + panic(err) + } + ci, err = strconv.Atoi(flds[1]) + if err != nil { + panic(err) + } + ival, err := strconv.Atoi(flds[2]) + if err != nil { + panic(err) + } + val = float64(ival) + } else { + if len(flds) != 3 { + panic("bad real entry") + } + ri, err = strconv.Atoi(flds[0]) + if err != nil { + panic(err) + } + ci, err = strconv.Atoi(flds[1]) + if err != nil { + panic(err) + } + val, err = strconv.ParseFloat(flds[2], 64) + if err != nil { + panic(err) + } + } + + ri-- + ci-- + + r = append(r, ri) + c = append(c, ci) + v = append(v, val) + + if isSymmetric && ri != ci { + r = append(r, ci) + c = append(c, ri) + v = append(v, val) + } + } + + if err := scanner.Err(); err != nil { + panic(err) } coo := sparse.NewCOO(rows, cols, r, c, v) - return coo.ToCSR() } @@ -255,6 +352,10 @@ func doTimings(path string, denseRows int) Timing { func main() { mainBegin := time.Now() + fmt.Printf("BLAS backend: %T\n", blas64.Implementation()) + fmt.Printf("GOMAXPROCS: %d\n", runtime.GOMAXPROCS(0)) + fmt.Printf("NumCPU: %d\n", runtime.NumCPU()) + paths := []string{ "suitesparse_test_matrices/FEM_3D_thermal2.mtx", "suitesparse_test_matrices/ldoor.mtx", @@ -263,7 +364,7 @@ func main() { } denseRows := []int{ - 2048, 4096, 4096 * 2, 4096 * 4, + 1024, 2048, 4096, 4096 * 2, } numPaths := len(paths)