我正在尝试使用Math.Net's dense matrix class。但是,它不支持int
。因此,我必须为一个锯齿状的二维数组创建一个包装器。
我知道锯齿状数组有better performances。
Data Size : 966 x 345
Naked 2d Array : 10 milliseconds
Naked Jagged Array : 6 milliseconds
Jagged Wrapper : 82 milliseconds
Dense Wrapper : 88 milliseconds
2d Wrapper : 62 milliseconds
根据我的测试结果,一个锯齿状的裸阵列是最快的。 但是,就包装器而言,二维包装器相对较快。
现在,我有两个问题:
。
源代码
测试代码
Bitmap bmpImage = DataConverter2d.ReadGray("image.jpg");
int[][] intNakedJagged = DataConverter2d.ToInteger(bmpImage);
int[,] intNaked2d = JagMatrix<int>.To2d(intNakedJagged);
JagMatrix<int> intJaggedWrapper = new JagMatrix<int>(intNakedJagged);
DenMatrix<int> intDenWrapper = new DenMatrix<int>(intNaked2d);
Matrix<int> int2dWrapper = new Matrix<int>(intNaked2d);
Stopwatch sw1 = new Stopwatch();
sw1.Start();
double[,] dImage = DataConverter2d.ToDouble(intNaked2d);
sw1.Stop();
Console.WriteLine("Naked 2d Array : " + sw1.ElapsedMilliseconds.ToString() + " milliseconds", "Elapsed time");
Stopwatch sw2 = new Stopwatch();
sw2.Start();
double[][] dImageJagged = DataConverter2d.ToDouble(intNakedJagged);
sw2.Stop();
Console.WriteLine("Naked Jagged Array : " + sw2.ElapsedMilliseconds.ToString() + " milliseconds", "Elapsed time");
Stopwatch sw3 = new Stopwatch();
sw3.Start();
JagMatrix<double> dJagArray2d = DataConverter2d.ToDouble(intJaggedWrapper);
sw3.Stop();
Console.WriteLine("Jagged Wrapper : " + sw3.ElapsedMilliseconds.ToString() + " milliseconds", "Elapsed time");
Stopwatch sw4 = new Stopwatch();
sw4.Start();
DenMatrix<double> dDenArray2d = DataConverter2d.ToDouble(intDenWrapper);
sw4.Stop();
Console.WriteLine("Dense Wrapper : " + sw4.ElapsedMilliseconds.ToString() + " milliseconds", "Elapsed time");
Stopwatch sw5 = new Stopwatch();
sw5.Start();
Matrix<double> dArray2d = DataConverter2d.ToDouble(int2dWrapper);
sw5.Stop();
Console.WriteLine("2d Wrapper : " + sw5.ElapsedMilliseconds.ToString() + " milliseconds", "Elapsed time");
Console.ReadKey();
2d矩阵
public class Matrix<T> : IDisposable where T : struct , IComparable<T>
{
private T[,] __array2d;
public int Width { get; set; }
public int Height { get; set; }
public bool IsEmpty
{
get
{
if (__array2d == null) return true;
else return false;
}
}
public Matrix() { }
public Matrix(T[,] data)
{
this.Set(data);
}
public Matrix(int rows, int cols)
{
Width = rows;
Height = cols;
__array2d = new T[Width, Height];
}
public T Get(int x, int y)
{
if (__array2d == null)
{
throw new Exception("array is empty");
}
if (x < Width && y < Height)
{
if (__array2d != null)
{
return __array2d[x, y];
}
else
{
throw new Exception("array is null");
}
}
else
{
string message = string.Empty;
if (x >= Width) message = "x-value exceeds Width ";
if (y >= Height) message += "y-value exceeds Height ";
message += "in Array2d.Get(x,y).";
throw new Exception(message);
}
}
public void Set(int x, int y, T val)
{
if (__array2d == null)
{
__array2d = new T[Width, Height];
}
else
{
if (Width != __array2d.GetLength(0))
{
__array2d = null;
__array2d = new T[Width, Height];
}
}
if (x < Width && y < Height)
{
__array2d[x, y] = val;
}
else
{
throw new Exception(x + ", " + Width + "," + y + "," + Height);
}
}
public T this[int x, int y]
{
get
{
return Get(x, y);
}
set
{
Set(x, y, value);
}
}
public void Set(T[,] arr)
{
if (arr != null)
{
int rows = arr.GetLength(0);
int cols = arr.GetLength(1);
__array2d = arr;
Width = rows;
Height = cols;
}
else
{
throw new Exception("array is null");
}
}
#region IDisposable implementation
~Matrix()
{
this.Dispose(false);
}
protected bool Disposed { get; private set; }
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.Disposed)
{
if (disposing)
{
// Perform managed cleanup here.
//IDisposable disp = (IDisposable)_2dArray;
__array2d = null;
}
// Perform unmanaged cleanup here.
Width = 0;
Height = 0;
this.Disposed = true;
}
}
#endregion
}
2d锯齿状矩阵
public class JagMatrix<T> : IDisposable where T : struct , IComparable<T>
{
private T[][] __array2d;
public int Width { get; set; }
public int Height { get; set; }
public bool IsEmpty
{
get
{
if (__array2d == null) return true;
else return false;
}
}
public JagMatrix() { }
public JagMatrix(T[][] data)
{
this.Set(data);
}
public JagMatrix(int rows, int cols)
{
Width = rows;
Height = cols;
__array2d = new T[Width][];
for (int i = 0; i < Width; i++)
{
__array2d[i] = new T[Height];
}
}
public T Get(int x, int y)
{
if (__array2d == null)
{
throw new Exception("array is empty");
}
if (x < Width && y < Height)
{
if (__array2d != null)
{
return __array2d[x][y];
}
else
{
throw new Exception("array is null");
}
}
else
{
string message = string.Empty;
if (x >= Width) message = "x-value exceeds Width ";
if (y >= Height) message += "y-value exceeds Height ";
message += "in Array2d.Get(x,y).";
throw new Exception(message);
}
}
public void Set(int x, int y, T val)
{
if (__array2d == null)
{
__array2d = new T[Width][];
for (int i = 0; i < Width; i++)
{
__array2d[i] = new T[Height];
}
}
else
{
if (Width != __array2d.GetLength(0))
{
__array2d = null;
__array2d = new T[Width][];
for (int i = 0; i < Width; i++)
{
__array2d[i] = new T[Height];
}
}
}
if (x < Width && y < Height)
{
__array2d[x][y] = val;
}
else
{
throw new Exception(x + ", " + Width + "," + y + "," + Height);
}
}
public static T[,] To2d(T[][] source)
{
T[,] dest = new T[source.Length, source[0].Length];
for (int i = 0; i < source.Length; i++)
{
for (int j = 0; j < source[0].Length; j++)
{
dest[i,j] = source[i][j];
}
}
return dest;
}
public T this[int x, int y]
{
get
{
return Get(x, y);
}
set
{
Set(x, y, value);
}
}
public void Set(T[][] arr)
{
if (arr != null)
{
int rows = arr.Length;
int cols = arr[0].Length;
__array2d = arr;
Width = rows;
Height = cols;
}
else
{
throw new Exception("array is null");
}
}
#region IDisposable implementation
~JagMatrix()
{
this.Dispose(false);
}
protected bool Disposed { get; private set; }
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.Disposed)
{
if (disposing)
{
// Perform managed cleanup here.
//IDisposable disp = (IDisposable)_2dArray;
__array2d = null;
}
// Perform unmanaged cleanup here.
Width = 0;
Height = 0;
this.Disposed = true;
}
}
#endregion
}
2d密集矩阵
public class DenMatrix<T> : IDisposable where T : struct , IComparable<T>
{
private T[] __array1d;
public int Width { get; set; }
public int Height { get; set; }
public int Length { get { return Width * Height; } }
public bool IsEmpty
{
get
{
if (__array1d == null) return true;
else return false;
}
}
public DenMatrix() { }
public DenMatrix(T[,] data)
{
this.Set(data);
}
public DenMatrix(int rows, int cols)
{
Width = rows;
Height = cols;
__array1d = new T[Length];
}
public T Get(int x, int y)
{
if (__array1d == null)
{
throw new Exception("array is empty");
}
if (x < Width && y < Height)
{
if (__array1d != null)
{
return __array1d[x + y * Width];
}
else
{
throw new Exception("array is null");
}
}
else
{
string message = string.Empty;
if (x >= Width) message = "x-value exceeds Width ";
if (y >= Height) message += "y-value exceeds Height ";
message += "in Array2d.Get(x,y).";
throw new Exception(message);
}
}
public void Set(int x, int y, T val)
{
int length = Length;
if (__array1d == null)
{
__array1d = new T[length];
}
else
{
if (length != __array1d.Length)
{
__array1d = null;
__array1d = new T[length];
}
}
if (x < Width && y < Height)
{
__array1d[x + y * Width] = val;
}
else
{
throw new Exception(x + ", " + Width + "," + y + "," + Height);
}
}
public T[] To1d(T[,] array2d)
{
T[] array1d = new T[Length];
for (int x = 0; x < Height; x++)
{
for (int y = 0; y < Width; y++)
{
T val = array2d[x, y];
int index = x * Width + y;
array1d[index] = val;
}
}
return array1d;
}
public T this[int x, int y]
{
get
{
return Get(x, y);
}
set
{
Set(x, y, value);
}
}
public void Set(T[,] arr)
{
if (arr != null)
{
int rows = arr.GetLength(0);
int cols = arr.GetLength(1);
Width = cols;
Height = rows;
__array1d = To1d(arr);
}
else
{
throw new Exception("array is null");
}
}
#region IDisposable implementation
~DenMatrix()
{
this.Dispose(false);
}
protected bool Disposed { get; private set; }
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.Disposed)
{
if (disposing)
{
// Perform managed cleanup here.
//IDisposable disp = (IDisposable)_2dArray;
__array1d = null;
}
// Perform unmanaged cleanup here.
Width = 0;
Height = 0;
this.Disposed = true;
}
}
#endregion
}
public static double[][] ToDouble(int[][] image)
{
int Width = image.Length;
int Height = image[0].Length;
double[][] array2d = new double[Width][];
for (int x = 0; x < Width; x++)
{
array2d[x] = new double[Height];
for (int y = 0; y < Height; y++)
{
double d = image[x][y] / 255.0;
array2d[x][y] = d;
}
}
return array2d;
}
public static Matrix<double> ToDouble(Matrix<int> image)
{
int Width = image.Width;
int Height = image.Height;
Matrix<double> array2d = new Matrix<double>(Width, Height);
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
double d = image[x, y] / 255.0;
array2d[x, y] = d;
}
}
return array2d;
}
答案 0 :(得分:2)
- 为什么锯齿状包装比2d包装要慢?
我无法使用this代码重新创建您的测试结果:
Data Size : 4000 x 4000
Naked 2d Array : 188 milliseconds
Naked Jagged Array : 202 milliseconds
Jagged Wrapper : 311 milliseconds
Dense Wrapper : 501 milliseconds
2d Wrapper : 343 milliseconds
- 有可能使包装纸的运行速度与裸纸一样快吗?
尝试:
简化Get(x, y)
和Set(x, y, value)
方法,并让数组本身检查边界:
public T this[int x, int y]
{
get
{
try {
return _array[x, y];
} catch (IndexOutOfRangeException e) {
throw new Exception(String.Format(
"index ({0}, {1}) exceeds size of Matrix ({2}, {3})",
x, y, Width, Height
));
}
}
set
{
try {
_array[x, y] = value;
} catch (IndexOutOfRangeException e) {
throw new Exception(String.Format(
"index ({0}, {1}) exceeds size of Matrix ({2}, {3})",
x, y, Width, Height
));
}
}
}
结果:
Data Size : 4000 x 4000
Naked 2d Array : 186 milliseconds
matrix (2d Wrapper): 308 milliseconds
FastMatrix : 246 milliseconds
使用map方法(在Linq中称为Select
):
public FastMatrix<R> Map<R>(Func<T, R> func) where R : struct, IComparable<R>
{
FastMatrix<R> array2d = new FastMatrix<R>(Width, Height);
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
array2d._array[x, y] = func(_array[x, y]);
}
}
return array2d;
}
呼叫map
:
FastMatrix<double> dFastMatrix2 = intFastMatrix.Map(v => (double)v / 255.0);
结果是:
Data Size : 4000 x 4000
Naked 2d Array : 186 milliseconds
matrix (2d Wrapper): 308 milliseconds
FastMatrix.Map : 184 milliseconds
那与裸露的二维数组一样快!
摘要:
Data Size : 4000 x 4000
Naked 2d Array : 186 milliseconds
Naked Jagged Array : 200 milliseconds
Jagged Wrapper : 308 milliseconds
Dense Wrapper : 486 milliseconds
2d Wrapper : 308 milliseconds
Fast versions:
FastMatrix : 246 milliseconds
FastMatrix.Map : 184 milliseconds
完整代码为here。
答案 1 :(得分:2)
根据我的测试结果,一个锯齿状的裸阵列是最快的。但是,就包装器而言,二维包装器相对较快。
好吧,我的测试(通过发布模式下内置的exe通过J. Coenen's answer运行代码)显示了不同的结果:
Data Size : 4000 x 4000
Naked 2d Array : 70 milliseconds
Naked Jagged Array : 67 milliseconds
Jagged Wrapper : 205 milliseconds
Dense Wrapper : 391 milliseconds
2d Wrapper : 227 milliseconds
Faster versions:
FastMatrix : 143 milliseconds
FastMatrix.Map : 130 milliseconds
我已经运行了很多次,而且两个锯齿形实现都比其2d实现快一点。 Dense的执行速度明显慢。
有可能使包装器的运行速度与裸机一样快吗?
绝对!您的Get
/ Set
方法正在做很多不必要的工作,基本上以一种低效,冗余且不完全正确的方式来复制CLR工作(例如,不检查负索引)。不要那样做 J。 Coenen 显示了正确的方向,但是您可以走的更远。 C#7.0引入了一个Ref locals and returns(对性能有高要求的人们期待已久)功能,该功能允许您创建具有与CLR阵列相似的性能特征的自定义类索引器。甚至还附带了矩阵的用例/示例:)
您需要做的就是将类索引器更改为:
矩阵
public ref T this[int x, int y] => ref __array2d[x, y];
JagMatrix
public ref T this[int x, int y] => ref __array2d[x][y];
DenMatrix
public ref T this[int x, int y] => ref __array1d[x + y * Width];
现在相同的测试显示了不同的结果:
Data Size : 4000 x 4000
Naked 2d Array : 71 milliseconds
Naked Jagged Array : 68 milliseconds
Jagged Wrapper : 77 milliseconds
Dense Wrapper : 355 milliseconds
2d Wrapper : 79 milliseconds
Faster versions:
FastMatrix : 144 milliseconds
FastMatrix.Map : 132 milliseconds
如您所见,锯齿状/ 2d包装器的性能几乎与“裸”包装器相同。 Dense的实现仍然要慢得多(可能是由于除了CRL边界检查之外还需要手动计算索引),而其他答案的“更快的版本”处于中间。