From 1b18f0ba24eaa1e7530063975e64b333d1ed98ea Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Tue, 11 Jun 2024 10:58:17 -0700 Subject: [PATCH 1/4] Add ValueTuple based creation of CompressedColumnStorage to avoid Tuple allocations --- CSparse/Converter.cs | 20 ++++++++++++++++++++ CSparse/Storage/CompressedColumnStorage.cs | 10 ++++++++++ 2 files changed, 30 insertions(+) diff --git a/CSparse/Converter.cs b/CSparse/Converter.cs index 131cb91..4285117 100644 --- a/CSparse/Converter.cs +++ b/CSparse/Converter.cs @@ -289,5 +289,25 @@ public static CoordinateStorage FromEnumerable(IEnumerable + /// Convert a row major array to coordinate storage. + /// + /// Enumerates the entries of a matrix with value tuples. + /// Number of rows. + /// Number of columns. + /// Coordinate storage. + public static CoordinateStorage FromEnumerable(IEnumerable<(int, int, T)> enumerable, int rowCount, int columnCount) + where T : struct, IEquatable, IFormattable + { + var storage = new CoordinateStorage(rowCount, columnCount, Math.Max(rowCount, columnCount)); + + foreach (var item in enumerable) + { + storage.At(item.Item1, item.Item2, item.Item3); + } + + return storage; + } } } diff --git a/CSparse/Storage/CompressedColumnStorage.cs b/CSparse/Storage/CompressedColumnStorage.cs index e6a1b70..f5f81ad 100644 --- a/CSparse/Storage/CompressedColumnStorage.cs +++ b/CSparse/Storage/CompressedColumnStorage.cs @@ -153,6 +153,16 @@ public static CompressedColumnStorage OfIndexed(int rows, int columns, IEnume return Converter.ToCompressedColumnStorage(c); } + /// + /// Create a new sparse matrix as a copy of the given indexed enumerable using a value tuple. + /// + public static CompressedColumnStorage OfIndexed(int rows, int columns, IEnumerable<(int, int, T)> enumerable) + { + var c = Converter.FromEnumerable(enumerable, rows, columns); + + return Converter.ToCompressedColumnStorage(c); + } + /// /// Create a new sparse matrix as a copy of the given array (row-major). /// From 0e6c53c80336a42995d20431a64ab55e1a332e11 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Tue, 11 Jun 2024 11:33:25 -0700 Subject: [PATCH 2/4] Name the portions of the ValueTuple --- CSparse/Converter.cs | 4 ++-- CSparse/Storage/CompressedColumnStorage.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CSparse/Converter.cs b/CSparse/Converter.cs index 4285117..dec3661 100644 --- a/CSparse/Converter.cs +++ b/CSparse/Converter.cs @@ -297,14 +297,14 @@ public static CoordinateStorage FromEnumerable(IEnumerableNumber of rows. /// Number of columns. /// Coordinate storage. - public static CoordinateStorage FromEnumerable(IEnumerable<(int, int, T)> enumerable, int rowCount, int columnCount) + public static CoordinateStorage FromEnumerable(IEnumerable<(int row, int column, T value)> enumerable, int rowCount, int columnCount) where T : struct, IEquatable, IFormattable { var storage = new CoordinateStorage(rowCount, columnCount, Math.Max(rowCount, columnCount)); foreach (var item in enumerable) { - storage.At(item.Item1, item.Item2, item.Item3); + storage.At(item.row, item.column, item.value); } return storage; diff --git a/CSparse/Storage/CompressedColumnStorage.cs b/CSparse/Storage/CompressedColumnStorage.cs index f5f81ad..55c8f10 100644 --- a/CSparse/Storage/CompressedColumnStorage.cs +++ b/CSparse/Storage/CompressedColumnStorage.cs @@ -156,7 +156,7 @@ public static CompressedColumnStorage OfIndexed(int rows, int columns, IEnume /// /// Create a new sparse matrix as a copy of the given indexed enumerable using a value tuple. /// - public static CompressedColumnStorage OfIndexed(int rows, int columns, IEnumerable<(int, int, T)> enumerable) + public static CompressedColumnStorage OfIndexed(int rows, int columns, IEnumerable<(int row, int column, T value)> enumerable) { var c = Converter.FromEnumerable(enumerable, rows, columns); From 9257c82360307ba1116073aaa8cb3ecf7f3c1190 Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Tue, 11 Jun 2024 13:00:50 -0700 Subject: [PATCH 3/4] Document method parameters in CompressedColumnStorage class. Also catch the method I copied from while I'm here. --- CSparse/Storage/CompressedColumnStorage.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CSparse/Storage/CompressedColumnStorage.cs b/CSparse/Storage/CompressedColumnStorage.cs index 55c8f10..79473ed 100644 --- a/CSparse/Storage/CompressedColumnStorage.cs +++ b/CSparse/Storage/CompressedColumnStorage.cs @@ -146,6 +146,9 @@ public static CompressedColumnStorage OfIndexed(CoordinateStorage coordina /// /// Create a new sparse matrix as a copy of the given indexed enumerable. /// + /// The number of rows. + /// The number of columns. + /// Tuples with the three elements of row, column, and the value that belongs at that position. public static CompressedColumnStorage OfIndexed(int rows, int columns, IEnumerable> enumerable) { var c = Converter.FromEnumerable(enumerable, rows, columns); @@ -156,6 +159,9 @@ public static CompressedColumnStorage OfIndexed(int rows, int columns, IEnume /// /// Create a new sparse matrix as a copy of the given indexed enumerable using a value tuple. /// + /// The number of rows. + /// The number of columns. + /// Value tuples with the three elements of row, column, and the value that belongs at that position. public static CompressedColumnStorage OfIndexed(int rows, int columns, IEnumerable<(int row, int column, T value)> enumerable) { var c = Converter.FromEnumerable(enumerable, rows, columns); From 24611b8562753eb3c60a878748c0dcb65a50f6fe Mon Sep 17 00:00:00 2001 From: Michael Niksa Date: Tue, 11 Jun 2024 13:15:31 -0700 Subject: [PATCH 4/4] Add Value Tuple based iteration to Matrix base class and DenseColumn and CompressedColumn storage mechanisms derived from that class. Use the value-based variant in the OfMatrix helper to avoid allocations. --- CSparse/Matrix.cs | 9 +++++++++ CSparse/Storage/CompressedColumnStorage.cs | 13 +++++++++++-- CSparse/Storage/DenseColumnMajorStorage.cs | 15 ++++++++++++--- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/CSparse/Matrix.cs b/CSparse/Matrix.cs index 907254e..aafcda9 100644 --- a/CSparse/Matrix.cs +++ b/CSparse/Matrix.cs @@ -132,9 +132,18 @@ protected Matrix(int rowCount, int columnCount) /// /// Enumerates all values of the matrix. /// + /// + /// for a version that returns stack-allocated value tuples to save transient heap allocations (saves performance overhead of allocations + garbage collection) of the class. + /// /// Enumeration of tuples (i, j, a[i, j]). public abstract IEnumerable> EnumerateIndexed(); + /// + /// Enumerates all values of the matrix, but returns as stack-allocated value tuples instead of heap-allocated tuples. + /// + /// Enumeration of tuples (i, j, a[i, j]). + public abstract IEnumerable<(int row, int column, T value)> EnumerateIndexedAsValueTuples(); + /// /// Enumerates all values of the matrix. /// diff --git a/CSparse/Storage/CompressedColumnStorage.cs b/CSparse/Storage/CompressedColumnStorage.cs index 79473ed..2329119 100644 --- a/CSparse/Storage/CompressedColumnStorage.cs +++ b/CSparse/Storage/CompressedColumnStorage.cs @@ -110,7 +110,7 @@ public CompressedColumnStorage(int rowCount, int columnCount, T[] values, int[] /// public static CompressedColumnStorage OfMatrix(Matrix matrix) { - var c = Converter.FromEnumerable(matrix.EnumerateIndexed(), matrix.RowCount, matrix.ColumnCount); + var c = Converter.FromEnumerable(matrix.EnumerateIndexedAsValueTuples(), matrix.RowCount, matrix.ColumnCount); return Converter.ToCompressedColumnStorage(c); } @@ -580,6 +580,15 @@ public CompressedColumnStorage Clone(bool values = true) /// public override IEnumerable> EnumerateIndexed() + { + foreach (var valueTuple in EnumerateIndexedAsValueTuples()) + { + yield return Tuple.Create(valueTuple.row, valueTuple.column, valueTuple.value); + } + } + + /// + public override IEnumerable<(int row, int column, T value)> EnumerateIndexedAsValueTuples() { var ax = Values; var ap = ColumnPointers; @@ -590,7 +599,7 @@ public override IEnumerable> EnumerateIndexed() var end = ap[i + 1]; for (var j = ap[i]; j < end; j++) { - yield return new Tuple(ai[j], i, ax[j]); + yield return (ai[j], i, ax[j]); } } } diff --git a/CSparse/Storage/DenseColumnMajorStorage.cs b/CSparse/Storage/DenseColumnMajorStorage.cs index 053a6da..ae7c653 100644 --- a/CSparse/Storage/DenseColumnMajorStorage.cs +++ b/CSparse/Storage/DenseColumnMajorStorage.cs @@ -165,8 +165,8 @@ public static DenseColumnMajorStorage OfDiagonalArray(T[] diagonal) { int order = diagonal.Length; - var A = Create(order, order); - + var A = Create(order, order); + for (int i = 0; i < order; i++) { A.At(i, i, diagonal[i]); @@ -547,12 +547,21 @@ public override void Clear() /// public override IEnumerable> EnumerateIndexed() + { + foreach (var valueTuple in EnumerateIndexedAsValueTuples()) + { + yield return Tuple.Create(valueTuple.row, valueTuple.column, valueTuple.value); + } + } + + /// + public override IEnumerable<(int row, int column, T value)> EnumerateIndexedAsValueTuples() { for (int row = 0; row < rows; row++) { for (int column = 0; column < columns; column++) { - yield return new Tuple(row, column, Values[(column * rows) + row]); + yield return (row, column, Values[(column * rows) + row]); } } }