diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2b53b5..868d947 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,5 +25,5 @@ jobs: run: For /R %%I in (*.sln) do dotnet build %%I shell: cmd - name: test - run: For /R %%I in (*.sln) do dotnet build %%I + run: For /R %%I in (*.sln) do dotnet test %%I shell: cmd diff --git a/C#/forSpbu/MatrixMult.Benchmark/MatrixMult.Benchmark.csproj b/C#/forSpbu/MatrixMult.Benchmark/MatrixMult.Benchmark.csproj new file mode 100644 index 0000000..78592f1 --- /dev/null +++ b/C#/forSpbu/MatrixMult.Benchmark/MatrixMult.Benchmark.csproj @@ -0,0 +1,18 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + + + + + diff --git a/C#/forSpbu/MatrixMult.Benchmark/MatrixMultBenchmark.cs b/C#/forSpbu/MatrixMult.Benchmark/MatrixMultBenchmark.cs new file mode 100644 index 0000000..3f5fb10 --- /dev/null +++ b/C#/forSpbu/MatrixMult.Benchmark/MatrixMultBenchmark.cs @@ -0,0 +1,41 @@ +using BenchmarkDotNet.Attributes; +namespace MatrixMult.Benchmark; + +public class MatrixMultBenchmark +{ + [Params(12, 24, 48, 96, 192, 384, 768, 1536)] + public int Size { get; set; } + private Matrix _fst = new Matrix(); + private Matrix _sec = new Matrix(); + + [GlobalSetup] + public void GenerateRandomMatrix() + { + var random = new Random(); + var fstElements = new int[Size, Size]; + var secElements = new int[Size, Size]; + for (int i = 0; i < Size; i++) + { + for (int j = 0; j < Size; j++) + { + fstElements[i, j] = random.Next(); + secElements[i, j] = random.Next(); + } + } + + _fst = new Matrix(fstElements); + _sec = new Matrix(secElements); + } + + [Benchmark] + public void MultiThreaded() + { + MatrixMultiplier.MultiThreadedMultiply(_fst, _sec); + } + + [Benchmark] + public void SingleThreaded() + { + MatrixMultiplier.SingleThreadedMultiply(_fst, _sec); + } +} \ No newline at end of file diff --git a/C#/forSpbu/MatrixMult.Benchmark/Program.cs b/C#/forSpbu/MatrixMult.Benchmark/Program.cs new file mode 100644 index 0000000..04960b1 --- /dev/null +++ b/C#/forSpbu/MatrixMult.Benchmark/Program.cs @@ -0,0 +1,5 @@ +using MatrixMult.Benchmark; +using BenchmarkDotNet.Running; + +var table = BenchmarkRunner.Run(); +Console.WriteLine(table); \ No newline at end of file diff --git a/C#/forSpbu/MatrixMult.Tests/MatrixMult.Tests.csproj b/C#/forSpbu/MatrixMult.Tests/MatrixMult.Tests.csproj new file mode 100644 index 0000000..7cdd389 --- /dev/null +++ b/C#/forSpbu/MatrixMult.Tests/MatrixMult.Tests.csproj @@ -0,0 +1,24 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + diff --git a/C#/forSpbu/MatrixMult.Tests/MultTests.cs b/C#/forSpbu/MatrixMult.Tests/MultTests.cs new file mode 100644 index 0000000..bab80a0 --- /dev/null +++ b/C#/forSpbu/MatrixMult.Tests/MultTests.cs @@ -0,0 +1,108 @@ +namespace MatrixMult.Tests; + +public static class MultTests +{ + private static readonly (Matrix, Matrix, Matrix?)[] Tests = + { + ( + new Matrix( + new int[,] + { + { 1, 2 }, + { 3, 4 } + }), + new Matrix( + new int[,] + { + { 2, 3 }, + { 4, 5 } + }), + new Matrix( + new int[,] + { + { 10, 13 }, + { 22, 29 } + }) + ), + ( + new Matrix( + new int[,] + { + { 1, 2, 3 }, + { 4, 5, 6 } + }), + new Matrix( + new int[,] + { + { 1, 2 }, + { 4, 5 } + }), + null + ), + ( + new Matrix( + new int[,] + { + { 1, 2, 3 }, + { 4, 5, 6 } + }), + new Matrix( + new int[,] + { + { 1, 2 }, + { 3, 4 }, + { 5, 6 } + }), + new Matrix( + new int[,] + { + { 22, 28 }, + { 49, 64 } + }) + ) + }; + + private static IEnumerable TestCases() + { + foreach (var test in Tests) + { + yield return new TestCaseData(new Func(MatrixMultiplier.MultiThreadedMultiply), test); + yield return new TestCaseData(new Func(MatrixMultiplier.SingleThreadedMultiply), test); + } + } + + private static bool AreMatricesEqual(Matrix fst, Matrix sec) + { + if (fst.Height != sec.Height || fst.Width != sec.Width) + { + return false; + } + + for (int i = 0; i < fst.Height; i++) + { + for (int j = 0; j < sec.Width; j++) + { + if (fst.GetElement(i, j) != sec.GetElement(i, j)) + { + return false; + } + } + } + + return true; + } + + [Test, TestCaseSource(nameof(TestCases))] + public static void MultiplyMatricesTest(Func matrixMultImpl, (Matrix, Matrix, Matrix?) test) + { + if (test.Item3 == null) + { + Assert.Throws(() => matrixMultImpl(test.Item1, test.Item2)); + } + else + { + var multResult = matrixMultImpl(test.Item1, test.Item2); + Assert.That(AreMatricesEqual(multResult, test.Item3), Is.True); + } + } +} \ No newline at end of file diff --git a/C#/forSpbu/MatrixMult.Tests/Usings.cs b/C#/forSpbu/MatrixMult.Tests/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/C#/forSpbu/MatrixMult.Tests/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file diff --git a/C#/forSpbu/MatrixMult/Matrix.cs b/C#/forSpbu/MatrixMult/Matrix.cs new file mode 100644 index 0000000..23b88c2 --- /dev/null +++ b/C#/forSpbu/MatrixMult/Matrix.cs @@ -0,0 +1,101 @@ +namespace MatrixMult; + +/// +/// Class with matrix container +/// +public class Matrix +{ + /// + /// Matrix elements + /// + private readonly int[,] _elements; + + /// + /// Number of matrix rows + /// + public int Height => this._elements.GetLength(0); + + /// + /// Number of matrix columns + /// + public int Width => this._elements.GetLength(1); + + /// + /// Constructs empty matrix + /// + public Matrix() + { + this._elements = new int[0, 0]; + } + + /// + /// Constructs matrix with given elements + /// + /// Matrix elements + public Matrix(int [,] newElements) + { + this._elements = newElements; + } + + /// + /// Constructs matrix with given string lines + /// + /// Array of lines to build matrix from + /// If given array of lines is incorrect + public Matrix(string[] lines) + { + var splitLines = lines.Select(str => str.Split(' ')).ToArray(); + var localHeight = splitLines.Length; + var localWidth = splitLines[0].Length; + + this._elements = new int[localHeight, localWidth]; + for (int i = 0; i < localHeight; i++) + { + if (splitLines[i].Length != localWidth) + { + throw new MatrixCreationException("Inconsistent lines length"); + } + + for (var j = 0; j < localWidth; j++) + { + if (!int.TryParse(splitLines[i][j], out var parseValue)) + { + throw new MatrixCreationException("Incorrect matrix element"); + } + this._elements[i, j] = parseValue; + } + } + } + + /// + /// Returns element by index + /// + /// First index + /// Second index + /// Value of matrix element in given position + /// If some index is out of range + public int GetElement(int firstInd, int secondInd) => + firstInd < 0 || firstInd >= this.Height + ? throw new ArgumentOutOfRangeException(nameof(firstInd)) + : secondInd < 0 || secondInd >= this.Width + ? throw new ArgumentOutOfRangeException(nameof(secondInd)) + : this._elements[firstInd, secondInd]; + + /// + /// Returns matrix as array of string + /// + /// String array matrix representation + public string[] GetStringRepresentation() + { + var lines = new string[this.Height]; + for (int i = 0; i < this.Height; i++) + { + for (int j = 0; j < this.Width; j++) + { + lines[i] += _elements[i, j] + " "; + } + } + + return lines; + } +} \ No newline at end of file diff --git a/C#/forSpbu/MatrixMult/MatrixCreationException.cs b/C#/forSpbu/MatrixMult/MatrixCreationException.cs new file mode 100644 index 0000000..bcd56ac --- /dev/null +++ b/C#/forSpbu/MatrixMult/MatrixCreationException.cs @@ -0,0 +1,15 @@ +namespace MatrixMult; + +/// +/// Class for matrix creation exceptions +/// +public class MatrixCreationException : Exception +{ + public MatrixCreationException() : base() + { + } + + public MatrixCreationException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/C#/forSpbu/MatrixMult/MatrixMult.csproj b/C#/forSpbu/MatrixMult/MatrixMult.csproj new file mode 100644 index 0000000..2b14c81 --- /dev/null +++ b/C#/forSpbu/MatrixMult/MatrixMult.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/C#/forSpbu/MatrixMult/MatrixMultiplier.cs b/C#/forSpbu/MatrixMult/MatrixMultiplier.cs new file mode 100644 index 0000000..486acfa --- /dev/null +++ b/C#/forSpbu/MatrixMult/MatrixMultiplier.cs @@ -0,0 +1,88 @@ +namespace MatrixMult; + +/// +/// Class for matrix multiplication +/// +public static class MatrixMultiplier +{ + /// + /// Performs matrix multiplication in single threaded mode + /// + /// First matrix in multiplication + /// Second matrix in multiplication + /// Result matrix + /// If matrices are incompatible(width of first and height of second are not equal) + public static Matrix SingleThreadedMultiply(Matrix first, Matrix second) + { + if (first.Width != second.Height) + { + throw new MatrixMultiplierException("Incompatible matrices"); + } + + var newElements = new int[first.Height, second.Width]; + for (var i = 0; i < first.Height; i++) + { + for (var j = 0; j < second.Width; j++) + { + for (var k = 0; k < second.Height; k++) + { + newElements[i, j] += first.GetElement(i, k) * second.GetElement(k, j); + } + } + } + + return new Matrix(newElements); + } + + /// + /// Performs matrix multiplication in multi threaded mode + /// + /// First matrix in multiplication + /// Second matrix in multiplication + /// Result matrix + /// If matrices are incompatible(width of first and height of second are not equal) + public static Matrix MultiThreadedMultiply(Matrix first, Matrix second) + { + if (first.Width != second.Height) + { + throw new MatrixMultiplierException("Incompatible matrices"); + } + + var threadsAmount = Math.Min(Environment.ProcessorCount, first.Height); + var rowsForThread = first.Height / threadsAmount; + + var threads = new Thread[threadsAmount]; + var newElements = new int[first.Height, second.Width]; + for (int i = 0; i < threadsAmount; i++) + { + var startRow = i * rowsForThread; + var endRow = (i == threadsAmount - 1) ? first.Height : (i + 1) * rowsForThread; + + threads[i] = new Thread(() => + { + for (var row = startRow; row < endRow; row++) + { + for (int j = 0; j < second.Width; j++) + { + for (int k = 0; k < second.Height; k++) + { + newElements[row, j] += first.GetElement(row, k) * second.GetElement(k, j); + } + } + } + }); + } + + foreach (var thread in threads) + { + thread.Start(); + } + + foreach (var thread in threads) + { + thread.Join(); + } + + return new Matrix(newElements); + } +} \ No newline at end of file diff --git a/C#/forSpbu/MatrixMult/MatrixMultiplierException.cs b/C#/forSpbu/MatrixMult/MatrixMultiplierException.cs new file mode 100644 index 0000000..01b5d2b --- /dev/null +++ b/C#/forSpbu/MatrixMult/MatrixMultiplierException.cs @@ -0,0 +1,15 @@ +namespace MatrixMult; + +/// +/// Class for matrix multiplier exceptions +/// +public class MatrixMultiplierException : Exception +{ + public MatrixMultiplierException() : base() + { + } + + public MatrixMultiplierException(string message) : base(message) + { + } +} \ No newline at end of file diff --git a/C#/forSpbu/MatrixMult/Program.cs b/C#/forSpbu/MatrixMult/Program.cs new file mode 100644 index 0000000..ddf9141 --- /dev/null +++ b/C#/forSpbu/MatrixMult/Program.cs @@ -0,0 +1,58 @@ +using MatrixMult; + +if (args.Length != 3) +{ + Console.WriteLine("Wrong number of arguments(should be mode -s(single) or -m(multi) and two file paths)"); + return; +} + +var mode = args[0]; +var fstFilePath = args[1]; +var secFilePath = args[2]; +if (string.IsNullOrEmpty(fstFilePath) || string.IsNullOrEmpty(secFilePath)) +{ + Console.WriteLine("Wrong(null or empty) paths"); + return; +} +if (mode != "-m" && mode != "-s") +{ + Console.WriteLine("Incorrect mode - s(single) or m(multi)"); + return; +} + +try +{ + var fstMatrix = new Matrix(File.ReadAllLines(fstFilePath)); + var secMatrix = new Matrix(File.ReadAllLines(secFilePath)); + + var result = new Matrix(); + switch (mode) + { + case "-m": + { + result = MatrixMultiplier.MultiThreadedMultiply(fstMatrix, secMatrix); + break; + } + case "-s": + { + result = MatrixMultiplier.SingleThreadedMultiply(fstMatrix, secMatrix); + break; + } + } + + var resultLines = result.GetStringRepresentation(); + File.WriteAllLines("result.txt", resultLines); + Console.WriteLine("Multiplication successful, result matrix has been written to result.txt"); +} +catch (IOException) +{ + Console.WriteLine("File not found"); +} +catch (MatrixCreationException) +{ + Console.WriteLine("Matrix creation error(given incorrect matrices)"); +} +catch (MatrixMultiplierException) +{ + Console.WriteLine("Multiplication error(given incompatible matrices)"); +} diff --git a/C#/forSpbu/forSpbu.sln b/C#/forSpbu/forSpbu.sln index 527f400..04b3136 100644 --- a/C#/forSpbu/forSpbu.sln +++ b/C#/forSpbu/forSpbu.sln @@ -10,6 +10,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "03.03", "03.03", "{882A9B9C EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "10.03", "10.03", "{EA6FC7D9-BDFB-49CD-AC00-FC5DDC5274B0}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "2023", "2023", "{C0783CE2-5615-41A8-B385-64A80FBD28AC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "07.09", "07.09", "{39F19078-6F77-48A7-A107-91BE48897BC7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixMult", "MatrixMult\MatrixMult.csproj", "{2D51ED0F-2E78-41A9-B4D7-CFD36A9FCCEB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixMult.Tests", "MatrixMult.Tests\MatrixMult.Tests.csproj", "{CCF2E7C9-46CB-4D91-8277-761DD7A5CF28}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MatrixMult.Benchmark", "MatrixMult.Benchmark\MatrixMult.Benchmark.csproj", "{85632487-7520-4FD5-9FCB-56E9C2C147E3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,12 +31,28 @@ Global {E007586F-9760-4744-BB25-EDEFD6BA860C}.Release|Any CPU.ActiveCfg = Release|Any CPU {E007586F-9760-4744-BB25-EDEFD6BA860C}.Release|Any CPU.Build.0 = Release|Any CPU {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Release|Any CPU.Build.0 = Release|Any CPU + {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E}.Release|Any CPU.Build.0 = Release|Any CPU + {2D51ED0F-2E78-41A9-B4D7-CFD36A9FCCEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D51ED0F-2E78-41A9-B4D7-CFD36A9FCCEB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D51ED0F-2E78-41A9-B4D7-CFD36A9FCCEB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D51ED0F-2E78-41A9-B4D7-CFD36A9FCCEB}.Release|Any CPU.Build.0 = Release|Any CPU + {CCF2E7C9-46CB-4D91-8277-761DD7A5CF28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CCF2E7C9-46CB-4D91-8277-761DD7A5CF28}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CCF2E7C9-46CB-4D91-8277-761DD7A5CF28}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CCF2E7C9-46CB-4D91-8277-761DD7A5CF28}.Release|Any CPU.Build.0 = Release|Any CPU + {85632487-7520-4FD5-9FCB-56E9C2C147E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {85632487-7520-4FD5-9FCB-56E9C2C147E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {85632487-7520-4FD5-9FCB-56E9C2C147E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {85632487-7520-4FD5-9FCB-56E9C2C147E3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution - {E007586F-9760-4744-BB25-EDEFD6BA860C} = {D3FCB669-E93F-4F0B-B9C5-6592CE93AC7F} + {E007586F-9760-4744-BB25-EDEFD6BA860C} = {D3FCB669-E93F-4F0B-B9C5-6592CE93AC7F} {A4F6ADD5-85FD-4F67-8B29-549DDDF6F82E} = {D3FCB669-E93F-4F0B-B9C5-6592CE93AC7F} + {39F19078-6F77-48A7-A107-91BE48897BC7} = {C0783CE2-5615-41A8-B385-64A80FBD28AC} + {2D51ED0F-2E78-41A9-B4D7-CFD36A9FCCEB} = {39F19078-6F77-48A7-A107-91BE48897BC7} + {CCF2E7C9-46CB-4D91-8277-761DD7A5CF28} = {39F19078-6F77-48A7-A107-91BE48897BC7} + {85632487-7520-4FD5-9FCB-56E9C2C147E3} = {39F19078-6F77-48A7-A107-91BE48897BC7} EndGlobalSection EndGlobal