如何提高2D阵列的性能?

时间:2018-10-03 21:57:43

标签: c# performance optimization wrapper

我正在尝试使用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

根据我的测试结果,一个锯齿状的裸阵列是最快的。 但是,就包装器而言,二维包装器相对较快。

现在,我有两个问题:

  1. 为什么锯齿状包装比2d包装要慢?
  2. 有可能使包装纸的运行速度与裸纸一样快吗?

源代码

测试代码

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
}

double [] [] ToDouble(int [] []图片)

    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;
    }

DataConverter2d.Todouble(矩阵图像)

    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;
    }

2 个答案:

答案 0 :(得分:2)

  
      
  1. 为什么锯齿状包装比2d包装要慢?
  2.   

我无法使用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
  
      
  1. 有可能使包装纸的运行速度与裸纸一样快吗?
  2.   

尝试:

  • 简化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边界检查之外还需要手动计算索引),而其他答案的“更快的版本”处于中间。