お久しぶりです、エンジニアのMasashiです。
今回は機械学習の計算処理などでよく使用される行列の積に関する速度比較を行いました。
自分が関わってきた案件内では、行列の積を求めるような計算処理を使用することはありませんでしたが、
計算処理として非常にポピュラーなものであるので、単純な2つの方法から計算速度を比較してみたいと思います。
測定までの流れ
今回測定を行うのは、1000行1000列の行列と1000行1100列の行列の積を実行した場合の計算完了までに要した時間になります。
行列内のデータは0~99までの乱数を入れています。
比較対象としてシングルスレッドで計算処理を実装した場合、マルチスレッドで計算処理を実装した場合の2つについて検証します。
また、ソースコード上ではGPUを使用した計算処理を実装したものがありますが、実行環境がなかったため、
ソースコードだけ残しておき実行環境ができたら試してみたいと思います。
測定環境
- CPU:Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz (4 CPUs), ~2.9GHz
- メモリ:16GB
- Visual Studio 2015
対象データ
- 計算を実行する行列:1000行1000列・1000行1100列
- 行列内データ:0~99までの乱数
- 計算内容:積
測定対象
- シングルスレッドで計算処理を実装
- マルチスレッドで計算処理を実装
- GPUを使用して計算処理を実装(ソースコードのみ)
テストコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
using Alea; using Alea.Parallel; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace GpuCpuSpeedTest { [GpuManaged] class Program { static void Main(string[] args) { // 配列のサイズ指定で使用する変数 var sizeA = 1000; var sizeB = 1100; // 行列作成 var matrixA = CreateMatrixData(sizeA, sizeA); var matrixB = CreateMatrixData(sizeA, sizeB); var normalTimes = new List<double>(); var parallelTimes = new List<double>(); var gpuTimes = new List<double>(); for (var i = 0; i < 10; i++) { normalTimes.Add(MatrixProductNormal(matrixA,matrixB)); parallelTimes.Add(MatrixProductParallel(matrixA,matrixB)); //gpuTimes.Add(MatrixProductGpu(matrixA, matrixB)); } Console.WriteLine("Normal平均値:" + normalTimes.Average()); Console.WriteLine("Parallel平均値:" + parallelTimes.Average()); //Console.WriteLine("Gpu平均値:" + gpuTimes.Average()); Console.ReadLine(); } // シングルスレッドで計算処理を実装 public static double MatrixProductNormal(double[,] matrixA, double[,] matrixB) { var stopwatch = new Stopwatch(); stopwatch.Start(); double[,] product = new double[matrixA.GetLength(0), matrixB.GetLength(1)]; for (int i = 0; i < matrixA.GetLength(0); i++) { for (int j = 0; j < matrixB.GetLength(1); j++) { for (int k = 0; k < matrixA.GetLength(1); k++) { product[i, j] += matrixA[i, k] * matrixB[k, j]; } } } stopwatch.Stop(); Console.WriteLine("計測時間【Normal】: " + stopwatch.Elapsed.TotalSeconds + "s"); return stopwatch.Elapsed.TotalSeconds; } // マルチスレッドで計算処理を実装 public static double MatrixProductParallel(double[,] matrixA, double[,] matrixB) { var stopwatch = new Stopwatch(); stopwatch.Start(); double[,] product = new double[matrixA.GetLength(0), matrixB.GetLength(1)]; Parallel.For(0, matrixA.GetLength(0), i => { for (int j = 0; j < matrixB.GetLength(1); j++) { for (int k = 0; k < matrixA.GetLength(1); k++) { product[i, j] += matrixA[i, k] * matrixB[k, j]; } } }); stopwatch.Stop(); Console.WriteLine("計測時間【Parallel】: " + stopwatch.Elapsed.TotalSeconds + "s"); return stopwatch.Elapsed.TotalSeconds; } // GPUを使用する方法で計算処理を実装 // 実行環境がないため今回は実装のみ public static double MatrixProductGpu(double[,] matrixA, double[,] matrixB) { var stopwatch = new Stopwatch(); stopwatch.Start(); double[,] product = new double[matrixA.GetLength(0), matrixB.GetLength(1)]; var matrixARows = matrixA.GetLength(0); var gpu = Gpu.Default; gpu.For(0, matrixARows, i => { for (int j = 0; j < matrixB.GetLength(1); j++) { for (int k = 0; k < matrixA.GetLength(1); k++) { product[i, j] += matrixA[i, k] * matrixB[k, j]; } } }); stopwatch.Stop(); Console.WriteLine("計測時間【GPU】: " + stopwatch.Elapsed.TotalSeconds + "s"); return stopwatch.Elapsed.TotalSeconds; } // 行列データの作成 public static double[,] CreateMatrixData(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; } } } |
行列の積を求める計算の測定結果
10回試行した結果の平均値が下記になっています。小数点第1位で四捨五入しています。
計測項目 | 計測時間(s) |
---|---|
シングルスレッドで計算処理を実装 | 33 |
マルチスレッドで計算処理を実装 | 17 |
計測結果についてはマルチスレッドを使用して計算させるとシングルスレッドで計算させた場合と比べておよそ半分の処理時間で計算を終了させることができました。
処理として行っていることは非常にシンプルですが、マルチスレッドで行わせることで速度が非常に速くなることがわかります。
また書き方も非常にシンプルであるため書きやすい点も素晴らしいです。
行列の積を求める計算のまとめ
シングルスレッドで計算処理を実装する場合は、行っていることが単純なLoopだからこそ非常に処理に時間がかかってしまうことがわかります。
マルチスレッドで計算処理を実装する場合は、マルチスレッドを利用してLoop処理を行うことでシングルスレッドで行うよりも速度が速くなることが結果としてわかります。
上記以外にGPUを使用して計算処理を記載することでさらに速度が向上する結果を載せたかったのですが、こちらは実行環境が整い次第試してみたいと思います。単純なLoop処理等での処理速度について解決を行う場合は、マルチスレッドを利用した記載方法に変更してみるのがいいでしょう。また、GPUを利用できる環境がある場合は、C#もGPUを利用して処理する方法が充実してきていますので利用して実装してみてはいかがでしょうか?
今回は行列の積に関する速度比較というよりシングルスレッドを利用した場合とマルチスレッドを利用した場合の速度差の比較記事みたいになってしまいましたが、マルチスレッドを利用するだけで速度は非常に上がります。
処理速度がネックになっている場合は、処理方法をマルチスレッドを利用したものやGPUを利用したものに変更してみてはいかがでしょうか?
今回の記事が皆様のお役に立てば幸いです。