2
\$\begingroup\$

I was trying to use Math.Net's dense matrix class. But, it doesn't support int. So, I have to create a wrapper for a jagged 2d array.

I knew that jagged arrays have better performance.

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

According to my test results, a naked jagged array is the fastest of all. But, in terms of wrapper, the 2d wrapper is relatively faster.

Now, I have two questions:

  1. Why is the jagged wrapper slower than 2d wrapper?
  2. How can I make my wrapper classes work faster, so that they can run as fast as their naked counterparts?

Source Code

Test Code

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 Matrix

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 Jagged Matrix

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 Dense Matrix

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[][] image)

    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(Matrix image)

    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;
    }
\$\endgroup\$
2
\$\begingroup\$

I'm not able to add anything to what t3chb0t wrote in his comment about performance, but below find some comments and improvements of the code. I just wonder what you actually want to measure performance-wise: Is it that important with a fast conversion between int and double? Is that something you do often? Would it be more interesting to measure more ordinary matrix operations (add, multiply, inverse, determinant, transposed etc.)? Or what is the purpose of the matrix at all?

You should not implement IDisposable, because you really don't dispose anything. An array isn't disposable in itself.

Be aware that Matrix2d usually means a matrix that is used in the 2-dimensional euclidean plane in transformation calculations and is limited to the size [3,3] - just as a Matrix3d typically means a [4, 4] matrix used in euclidean space. A better term would be Array2d.

It seems that you confuse Width and Height: Width should be columns and Height should be rows: __matrix2d[Width][Height] but it should be __matrix2d[Height][Width]. The use of x and y is also inconsistent as parameters. Instead use [row, col]

Below I've added some modifications and inline comment in your code:

public class Matrix2d<T> where T : struct, IComparable<T>
{
  private T[][] __matrix2d;

  // You should make Width and Height read only in order to make the matrix fixed in size
  // The fixed size is desirable in order to make the type behave more like a value type,
  // when it participate in calculations. It should never change dimensions and meaning in any context.
  // If you want to change the size of a matrix then create a new.
  // If you really want these properties to be settable, you should reformat the array in the setters
  // or else the matrix is in an inconsistent state.
  public int Width { get; }// set; } // I would call this Columns
  public int Height { get; } //set; } // I would call this Rows

  // This should be redundant because the matrix should never be uninitialized.
  public bool IsEmpty
  {
    get
    {
      return __matrix2d == null;
      //if (__matrix2d == null) return true;
      //else return false;
    }
  }

  // In order to make the matrix fixed in size, you'll need to initialize the matrix in all constructors
  // so this constructor is useless:
  //public Matrix2d()
  //{

  //}

  public Matrix2d(T[,] data)
  {
    if (data == null) throw new ArgumentNullException(nameof(data));

    Width = data.GetLength(0);
    Height = data.GetLength(1);

    __matrix2d = new T[Width][];

    for (int x = 0; x < Width; x++)
    {
      T[] col = __matrix2d[x] = new T[Height];
      for (int y = 0; y < Height; y++)
      {
        col[y] = data[x, y];
      }
    }
  }

  // This should maybe be a real copy constructor
  public Matrix2d(T[][] data)
  {
    if (data == null) throw new ArgumentNullException(nameof(data));

    Width = data.Length;
    Height = data[0].Length;

    __matrix2d = new T[Width][];

    for (int x = 0; x < Width; x++)
    {
      T[] col = __matrix2d[x] = new T[Height];
      for (int y = 0; y < Height; y++)
      {
        col[y] = data[x][y];
      }
    }
  }

  // Name the parameters width and height instead of rows and cols or change everything to Rows and Columns - be consistent
  public Matrix2d(int width, int height)
  {
    Width = width;
    Height = height;
    ResetMatrix();
  }

  // You do this operation more than once, so only one function (DRY-principle)
  private void ResetMatrix()
  {
    __matrix2d = new T[Width][];
    for (int i = 0; i < Width; i++)
    {
      __matrix2d[i] = new T[Height];
    }
  }

  // You do this check mutiple times, so make a method 
  private void ValidateIndices(int x, int y)
  {
    if (x >= 0 && x < Width && y <= 0 && y < Height)
      return;

    throw new ArgumentOutOfRangeException($"{nameof(x)} or {nameof(y)}", "x should be in the range of --- and y in the range of ---");
  }

  public T Get(int x, int y)
  {
    ValidateIndices(x, y);

    // It's fine to check for a null matrix, but use you own proptery IsEmpty
    // In fact you can skip this check if you ensure that the internal array is never uninitialized
    if (IsEmpty) //__matrix2d == null)
    {
      throw new Exception("array is empty");
    }

    // Fine to check for valid values, but what about x < 0 and y < 0?
    //if (x < Width && y < Height)
    //{
    //if (__matrix2d != null) // This is redundant. You do the check at the start of the method
    //{
    return __matrix2d[x][y];
    //}
    //else
    //{
    //  throw new Exception("array is null");
    //}
    //}
    //else
    //{
    //  // This is fine but use the predefined exceptions when appropriate:
    //  //string message = string.Empty;

    //  //if (x >= Width) message = "x-value exceeds Width ";
    //  //if (y >= Height) message += "y-value exceeds Height ";
    //  //message += "in Matrix2d.Get(x,y).";

    //  //throw new Exception(message);

    //  throw new ArgumentOutOfRangeException($"{nameof(x)} or {nameof(y)}", "x should be in the range of --- and y in the range of ---");
    //}
  }

  public void Set(int x, int y, T val)
  {
    ValidateIndices(x, y);

    __matrix2d[x][y] = val;

    // This should never happen:
    //if (__matrix2d == null)
    //{
    //  // DRY:
    //  ResetMatrix();
    //  //__matrix2d = new T[Width][];
    //  //for (int i = 0; i < Width; i++)
    //  //{
    //  //  __matrix2d[i] = new T[Height];
    //  //}
    //}
    //else
    //{
    // This seems really odd and I don't understand why you want to do this.
    //if (Width != __matrix2d.GetLength(0))
    //{
    //  __matrix2d = null;

    //  __matrix2d = new T[Width][];
    //  for (int i = 0; i < Width; i++)
    //  {
    //    __matrix2d[i] = new T[Height];
    //  }
    //}
    //}

    //if (x < Width && y < Height)
    //{
    //  __matrix2d[x][y] = val;
    //}
    //else
    //{

    //  throw new Exception(x + ", " + Width + "," + y + "," + Height);
    //}
  }

  public T[,] Get()
  {
    return To2d(__matrix2d);
  }

  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 k = 0; k < source[0].Length; k++)
      {
        dest[i, k] = source[i][k];
      }
    }

    return dest;
  }

  public T this[int x, int y]
  {
    get
    {
      return Get(x, y);
    }
    set
    {
      Set(x, y, value);
    }
  }

  // In order to maintain a fixed size this method is redundant
  // The client should create a new instance instead.
  //public void Set(T[,] arr)
  //{
  //  if (arr != null)
  //  {
  //    int rows = arr.GetLength(0);
  //    int cols = arr.GetLength(1);

  //    __matrix2d = ToJagged(arr);

  //    Width = rows;
  //    Height = cols;
  //  }
  //  else
  //  {
  //    throw new Exception("array is null");
  //  }
  //}

  public static T[][] ToJagged(T[,] source)
  {
    int Width = source.GetLength(0);
    int Height = source.GetLength(1);

    T[][] dest = new T[Width][];

    for (int i = 0; i < Width; i++)
    {
      dest[i] = new T[Height];

      for (int k = 0; k < Height; k++)
      {
        dest[i][k] = source[i, k];
      }
    }

    return dest;
  }

  // Consider a copy constructor instead
  public Matrix2d<T> Copy()
  {
    return new Matrix2d<T>(this.__matrix2d);
    //return copy;
    //copy.Set(this.Get());

    //return copy;
  }

  public static Matrix2d<T> From2d(T[,] data)
  {
    // You have a nice constructor: Matrix2d(T[,] data),
    // Why not use that:

    return new Matrix2d<T>(data);

    //Matrix2d<T> array2d = new Matrix2d<T>();

    //array2d.Set(data);

    //return array2d;
  }

  public void Clear()
  {
    ResetMatrix();
  }
}
\$\endgroup\$
  • \$\begingroup\$ main question edited. \$\endgroup\$ – user181134 Oct 4 '18 at 22:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy