0

実際、i3プロセッサのすべての論理コアを使用する行列乗算用のビジュアルC#プログラムがありますが、Cでの実装方法とその説明を知りたいです。プログラムは、

using System;
using System.Diagnostics;
using System.Threading.Tasks;

namespace MatrixMultiplication
{
    internal class Program
    {
        #region Sequential_Loop

        private 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

        private 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

        private 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 = 800;
            int rowCount = 800;
            int colCount2 = 800;
            double[,] m1 = InitializeMatrix(rowCount, colCount);
            double[,] m2 = InitializeMatrix(colCount, colCount2);
            var result = new double[rowCount,colCount2];

            // First do the sequential version.
            Console.WriteLine("Executing sequential loop...");
            var 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

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

            var 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
    }
}
4

1 に答える 1

1

行列の乗算は、"非常に並列" な問題です。つまり、結果配列の各セルは他のセルの値に依存しないため、簡単に並列化できます。

コード内の並列ソリューションは、行ではなくセルで分割することにより、さらに並列化できます。たとえば、結果マトリックスの各セルを j + i*matBCols として番号付けすることにより (注意このコードはチェックしていません。いくつかのインデックスを切り替えた可能性があります、エラーを見つけた場合はコメントしてください):

private 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*matBCols, ij =>
                                  {
                                      i = ij / matBCols;
                                      j = ij % matBCols;

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

C でこれを機能させる簡単な方法は、結果マトリックスの各セルに対してスレッドを作成することですが、Parallel.For が実際に最適化するために舞台裏でいくつかの計算を行っているため、これは無駄が多く、最適ではありません。計算の速さ。

最良のシナリオでは、配列を分割して、各コアが配列乗算の均等なシェアを取得するようにします。Parallel.For を含む Task Parallel Library (TPL) では、各セルの計算 (私の例では) または行の計算 (オリジナルでは) がタスクに変換されます。Parallel.For は、コアの数を考慮して各コアにワーカー スレッドを割り当て、コアと最小量のスレッド間の作業のバランスを維持しようとします。2 つのコアを使用する完璧なシナリオでは、これは 2 つのスレッドでそれぞれ半分の行列乗算を行うことになります。ただし、TPL にはダイナミック バランシングが組み込まれています。

たとえば、コアの 1 つがビジー状態になった場合 (別のプロセスの実行など)、またはワーカー スレッドの 1 つがブロックされた場合 (仮想メモリからのブロックを待機している場合など)、TPL はより多くのスレッドを生成し、作業負荷のバランスを取り直します。

ここでそれについて読むことができます。

私が言おうとしているのは、Parallel.For の作業を C で複製することは簡単な作業ではないということです。行列乗算の場合、タスクの動的な再割り当てを控えることで、優れたファクシミリを得ることができます。スレッドごとにコア アフィニティを使用して CPU コアと同じ数のスレッドを作成し、それらの間で行列を均等に分割するだけです。

Windows では、以下を使用してコア数を取得しGetSystemInfo(または他のオプションについてはこちらCreateThreadを参照)、 および を使用してコア アフィニティを持つスレッドを作成できますSetThreadAffinityMask

于 2013-10-15T05:09:44.910 に答える