簡體   English   中英

使C#矩陣代碼更快

[英]Make c# matrix code faster

在處理某些矩陣代碼時,我擔心性能問題。

這里是它的工作原理是:我有一個IMatrix抽象類(所有矩陣操作等),通過實施ColumnMatrix類。

abstract class IMatrix
{
    public int Rows {get;set;}
    public int Columns {get;set;}
    public abstract float At(int row, int column);
}

class ColumnMatrix : IMatrix
{
    private data[];

    public override float At(int row, int column)
    {
        return data[row + columns * this.Rows];
    }
}

在我的應用程序中經常使用該類,但是我擔心性能問題。 測試僅對相同大小的鋸齒狀數組讀取2000000x15矩陣,我得到1359ms的數組訪問再次和9234ms的矩陣訪問:

public void TestAccess()
{
    int iterations = 10;

    int rows = 2000000;
    int columns = 15;
    ColumnMatrix matrix = new ColumnMatrix(rows, columns);
    for (int i = 0; i < rows; i++)
        for (int j = 0; j < columns; j++)
            matrix[i, j] = i + j;

    float[][] equivalentArray = matrix.ToRowsArray();

    TimeSpan totalMatrix = new TimeSpan(0);
    TimeSpan totalArray = new TimeSpan(0);

    float total = 0f;
    for (int iteration = 0; iteration < iterations; iteration++)
    {
        total = 0f;
        DateTime start = DateTime.Now;
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < columns; j++)
                total = matrix.At(i, j);
        totalMatrix += (DateTime.Now - start);

        total += 1f; //Ensure total is read at least once.
        total = total > 0 ? 0f : 0f;

        start = DateTime.Now;
        for (int i = 0; i < rows; i++)
            for (int j = 0; j < columns; j++)
                total = equivalentArray[i][j];
        totalArray += (DateTime.Now - start);
    }

    if (total < 0f)
        logger.Info("Nothing here, just make sure we read total at least once.");

    logger.InfoFormat("Average time for a {0}x{1} access, matrix : {2}ms", rows, columns, totalMatrix.TotalMilliseconds);
    logger.InfoFormat("Average time for a {0}x{1} access, array : {2}ms", rows, columns, totalArray.TotalMilliseconds);
    Assert.IsTrue(true);
}

所以我的問題是:我怎樣才能使這件事更快? 有什么辦法可以使我的ColumnMatrix.At更快? 干杯!

  1. 刪除abstract class IMatrix 這是錯誤的,因為它不是接口,並且調用覆蓋的方法比調用final(也就是非修飾符方法)要慢。
  2. 您可以使用不安全的代碼(指針)來獲取數組的元素,而無需進行數組邊界檢查(更快,但工作更多且不安全)

如果二維數組的性能要好得多,那么您不使用二維數組作為類的內部存儲對象,而不是使用一維數組作為計算索引的開銷嗎?

您可以很容易地對已編寫的數組代碼進行優化,因為很明顯,您正在按順序訪問內存。 這意味着JIT編譯器在將其轉換為本地代碼方面可能會做得更好,並且可以帶來更好的性能。
您沒有考慮的另一件事是,內聯仍然會碰到很多東西,因此如果未內聯At方法(為什么不使用indexer屬性,那么?)會由於調用的使用而遭受巨大的性能損失和堆棧操作。 最后,您應該考慮密封ColumnMatrix類,因為這將使JIT編譯器的優化更加容易(調用肯定比callvirt更好)。

當您使用DateTime.Now衡量性能時,結果是非常隨機的。 時鍾的分辨率約為1/20秒,因此您無需測量實際時間,而是測量時鍾在時鍾中滴答的位置。

您應該改用Stopwatch類,它具有更高的分辨率。

對於元素的每次訪問,您都要進行乘法運算:行+列* this.Rows。 您可能會看到內部是否也可以使用二維數組

您還將獲得額外的開銷,因為事情需要在類中抽象出來。 每次訪問矩陣中的元素時,您都在進行額外的方法調用

更改為此:

interface IMatrix
{
    int Rows {get;set;}
    int Columns {get;set;}
    float At(int row, int column);
}

class ColumnMatrix : IMatrix
{
    private data[,];

    public int Rows {get;set;}
    public int Columns {get;set;}

    public float At(int row, int column)
    {
        return data[row,column];
    }
}

與抽象類相比,使用接口要好得多-如果需要通用類的功能,請為接口添加擴展方法。

同樣,二維矩陣比鋸齒形或扁平形的矩陣要快。

您可以使用並行編程來加快算法的速度。 您可以編譯此代碼,並比較常規矩陣方程(MultiplyMatricesSequential函數)和並行矩陣方程(MultiplyMatricesParallel函數)的性能。 您已經實現了此方法的性能比較功能(在“主要功能”中)。

您可以在Visual Studio 2010(.NET 4.0)下編譯此代碼

namespace MultiplyMatrices
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Concurrent;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;

    class Program
    {
        #region Sequential_Loop
        static void MultiplyMatricesSequential(double[,] matA, double[,] matB,
                                                double[,] result)
        {
            int matACols = matA.GetLength(1);
            int matBCols = matB.GetLength(1);
            int matARows = matA.GetLength(0);

            for (int i = 0; i < matARows; i++)
            {
                for (int j = 0; j < matBCols; j++)
                {
                    for (int k = 0; k < matACols; k++)
                    {
                        result[i, j] += matA[i, k] * matB[k, j];
                    }
                }
            }
        }
        #endregion

        #region Parallel_Loop

        static void MultiplyMatricesParallel(double[,] matA, double[,] matB, double[,] result)
        {
            int matACols = matA.GetLength(1);
            int matBCols = matB.GetLength(1);
            int matARows = matA.GetLength(0);

            // A basic matrix multiplication.
            // Parallelize the outer loop to partition the source array by rows.
            Parallel.For(0, matARows, i =>
            {
                for (int j = 0; j < matBCols; j++)
                {
                    // Use a temporary to improve parallel performance.
                    double temp = 0;
                    for (int k = 0; k < matACols; k++)
                    {
                        temp += matA[i, k] * matB[k, j];
                    }
                    result[i, j] = temp;
                }
            }); // Parallel.For
        }

        #endregion


        #region Main
        static void Main(string[] args)
        {
            // Set up matrices. Use small values to better view 
            // result matrix. Increase the counts to see greater 
            // speedup in the parallel loop vs. the sequential loop.
            int colCount = 180;
            int rowCount = 2000;
            int colCount2 = 270;
            double[,] m1 = InitializeMatrix(rowCount, colCount);
            double[,] m2 = InitializeMatrix(colCount, colCount2);
            double[,] result = new double[rowCount, colCount2];

            // First do the sequential version.
            Console.WriteLine("Executing sequential loop...");
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            MultiplyMatricesSequential(m1, m2, result);
            stopwatch.Stop();
            Console.WriteLine("Sequential loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds);

            // For the skeptics.
            OfferToPrint(rowCount, colCount2, result);

            // Reset timer and results matrix. 
            stopwatch.Reset();
            result = new double[rowCount, colCount2];

            // Do the parallel loop.
            Console.WriteLine("Executing parallel loop...");
            stopwatch.Start();
            MultiplyMatricesParallel(m1, m2, result);
            stopwatch.Stop();
            Console.WriteLine("Parallel loop time in milliseconds: {0}", stopwatch.ElapsedMilliseconds);
            OfferToPrint(rowCount, colCount2, result);

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }


        #endregion

        #region Helper_Methods

        static double[,] InitializeMatrix(int rows, int cols)
        {
            double[,] matrix = new double[rows, cols];

            Random r = new Random();
            for (int i = 0; i < rows; i++)
            {
                for (int j = 0; j < cols; j++)
                {
                    matrix[i, j] = r.Next(100);
                }
            }
            return matrix;
        }

        private static void OfferToPrint(int rowCount, int colCount, double[,] matrix)
        {
            Console.WriteLine("Computation complete. Print results? y/n");
            char c = Console.ReadKey().KeyChar;
            if (c == 'y' || c == 'Y')
            {
                Console.WindowWidth = 180;
                Console.WriteLine();
                for (int x = 0; x < rowCount; x++)
                {
                    Console.WriteLine("ROW {0}: ", x);
                    for (int y = 0; y < colCount; y++)
                    {
                        Console.Write("{0:#.##} ", matrix[x, y]);
                    }
                    Console.WriteLine();
                }

            }
        }

        #endregion
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM