diff --git a/.gitattributes b/.gitattributes index e69de29..4414986 100644 --- a/.gitattributes +++ b/.gitattributes @@ -0,0 +1,2 @@ +jekyll_site/ru/** linguist-language=Java +jekyll_site/en/** linguist-language=Java diff --git a/.gitignore b/.gitignore index c38fa4e..42e9bb7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .idea *.iml +*.zip +_site* +.repo_*.sh diff --git a/DIRECTORY-TREE.md b/DIRECTORY-TREE.md new file mode 100644 index 0000000..ef0737b --- /dev/null +++ b/DIRECTORY-TREE.md @@ -0,0 +1,61 @@ +## Дерево каталогов + +
+. +├─ jekyll_site +│ ├─ _includes +│ │ ├─ counters_body.html +│ │ └─ counters_head.html +│ ├─ en +│ │ ├─ 2021 +│ │ │ └─ 12 +│ │ │ ├─ 10 +│ │ │ │ └─ optimizing-matrix-multiplication.md +│ │ │ ├─ 13 +│ │ │ │ └─ matrix-rotation-90-degrees.md +│ │ │ └─ 17 +│ │ │ └─ matrix-rotation-180-degrees.md +│ │ ├─ 2022 +│ │ │ └─ 02 +│ │ │ ├─ 09 +│ │ │ │ └─ matrix-multiplication-parallel-streams.md +│ │ │ └─ 11 +│ │ │ └─ winograd-strassen-algorithm.md +│ │ └─ index.md +│ ├─ img +│ │ ├─ block-matrices.svg +│ │ ├─ products.svg +│ │ ├─ sums1.svg +│ │ ├─ sums2.svg +│ │ └─ sums3.svg +│ ├─ ru +│ │ ├─ 2021 +│ │ │ └─ 12 +│ │ │ ├─ 09 +│ │ │ │ └─ optimizing-matrix-multiplication.md +│ │ │ ├─ 12 +│ │ │ │ └─ matrix-rotation-90-degrees.md +│ │ │ └─ 16 +│ │ │ └─ matrix-rotation-180-degrees.md +│ │ ├─ 2022 +│ │ │ └─ 02 +│ │ │ ├─ 08 +│ │ │ │ └─ matrix-multiplication-parallel-streams.md +│ │ │ └─ 10 +│ │ │ └─ winograd-strassen-algorithm.md +│ │ └─ index.md +│ ├─ Gemfile_color +│ ├─ Gemfile_older +│ ├─ _config_color.yml +│ ├─ _config_older.yml +│ └─ robots.txt +├─ CONTRIBUTING.md +├─ DIRECTORY-TREE.md +├─ LICENSE.md +├─ OPEN_LICENSE.txt +├─ README.en.md +├─ README.md +├─ build.sh +├─ package.sh +└─ serve.sh +diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000..205ffbb --- /dev/null +++ b/README.en.md @@ -0,0 +1,17 @@ +## Website pages + +- [Winograd — Strassen algorithm](https://pomodoro3.mircloud.ru/en/2022/02/11/winograd-strassen-algorithm.html) — 11.02.2022. +- [Matrix multiplication in parallel streams](https://pomodoro3.mircloud.ru/en/2022/02/09/matrix-multiplication-parallel-streams.html) — 09.02.2022. +- [Matrix rotation 180 degrees](https://pomodoro3.mircloud.ru/en/2021/12/17/matrix-rotation-180-degrees.html) — 17.12.2021. +- [Matrix rotation 90 degrees](https://pomodoro3.mircloud.ru/en/2021/12/13/matrix-rotation-90-degrees.html) — 13.12.2021. +- [Optimizing matrix multiplication](https://pomodoro3.mircloud.ru/en/2021/12/10/optimizing-matrix-multiplication.html) — 10.12.2021. + +## [Source texts](README.md) + +- Series of the static websites [«Pomodori»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.en.md). +- Used formats — Markdown, Liquid, YAML. +- Build tool — Jekyll with tomato design themes. +- Automation of processes — Bash scripts for command line. +- [build.sh](build.sh) — Building a site in two tomato themes and optimizing the results. +- [serve.sh](serve.sh) — Local deployment to verify the correctness of the build. +- [package.sh](package.sh) — Preparing an archive for subsequent deployment. diff --git a/README.md b/README.md new file mode 100644 index 0000000..8ff27ff --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +## Страницы вёб-сайта + +- [Алгоритм Винограда — Штрассена](https://pomodoro3.mircloud.ru/ru/2022/02/10/winograd-strassen-algorithm.html) — 10.02.2022. +- [Умножение матриц в параллельных потоках](https://pomodoro3.mircloud.ru/ru/2022/02/08/matrix-multiplication-parallel-streams.html) — 08.02.2022. +- [Поворот матрицы на 180 градусов](https://pomodoro3.mircloud.ru/ru/2021/12/16/matrix-rotation-180-degrees.html) — 16.12.2021. +- [Поворот матрицы на 90 градусов](https://pomodoro3.mircloud.ru/ru/2021/12/12/matrix-rotation-90-degrees.html) — 12.12.2021. +- [Оптимизация умножения матриц](https://pomodoro3.mircloud.ru/ru/2021/12/09/optimizing-matrix-multiplication.html) — 09.12.2021. + +## [Исходные тексты](README.en.md) + +- Серия статических вёб-сайтов [«Помидоры»](https://hub.mos.ru/golovin.gg/pomodoro/blob/master/README.md). +- Используемые форматы — Markdown, Liquid, YAML. +- Инструмент сборки — Jekyll с помидорными темами оформления. +- Автоматизация процессов — Bash скрипты для командной строки. +- [build.sh](build.sh) — Сборка сайта в двух помидорных темах и оптимизация результатов. +- [serve.sh](serve.sh) — Локальное развёртывание для проверки корректности сборки. +- [package.sh](package.sh) — Подготовка архива для последующего развёртывания. diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..265203a --- /dev/null +++ b/build.sh @@ -0,0 +1,51 @@ +#!/bin/bash +echo "Сборка сайта в двух помидорных темах и оптимизация результатов." +milliseconds=$(date '+%s%3N') +rm -rf _site +rm -rf _site_older +rm -rf _site_color +echo "Сборка старого помидора." +mkdir -p _site_older +cp -r jekyll_site/_includes _site_older +cp -r jekyll_site/ru _site_older +cp -r jekyll_site/en _site_older +cp -r jekyll_site/ru/index.md _site_older +cp -r jekyll_site/_config_older.yml _site_older/_config.yml +cp -r jekyll_site/Gemfile_older _site_older/Gemfile +cd _site_older || exit +jekyll build +cp -r _site .. +cd .. +echo "Сборка цветного помидора." +mkdir -p _site_color +cp -r jekyll_site/_includes _site_color +cp -r jekyll_site/ru _site_color +cp -r jekyll_site/en _site_color +cp -r jekyll_site/ru/index.md _site_color +cp -r jekyll_site/_config_color.yml _site_color/_config.yml +cp -r jekyll_site/Gemfile_color _site_color/Gemfile +cd _site_color || exit +jekyll build +cp -r _site ../_site/color +cd .. +echo "Копирование без сборки." +cp -r jekyll_site/img _site +cp -r jekyll_site/robots.txt _site +echo "Оптимизация собранного контента." +cd _site || exit +cp -r assets/* . +rm -r assets +rm -r color/assets/favicon.ico +cp -r color/assets/* . +rm -r color/assets +rm -r color/404.html +find . -type f -name '*.html' | sort -r | while read -r file; do + sed -i 's/layout-padding=""/layout-padding/g' "$file" + sed -i 's/ class="language-plaintext highlighter-rouge"//g' "$file" + sed -i 's/ class="language-java highlighter-rouge"//g' "$file" + sed -i 's/
//g' "$file"
+ sed -i 's/<\/code><\/pre><\/div><\/div>/<\/code><\/pre><\/div>/g' "$file"
+ sed -i 's/
/
/g' "$file"
+ sed -i -r 's/
/
/g' "$file"
+done
+echo "Время выполнения сборки: $(("$(date '+%s%3N')" - "$milliseconds")) мс."
diff --git a/jekyll_site/Gemfile_color b/jekyll_site/Gemfile_color
new file mode 100644
index 0000000..136ccb7
--- /dev/null
+++ b/jekyll_site/Gemfile_color
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+gem "jekyll"
+gem "color-tomato-theme"
diff --git a/jekyll_site/Gemfile_older b/jekyll_site/Gemfile_older
new file mode 100644
index 0000000..5e19e6f
--- /dev/null
+++ b/jekyll_site/Gemfile_older
@@ -0,0 +1,3 @@
+source "https://rubygems.org"
+gem "jekyll"
+gem "older-tomato-theme"
diff --git a/jekyll_site/_config_color.yml b/jekyll_site/_config_color.yml
new file mode 100644
index 0000000..8a6b449
--- /dev/null
+++ b/jekyll_site/_config_color.yml
@@ -0,0 +1,20 @@
+# site parameters
+name: "Код с комментариями"
+name_translated: "Code with comments"
+url: "https://pomodoro3.mircloud.ru"
+baseurl: "/color"
+homepage_url: "https://git.org.ru/pomodoro/3"
+homepage_name: "GIT.ORG.RU"
+older_tomato_baseurl: ""
+timezone: "Europe/Moscow"
+author: "Головин Г.Г."
+author_translated: "Golovin G.G."
+translation_caption: "translation from Russian"
+# build parameters
+disable_disk_cache: true
+theme: color-tomato-theme
+defaults:
+ - scope:
+ path: ""
+ values:
+ layout: default
diff --git a/jekyll_site/_config_older.yml b/jekyll_site/_config_older.yml
new file mode 100644
index 0000000..b426c4f
--- /dev/null
+++ b/jekyll_site/_config_older.yml
@@ -0,0 +1,20 @@
+# site parameters
+name: "Код с комментариями"
+name_translated: "Code with comments"
+url: "https://pomodoro3.mircloud.ru"
+baseurl: ""
+homepage_url: "https://git.org.ru/pomodoro/3"
+homepage_name: "GIT.ORG.RU"
+color_tomato_baseurl: "/color"
+timezone: "Europe/Moscow"
+author: "Головин Г.Г."
+author_translated: "Golovin G.G."
+translation_caption: "translation from Russian"
+# build parameters
+disable_disk_cache: true
+theme: older-tomato-theme
+defaults:
+ - scope:
+ path: ""
+ values:
+ layout: default
diff --git a/jekyll_site/_includes/counters_body.html b/jekyll_site/_includes/counters_body.html
new file mode 100644
index 0000000..269f6ab
--- /dev/null
+++ b/jekyll_site/_includes/counters_body.html
@@ -0,0 +1,2 @@
+
+
diff --git a/jekyll_site/_includes/counters_head.html b/jekyll_site/_includes/counters_head.html
new file mode 100644
index 0000000..637ad8a
--- /dev/null
+++ b/jekyll_site/_includes/counters_head.html
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/jekyll_site/en/2021/12/10/optimizing-matrix-multiplication.md b/jekyll_site/en/2021/12/10/optimizing-matrix-multiplication.md
new file mode 100644
index 0000000..f431e77
--- /dev/null
+++ b/jekyll_site/en/2021/12/10/optimizing-matrix-multiplication.md
@@ -0,0 +1,248 @@
+---
+title: Optimizing matrix multiplication
+description: Consider an algorithm for multiplying matrices using three nested loops. The complexity of such an algorithm by definition should be O(n³), but there are...
+sections: [Permutations,Nested loops,Comparing algorithms]
+tags: [java,arrays,multidimensional arrays,matrices,rows,columns,layers,loops]
+canonical_url: /en/2021/12/10/optimizing-matrix-multiplication.html
+url_translated: /ru/2021/12/09/optimizing-matrix-multiplication.html
+title_translated: Оптимизация умножения матриц
+date: 2021.12.10
+lang: en
+---
+
+Consider an algorithm for multiplying matrices using three nested loops. The complexity of such
+an algorithm by definition should be `O(n³)`, but there are particularities related to the execution
+environment — the speed of the algorithm depends on the sequence in which the loops are executed.
+
+Let's compare different permutations of nested loops and the execution time of the algorithms.
+Let's take two matrices: {`L×M`} and {`M×N`} → three loops → six permutations:
+`LMN`, `LNM`, `MLN`, `MNL`, `NLM`, `NML`.
+
+The algorithms that work faster than others are those that write data to the resulting matrix
+*row-wise in layers*: `LMN` and `MLN`, — the percentage difference to other algorithms is substantial
+and depends on the execution environment.
+
+*Further optimization: [Matrix multiplication in parallel streams]({{ '/en/2022/02/09/matrix-multiplication-parallel-streams.html' | relative_url }}).*
+
+## Row-wise algorithm {#row-wise-algorithm}
+
+The outer loop bypasses the rows of the first matrix `L`, then there is a loop across the *common side*
+of the two matrices `M` and it is followed by a loop across the columns of the second matrix `N`.
+Writing to the resulting matrix occurs row-wise, and each row is filled in layers.
+
+```java
+/**
+ * @param l rows of matrix 'a'
+ * @param m columns of matrix 'a'
+ * and rows of matrix 'b'
+ * @param n columns of matrix 'b'
+ * @param a first matrix 'l×m'
+ * @param b second matrix 'm×n'
+ * @return resulting matrix 'l×n'
+ */
+public static int[][] matrixMultiplicationLMN(int l, int m, int n, int[][] a, int[][] b) {
+ // resulting matrix
+ int[][] c = new int[l][n];
+ // bypass the indexes of the rows of matrix 'a'
+ for (int i = 0; i < l; i++)
+ // bypass the indexes of the common side of two matrices:
+ // the columns of matrix 'a' and the rows of matrix 'b'
+ for (int k = 0; k < m; k++)
+ // bypass the indexes of the columns of matrix 'b'
+ for (int j = 0; j < n; j++)
+ // the sum of the products of the elements of the i-th
+ // row of matrix 'a' and the j-th column of matrix 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+
+## Layer-wise algorithm {#layer-wise-algorithm}
+
+The outer loop bypasses the *common side* of the two matrices `M`, then there is a loop across the rows
+of the first matrix `L`, and it is followed by a loop across the columns of the second matrix `N`.
+Writing to the resulting matrix occurs layer-wise, and each layer is filled row-wise.
+
+```java
+/**
+ * @param l rows of matrix 'a'
+ * @param m columns of matrix 'a'
+ * and rows of matrix 'b'
+ * @param n columns of matrix 'b'
+ * @param a first matrix 'l×m'
+ * @param b second matrix 'm×n'
+ * @return resulting matrix 'l×n'
+ */
+public static int[][] matrixMultiplicationMLN(int l, int m, int n, int[][] a, int[][] b) {
+ // resulting matrix
+ int[][] c = new int[l][n];
+ // bypass the indexes of the common side of two matrices:
+ // the columns of matrix 'a' and the rows of matrix 'b'
+ for (int k = 0; k < m; k++)
+ // bypass the indexes of the rows of matrix 'a'
+ for (int i = 0; i < l; i++)
+ // bypass the indexes of the columns of matrix 'b'
+ for (int j = 0; j < n; j++)
+ // the sum of the products of the elements of the i-th
+ // row of matrix 'a' and the j-th column of matrix 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+
+### Other algorithms {#other-algorithms}
+
+The bypass of the columns of the second matrix `N` occurs before the bypass of the *common side* of the
+two matrices `M` and/or before the bypass of the rows of the first matrix `L`.
+
+{% capture collapsed_md %}
+```java
+public static int[][] matrixMultiplicationLNM(int l, int m, int n, int[][] a, int[][] b) {
+ int[][] c = new int[l][n];
+ for (int i = 0; i < l; i++)
+ for (int j = 0; j < n; j++)
+ for (int k = 0; k < m; k++)
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+```java
+public static int[][] matrixMultiplicationNLM(int l, int m, int n, int[][] a, int[][] b) {
+ int[][] c = new int[l][n];
+ for (int j = 0; j < n; j++)
+ for (int i = 0; i < l; i++)
+ for (int k = 0; k < m; k++)
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+```java
+public static int[][] matrixMultiplicationMNL(int l, int m, int n, int[][] a, int[][] b) {
+ int[][] c = new int[l][n];
+ for (int k = 0; k < m; k++)
+ for (int j = 0; j < n; j++)
+ for (int i = 0; i < l; i++)
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+```java
+public static int[][] matrixMultiplicationNML(int l, int m, int n, int[][] a, int[][] b) {
+ int[][] c = new int[l][n];
+ for (int j = 0; j < n; j++)
+ for (int k = 0; k < m; k++)
+ for (int i = 0; i < l; i++)
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Code without comments" content=collapsed_md -%}
+
+## Comparing algorithms {#comparing-algorithms}
+
+To check, we take two matrices `A=[500×700]` and `B=[700×450]`, filled with random numbers. First, we
+compare the correctness of the implementation of the algorithms — all results obtained must match.
+Then we execute each method 10 times and calculate the average execution time.
+
+```java
+// start the program and output the result
+public static void main(String[] args) throws Exception {
+ // incoming data
+ int l = 500, m = 700, n = 450, steps = 10;
+ int[][] a = randomMatrix(l, m), b = randomMatrix(m, n);
+ // map of methods for comparison
+ var methods = new TreeMap>(Map.of(
+ "LMN", () -> matrixMultiplicationLMN(l, m, n, a, b),
+ "LNM", () -> matrixMultiplicationLNM(l, m, n, a, b),
+ "MLN", () -> matrixMultiplicationMLN(l, m, n, a, b),
+ "MNL", () -> matrixMultiplicationMNL(l, m, n, a, b),
+ "NLM", () -> matrixMultiplicationNLM(l, m, n, a, b),
+ "NML", () -> matrixMultiplicationNML(l, m, n, a, b)));
+ int[][] last = null;
+ // bypass the methods map, check the correctness of the returned
+ // results, all results obtained must be equal to each other
+ for (var method : methods.entrySet()) {
+ // next method for comparison
+ var next = methods.higherEntry(method.getKey());
+ // if the current method is not the last — compare the results of two methods
+ if (next != null) System.out.println(method.getKey() + "=" + next.getKey() + ": "
+ // compare the result of executing the current method and the next one
+ + Arrays.deepEquals(method.getValue().call(), next.getValue().call()));
+ // the result of the last method
+ else last = method.getValue().call();
+ }
+ int[][] test = last;
+ // bypass the methods map, measure the execution time of each method
+ for (var method : methods.entrySet())
+ // parameters: title, number of steps, runnable code
+ benchmark(method.getKey(), steps, () -> {
+ try { // execute the method, get the result
+ int[][] result = method.getValue().call();
+ // check the correctness of the results at each step
+ if (!Arrays.deepEquals(result, test)) System.out.print("error");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+}
+```
+
+{% capture collapsed_md %}
+```java
+// helper method, returns a matrix of the specified size
+private static int[][] randomMatrix(int row, int col) {
+ int[][] matrix = new int[row][col];
+ for (int i = 0; i < row; i++)
+ for (int j = 0; j < col; j++)
+ matrix[i][j] = (int) (Math.random() * row * col);
+ return matrix;
+}
+```
+```java
+// helper method for measuring the execution time of the passed code
+private static void benchmark(String title, int steps, Runnable runnable) {
+ long time, avg = 0;
+ System.out.print(title);
+ for (int i = 0; i < steps; i++) {
+ time = System.currentTimeMillis();
+ runnable.run();
+ time = System.currentTimeMillis() - time;
+ // execution time of one step
+ System.out.print(" | " + time);
+ avg += time;
+ }
+ // average execution time
+ System.out.println(" || " + (avg / steps));
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Helper methods" content=collapsed_md -%}
+
+Output depends on the execution environment, time in milliseconds:
+
+```
+LMN=LNM: true
+LNM=MLN: true
+MLN=MNL: true
+MNL=NLM: true
+NLM=NML: true
+LMN | 191 | 109 | 105 | 106 | 105 | 106 | 106 | 105 | 123 | 109 || 116
+LNM | 417 | 418 | 419 | 416 | 416 | 417 | 418 | 417 | 416 | 417 || 417
+MLN | 113 | 115 | 113 | 115 | 114 | 114 | 114 | 115 | 114 | 113 || 114
+MNL | 857 | 864 | 857 | 859 | 860 | 863 | 862 | 860 | 858 | 860 || 860
+NLM | 404 | 404 | 407 | 404 | 406 | 405 | 405 | 404 | 403 | 404 || 404
+NML | 866 | 872 | 867 | 868 | 867 | 868 | 867 | 873 | 869 | 863 || 868
+```
+
+All the methods described above, including collapsed blocks, can be placed in one class.
+
+{% capture collapsed_md %}
+```java
+import java.util.Arrays;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Required imports" content=collapsed_md -%}
diff --git a/jekyll_site/en/2021/12/13/matrix-rotation-90-degrees.md b/jekyll_site/en/2021/12/13/matrix-rotation-90-degrees.md
new file mode 100644
index 0000000..5fbc333
--- /dev/null
+++ b/jekyll_site/en/2021/12/13/matrix-rotation-90-degrees.md
@@ -0,0 +1,97 @@
+---
+title: Matrix rotation 90 degrees
+description: Consider the algorithm for rotating a matrix 90 degrees clockwise and anticlockwise. This algorithm is similar to matrix transpose, with the difference...
+sections: [Transpose,Comparing algorithms]
+tags: [java,arrays,multidimensional arrays,matrices,rows,columns,loops,nested loops]
+canonical_url: /en/2021/12/13/matrix-rotation-90-degrees.html
+url_translated: /ru/2021/12/12/matrix-rotation-90-degrees.html
+title_translated: Поворот матрицы на 90 градусов
+date: 2021.12.13
+lang: en
+---
+
+Consider the algorithm for rotating a matrix 90 degrees clockwise and anticlockwise. This
+algorithm is similar to *matrix transpose*, with the difference that here, for each point,
+one of the coordinates is mirrored.
+
+```java
+// matrix transpose — rows and columns are swapped
+swapped[j][i] = matrix[i][j];
+// clockwise ↻ — rows are mirrored
+rotated[j][i] = matrix[m-i-1][j];
+// anticlockwise ↺ — columns are mirrored
+rotated[j][i] = matrix[i][n-j-1];
+```
+
+*Similar algorithm: [Matrix rotation 180 degrees]({{ '/en/2021/12/17/matrix-rotation-180-degrees.html' | relative_url }}).*
+
+Let's write a method in Java to rotate a matrix {`m×n`} 90 degrees. The additional parameter
+sets the direction of rotation: clockwise or anticlockwise. As an example, let's take a
+rectangular matrix {`4×3`}.
+
+```java
+/**
+ * @param m number of rows of the original matrix
+ * @param n number of columns of the original matrix
+ * @param clock direction of rotation:
+ * clockwise ↻ or anticlockwise ↺
+ * @param matrix the original matrix
+ * @return the rotated matrix
+ */
+public static int[][] rotateMatrix(int m, int n, boolean clock, int[][] matrix) {
+ // new matrix, the number of rows and columns are swapped
+ int[][] rotated = new int[n][m];
+ // bypass the rows of the original matrix
+ for (int i = 0; i < m; i++)
+ // bypass the columns of the original matrix
+ for (int j = 0; j < n; j++)
+ if (clock) // clockwise rotation ↻
+ rotated[j][i] = matrix[m-i-1][j];
+ else // anticlockwise rotation ↺
+ rotated[j][i] = matrix[i][n-j-1];
+ return rotated;
+}
+```
+{% raw %}
+```java
+// start the program and output the result
+public static void main(String[] args) {
+ // incoming data
+ int m = 4, n = 3;
+ int[][] matrix = {{11, 12, 13}, {14, 15, 16}, {17, 18, 19}, {20, 21, 22}};
+ // rotate the matrix and output the result
+ outputMatrix("Original matrix:", matrix);
+ outputMatrix("Clockwise ↻:", rotateMatrix(m, n, true, matrix));
+ outputMatrix("Anticlockwise ↺:", rotateMatrix(m, n, false, matrix));
+}
+```
+{% endraw %}
+```java
+// helper method, prints the matrix to the console row-wise
+public static void outputMatrix(String title, int[][] matrix) {
+ System.out.println(title);
+ for (int[] row : matrix) {
+ for (int el : row)
+ System.out.print(" " + el);
+ System.out.println();
+ }
+}
+```
+
+Output:
+
+```
+Original matrix:
+ 11 12 13
+ 14 15 16
+ 17 18 19
+ 20 21 22
+Clockwise ↻:
+ 20 17 14 11
+ 21 18 15 12
+ 22 19 16 13
+Anticlockwise ↺:
+ 13 16 19 22
+ 12 15 18 21
+ 11 14 17 20
+```
diff --git a/jekyll_site/en/2021/12/17/matrix-rotation-180-degrees.md b/jekyll_site/en/2021/12/17/matrix-rotation-180-degrees.md
new file mode 100644
index 0000000..ecf91c9
--- /dev/null
+++ b/jekyll_site/en/2021/12/17/matrix-rotation-180-degrees.md
@@ -0,0 +1,85 @@
+---
+title: Matrix rotation 180 degrees
+description: Consider the algorithm for rotating a matrix 180 degrees. Unlike the transpose algorithm, here in the resulting matrix the rows and columns are not swapped...
+sections: [Transpose,Comparing algorithms]
+tags: [java,arrays,multidimensional arrays,matrices,rows,columns,loops,nested loops]
+canonical_url: /en/2021/12/17/matrix-rotation-180-degrees.html
+url_translated: /ru/2021/12/16/matrix-rotation-180-degrees.html
+title_translated: Поворот матрицы на 180 градусов
+date: 2021.12.17
+lang: en
+---
+
+Consider the algorithm for rotating a matrix 180 degrees. Unlike the *transpose* algorithm,
+here in the resulting matrix the rows and columns are not swapped, but are mirrored.
+
+```java
+// rows and columns are swapped
+swapped[j][i] = matrix[i][j];
+// rows and columns are mirrored
+rotated[i][j] = matrix[m-i-1][n-j-1];
+```
+
+*Similar algorithm: [Matrix rotation 90 degrees]({{ '/en/2021/12/13/matrix-rotation-90-degrees.html' | relative_url }}).*
+
+Let's write a method in Java to rotate a matrix {`m×n`} 180 degrees. As an example,
+let's take a rectangular matrix {`4×3`}.
+
+```java
+/**
+ * @param m number of rows of the original matrix
+ * @param n number of columns of the original matrix
+ * @param matrix the original matrix
+ * @return the rotated matrix
+ */
+public static int[][] rotateMatrix(int m, int n, int[][] matrix) {
+ // new matrix
+ int[][] rotated = new int[m][n];
+ // bypass the rows of the original matrix
+ for (int i = 0; i < m; i++)
+ // bypass the columns of the original matrix
+ for (int j = 0; j < n; j++)
+ // rows and columns are mirrored
+ rotated[i][j] = matrix[m-i-1][n-j-1];
+ return rotated;
+}
+```
+{% raw %}
+```java
+// start the program and output the result
+public static void main(String[] args) {
+ // incoming data
+ int m = 4, n = 3;
+ int[][] matrix = {{11, 12, 13}, {14, 15, 16}, {17, 18, 19}, {20, 21, 22}};
+ // rotate the matrix and output the result
+ outputMatrix("Original matrix:", matrix);
+ outputMatrix("Rotation by 180°:", rotateMatrix(m, n, matrix));
+}
+```
+{% endraw %}
+```java
+// helper method, prints the matrix to the console row-wise
+public static void outputMatrix(String title, int[][] matrix) {
+ System.out.println(title);
+ for (int[] row : matrix) {
+ for (int el : row)
+ System.out.print(" " + el);
+ System.out.println();
+ }
+}
+```
+
+Output:
+
+```
+Original matrix:
+ 11 12 13
+ 14 15 16
+ 17 18 19
+ 20 21 22
+Rotation by 180°:
+ 22 21 20
+ 19 18 17
+ 16 15 14
+ 13 12 11
+```
diff --git a/jekyll_site/en/2022/02/09/matrix-multiplication-parallel-streams.md b/jekyll_site/en/2022/02/09/matrix-multiplication-parallel-streams.md
new file mode 100644
index 0000000..1ae836a
--- /dev/null
+++ b/jekyll_site/en/2022/02/09/matrix-multiplication-parallel-streams.md
@@ -0,0 +1,184 @@
+---
+title: Matrix multiplication in parallel streams
+description: Consider an algorithm for multiplying rectangular matrices using Java Streams. Let's take the optimized version of the algorithm using three nested loops...
+sections: [Multithreading,Nested loops,Comparing algorithms]
+tags: [java,streams,arrays,multidimensional arrays,matrices,rows,loops]
+canonical_url: /en/2022/02/09/matrix-multiplication-parallel-streams.html
+url_translated: /ru/2022/02/08/matrix-multiplication-parallel-streams.html
+title_translated: Умножение матриц в параллельных потоках
+date: 2022.02.09
+lang: en
+---
+
+Consider an algorithm for multiplying rectangular matrices using Java Streams. Let's take the
+*optimized version* of the algorithm using three nested loops and replace the outer loop with
+a stream. Let's compare the operating time of two algorithms.
+
+We process the rows of the resulting matrix in parallel mode, and populate each row layerwise. Due to
+the parallel use of cache of the execution environment on multi-core machines, the computation time can
+be reduced by more than two times. To check, let's take two rectangular matrices {`L×M`} and {`M×N`}.
+
+*Further optimization: [Winograd — Strassen algorithm]({{ '/en/2022/02/11/winograd-strassen-algorithm.html' | relative_url }}).*
+
+## Parallel stream {#parallel-stream}
+
+We bypass the rows of the first matrix `L` in parallel mode. In each thread there are two nested loops:
+across the *common side* of two matrices `M` and across the columns of the second matrix `N`. Processing
+of the rows of the resulting matrix occurs independently of each other in parallel streams.
+
+```java
+/**
+ * @param l rows of matrix 'a'
+ * @param m columns of matrix 'a'
+ * and rows of matrix 'b'
+ * @param n columns of matrix 'b'
+ * @param a first matrix 'l×m'
+ * @param b second matrix 'm×n'
+ * @return resulting matrix 'l×n'
+ */
+public static int[][] parallelMatrixMultiplication(int l, int m, int n, int[][] a, int[][] b) {
+ // resulting matrix
+ int[][] c = new int[l][n];
+ // bypass the indexes of the rows of matrix 'a' in parallel mode
+ IntStream.range(0, l).parallel().forEach(i -> {
+ // bypass the indexes of the common side of two matrices:
+ // the columns of matrix 'a' and the rows of matrix 'b'
+ for (int k = 0; k < m; k++)
+ // bypass the indexes of the columns of matrix 'b'
+ for (int j = 0; j < n; j++)
+ // the sum of the products of the elements of the i-th
+ // row of matrix 'a' and the j-th column of matrix 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ });
+ return c;
+}
+```
+
+## Three nested loops {#three-nested-loops}
+
+We take the *optimized* variant of algorithm, that uses cache of the execution environment better
+than others. The outer loop bypasses the rows of the first matrix `L`, then there is a loop across
+the *common side* of the two matrices `M` and it is followed by a loop across the columns of the
+second matrix `N`. Writing to the resulting matrix occurs row-wise, and each row is filled in layers.
+
+```java
+/**
+ * @param l rows of matrix 'a'
+ * @param m columns of matrix 'a'
+ * and rows of matrix 'b'
+ * @param n columns of matrix 'b'
+ * @param a first matrix 'l×m'
+ * @param b second matrix 'm×n'
+ * @return resulting matrix 'l×n'
+ */
+public static int[][] sequentialMatrixMultiplication(int l, int m, int n, int[][] a, int[][] b) {
+ // resulting matrix
+ int[][] c = new int[l][n];
+ // bypass the indexes of the rows of matrix 'a'
+ for (int i = 0; i < l; i++)
+ // bypass the indexes of the common side of two matrices:
+ // the columns of matrix 'a' and the rows of matrix 'b'
+ for (int k = 0; k < m; k++)
+ // bypass the indexes of the columns of matrix 'b'
+ for (int j = 0; j < n; j++)
+ // the sum of the products of the elements of the i-th
+ // row of matrix 'a' and the j-th column of matrix 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+
+## Testing {#testing}
+
+To check, we take two matrices `A=[900×1000]` and `B=[1000×750]`, filled with random numbers.
+First, we compare the correctness of the implementation of the two algorithms — matrix products
+must match. Then we execute each method 10 times and calculate the average execution time.
+
+```java
+// start the program and output the result
+public static void main(String[] args) {
+ // incoming data
+ int l = 900, m = 1000, n = 750, steps = 10;
+ int[][] a = randomMatrix(l, m), b = randomMatrix(m, n);
+ // matrix products
+ int[][] c1 = parallelMatrixMultiplication(l, m, n, a, b);
+ int[][] c2 = sequentialMatrixMultiplication(l, m, n, a, b);
+ // check the correctness of the results
+ System.out.println("The results match: " + Arrays.deepEquals(c1, c2));
+ // measure the execution time of two methods
+ benchmark("Stream", steps, () -> {
+ int[][] c = parallelMatrixMultiplication(l, m, n, a, b);
+ if (!Arrays.deepEquals(c, c1)) System.out.print("error");
+ });
+ benchmark("Loops", steps, () -> {
+ int[][] c = sequentialMatrixMultiplication(l, m, n, a, b);
+ if (!Arrays.deepEquals(c, c2)) System.out.print("error");
+ });
+}
+```
+
+{% capture collapsed_md %}
+```java
+// helper method, returns a matrix of the specified size
+private static int[][] randomMatrix(int row, int col) {
+ int[][] matrix = new int[row][col];
+ for (int i = 0; i < row; i++)
+ for (int j = 0; j < col; j++)
+ matrix[i][j] = (int) (Math.random() * row * col);
+ return matrix;
+}
+```
+```java
+// helper method for measuring the execution time of the passed code
+private static void benchmark(String title, int steps, Runnable runnable) {
+ long time, avg = 0;
+ System.out.print(title);
+ for (int i = 0; i < steps; i++) {
+ time = System.currentTimeMillis();
+ runnable.run();
+ time = System.currentTimeMillis() - time;
+ // execution time of one step
+ System.out.print(" | " + time);
+ avg += time;
+ }
+ // average execution time
+ System.out.println(" || " + (avg / steps));
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Helper methods" content=collapsed_md -%}
+
+Output depends on the execution environment, time in milliseconds:
+
+```
+The results match: true
+Stream | 113 | 144 | 117 | 114 | 114 | 117 | 120 | 125 | 111 | 113 || 118
+Loops | 1357 | 530 | 551 | 569 | 535 | 538 | 525 | 517 | 518 | 514 || 615
+```
+
+## Comparing algorithms {#comparing-algorithms}
+
+On an eight-core Linux x64 computer, we create a Windows x64 virtual machine for tests. All
+other things being equal, in the settings, we change the number of processors. We run the
+above test 100 times instead of 10 — we get a summary table of results. Time in milliseconds.
+
+```
+ CPU | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+-------|-----|-----|-----|-----|-----|-----|-----|-----|
+Stream | 522 | 276 | 179 | 154 | 128 | 112 | 110 | 93 |
+Loops | 519 | 603 | 583 | 571 | 545 | 571 | 559 | 467 |
+```
+
+Results: with a large number of processors in the system, the multithreaded algorithm works
+out noticeably faster than three nested loops. The computation time can be reduced by more
+than two times.
+
+All the methods described above, including collapsed blocks, can be placed in one class.
+
+{% capture collapsed_md %}
+```java
+import java.util.Arrays;
+import java.util.stream.IntStream;
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Required imports" content=collapsed_md -%}
diff --git a/jekyll_site/en/2022/02/11/winograd-strassen-algorithm.md b/jekyll_site/en/2022/02/11/winograd-strassen-algorithm.md
new file mode 100644
index 0000000..5a76fc1
--- /dev/null
+++ b/jekyll_site/en/2022/02/11/winograd-strassen-algorithm.md
@@ -0,0 +1,298 @@
+---
+title: Winograd — Strassen algorithm
+description: Consider a modification of Strassen's algorithm for square matrix multiplication with fewer number of summations between blocks than in the ordinary...
+sections: [Multithreading,Block matrices,Comparing algorithms]
+tags: [java,streams,arrays,multidimensional arrays,matrices,recursion,loops,nested loops]
+canonical_url: /en/2022/02/11/winograd-strassen-algorithm.html
+url_translated: /ru/2022/02/10/winograd-strassen-algorithm.html
+title_translated: Алгоритм Винограда — Штрассена
+date: 2022.02.11
+lang: en
+---
+
+Consider a modification of Strassen's algorithm for square matrix multiplication with *fewer* number
+of summations between blocks than in the ordinary algorithm — 15 instead of 18 and the same number
+of multiplications as in the ordinary algorithm — 7. We will use Java Streams.
+
+Recursive partitioning of matrices into blocks during multiplication makes sense up to a certain
+limit, and then it loses its sense, since the Strassen's algorithm does not use cache of the execution
+environment. Therefore, for small blocks we will use a parallel version of nested loops, and for large
+blocks we will perform recursive partitioning in parallel.
+
+We determine the boundary between the two algorithms experimentally — we adjust it to the cache of the
+execution environment. The benefit of Strassen's algorithm becomes more evident on sizable matrices —
+the difference with the algorithm using nested loops becomes larger and depends on the execution
+environment. Let's compare the operating time of two algorithms.
+
+*Algorithm using three nested loops: [Optimizing matrix multiplication]({{ '/en/2021/12/10/optimizing-matrix-multiplication.html' | relative_url }}).*
+
+## Algorithm description {#algorithm-description}
+
+Matrices must be the same size. We partition each matrix into 4 equally sized blocks. The blocks
+must be square, therefore if this is not the case, then first we supplement the matrices with zero
+rows and columns, and after that partition them into blocks. We will remove the redundant rows and
+columns later from the resulting matrix.
+
+{% include image_svg.html src="/img/block-matrices.svg" style="width:221pt; height:33pt;"
+alt="{\displaystyle A={\begin{pmatrix}A_{11}&A_{12}\\A_{21}&A_{22}\end{pmatrix}},\quad B={\begin{pmatrix}B_{11}&B_{12}\\B_{21}&B_{22}\end{pmatrix}}.}" %}
+
+Summation of blocks.
+
+{% include image_svg.html src="/img/sums1.svg" style="width:101pt; height:148pt;"
+alt="{\displaystyle{\begin{aligned}S_{1}&=(A_{21}+A_{22});\\S_{2}&=(S_{1}-A_{11});\\S_{3}&=(A_{11}-A_{21});\\S_{4}&=(A_{12}-S_{2});\\S_{5}&=(B_{12}-B_{11});\\S_{6}&=(B_{22}-S_{5});\\S_{7}&=(B_{22}-B_{12});\\S_{8}&=(S_{6}-B_{21}).\end{aligned}}}" %}
+
+Multiplication of blocks.
+
+{% include image_svg.html src="/img/products.svg" style="width:75pt; height:127pt;"
+alt="{\displaystyle{\begin{aligned}P_{1}&=S_{2}S_{6};\\P_{2}&=A_{11}B_{11};\\P_{3}&=A_{12}B_{21};\\P_{4}&=S_{3}S_{7};\\P_{5}&=S_{1}S_{5};\\P_{6}&=S_{4}B_{22};\\P_{7}&=A_{22}S_{8}.\end{aligned}}}" %}
+
+Summation of blocks.
+
+{% include image_svg.html src="/img/sums2.svg" style="width:78pt; height:31pt;"
+alt="{\displaystyle{\begin{aligned}T_{1}&=P_{1}+P_{2};\\T_{2}&=T_{1}+P_{4}.\end{aligned}}}" %}
+
+Blocks of the resulting matrix.
+
+{% include image_svg.html src="/img/sums3.svg" style="width:240pt; height:33pt;"
+alt="{\displaystyle{\begin{pmatrix}C_{11}&C_{12}\\C_{21}&C_{22}\end{pmatrix}}={\begin{pmatrix}P_{2}+P_{3}&T_{1}+P_{5}+P_{6}\\T_{2}-P_{7}&T_{2}+P_{5}\end{pmatrix}}.}" %}
+
+## Hybrid algorithm {#hybrid-algorithm}
+
+We partition each matrix `A` and `B` into 4 equally sized blocks and, if necessary, we supplement
+the missing parts with zeros. Perform 15 summations and 7 multiplications over the blocks — we get
+4 blocks of the matrix `C`. Remove the redundant zeros, if added, and return the resulting matrix.
+We run recursive partitioning of large blocks in parallel mode, and for small blocks we call the
+algorithm with nested loops.
+
+```java
+/**
+ * @param n matrix size
+ * @param brd minimum matrix size
+ * @param a first matrix 'n×n'
+ * @param b second matrix 'n×n'
+ * @return resulting matrix 'n×n'
+ */
+public static int[][] multiplyMatrices(int n, int brd, int[][] a, int[][] b) {
+ // multiply small blocks using algorithm with nested loops
+ if (n < brd) return simpleMultiplication(n, a, b);
+ // midpoint of the matrix, round up — blocks should
+ // be square, if necessary add zero rows and columns
+ int m = n - n / 2;
+ // blocks of the first matrix
+ int[][] a11 = getQuadrant(m, n, a, true, true);
+ int[][] a12 = getQuadrant(m, n, a, true, false);
+ int[][] a21 = getQuadrant(m, n, a, false, true);
+ int[][] a22 = getQuadrant(m, n, a, false, false);
+ // blocks of the second matrix
+ int[][] b11 = getQuadrant(m, n, b, true, true);
+ int[][] b12 = getQuadrant(m, n, b, true, false);
+ int[][] b21 = getQuadrant(m, n, b, false, true);
+ int[][] b22 = getQuadrant(m, n, b, false, false);
+ // summation of blocks
+ int[][] s1 = sumMatrices(m, a21, a22, true);
+ int[][] s2 = sumMatrices(m, s1, a11, false);
+ int[][] s3 = sumMatrices(m, a11, a21, false);
+ int[][] s4 = sumMatrices(m, a12, s2, false);
+ int[][] s5 = sumMatrices(m, b12, b11, false);
+ int[][] s6 = sumMatrices(m, b22, s5, false);
+ int[][] s7 = sumMatrices(m, b22, b12, false);
+ int[][] s8 = sumMatrices(m, s6, b21, false);
+ int[][][] p = new int[7][][];
+ // multiplication of blocks in parallel streams
+ IntStream.range(0, 7).parallel().forEach(i -> {
+ switch (i) { // recursive calls
+ case 0: p[i] = multiplyMatrices(m, brd, s2, s6); break;
+ case 1: p[i] = multiplyMatrices(m, brd, a11, b11); break;
+ case 2: p[i] = multiplyMatrices(m, brd, a12, b21); break;
+ case 3: p[i] = multiplyMatrices(m, brd, s3, s7); break;
+ case 4: p[i] = multiplyMatrices(m, brd, s1, s5); break;
+ case 5: p[i] = multiplyMatrices(m, brd, s4, b22); break;
+ case 6: p[i] = multiplyMatrices(m, brd, a22, s8); break;
+ }
+ });
+ // summation of blocks
+ int[][] t1 = sumMatrices(m, p[0], p[1], true);
+ int[][] t2 = sumMatrices(m, t1, p[3], true);
+ // blocks of the resulting matrix
+ int[][] c11 = sumMatrices(m, p[1], p[2], true);
+ int[][] c12 = sumMatrices(m, t1, sumMatrices(m, p[4], p[5], true), true);
+ int[][] c21 = sumMatrices(m, t2, p[6], false);
+ int[][] c22 = sumMatrices(m, t2, p[4], true);
+ // assemble a matrix from blocks,
+ // remove zero rows and columns, if added
+ return putQuadrants(m, n, c11, c12, c21, c22);
+}
+```
+
+{% capture collapsed_md %}
+```java
+// helper method for matrix summation
+private static int[][] sumMatrices(int n, int[][] a, int[][] b, boolean sign) {
+ int[][] c = new int[n][n];
+ for (int i = 0; i < n; i++)
+ for (int j = 0; j < n; j++)
+ c[i][j] = sign ? a[i][j] + b[i][j] : a[i][j] - b[i][j];
+ return c;
+}
+```
+```java
+// helper method, gets a block of a matrix
+private static int[][] getQuadrant(int m, int n, int[][] x,
+ boolean first, boolean second) {
+ int[][] q = new int[m][m];
+ if (first) for (int i = 0; i < m; i++)
+ if (second) System.arraycopy(x[i], 0, q[i], 0, m); // x11
+ else System.arraycopy(x[i], m, q[i], 0, n - m); // x12
+ else for (int i = m; i < n; i++)
+ if (second) System.arraycopy(x[i], 0, q[i - m], 0, m); // x21
+ else System.arraycopy(x[i], m, q[i - m], 0, n - m); // x22
+ return q;
+}
+```
+```java
+// helper method, assembles a matrix from blocks
+private static int[][] putQuadrants(int m, int n,
+ int[][] x11, int[][] x12,
+ int[][] x21, int[][] x22) {
+ int[][] x = new int[n][n];
+ for (int i = 0; i < n; i++)
+ if (i < m) {
+ System.arraycopy(x11[i], 0, x[i], 0, m);
+ System.arraycopy(x12[i], 0, x[i], m, n - m);
+ } else {
+ System.arraycopy(x21[i - m], 0, x[i], 0, m);
+ System.arraycopy(x22[i - m], 0, x[i], m, n - m);
+ }
+ return x;
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Helper methods" content=collapsed_md -%}
+
+## Nested loops {#nested-loops}
+
+To supplement the previous algorithm and to compare with it, we take the *optimized* variant of nested
+loops, that uses cache of the execution environment better than others — processing of the rows of the
+resulting matrix occurs independently of each other in parallel streams. For small matrices, we use this
+algorithm — large matrices we partition into small blocks and use the same algorithm.
+
+```java
+/**
+ * @param n matrix size
+ * @param a first matrix 'n×n'
+ * @param b second matrix 'n×n'
+ * @return resulting matrix 'n×n'
+ */
+public static int[][] simpleMultiplication(int n, int[][] a, int[][] b) {
+ // the resulting matrix
+ int[][] c = new int[n][n];
+ // bypass the rows of matrix 'a' in parallel mode
+ IntStream.range(0, n).parallel().forEach(i -> {
+ // bypass the indexes of the common side of two matrices:
+ // the columns of matrix 'a' and the rows of matrix 'b'
+ for (int k = 0; k < n; k++)
+ // bypass the indexes of the columns of matrix 'b'
+ for (int j = 0; j < n; j++)
+ // the sum of the products of the elements of the i-th
+ // row of matrix 'a' and the j-th column of matrix 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ });
+ return c;
+}
+```
+
+## Testing {#testing}
+
+To check, we take two square matrices `A=[1000×1000]` and `B=[1000×1000]`, filled with random numbers.
+Take the minimum block size `[200×200]` elements. First, we compare the correctness of the implementation
+of the two algorithms — matrix products must match. Then we execute each method 10 times and calculate
+the average execution time.
+
+```java
+// start the program and output the result
+public static void main(String[] args) {
+ // incoming data
+ int n = 1000, brd = 200, steps = 10;
+ int[][] a = randomMatrix(n, n), b = randomMatrix(n, n);
+ // matrix products
+ int[][] c1 = multiplyMatrices(n, brd, a, b);
+ int[][] c2 = simpleMultiplication(n, a, b);
+ // check the correctness of the results
+ System.out.println("The results match: " + Arrays.deepEquals(c1, c2));
+ // measure the execution time of two methods
+ benchmark("Hybrid algorithm", steps, () -> {
+ int[][] c = multiplyMatrices(n, brd, a, b);
+ if (!Arrays.deepEquals(c, c1)) System.out.print("error");
+ });
+ benchmark("Nested loops ", steps, () -> {
+ int[][] c = simpleMultiplication(n, a, b);
+ if (!Arrays.deepEquals(c, c2)) System.out.print("error");
+ });
+}
+```
+
+{% capture collapsed_md %}
+```java
+// helper method, returns a matrix of the specified size
+private static int[][] randomMatrix(int row, int col) {
+ int[][] matrix = new int[row][col];
+ for (int i = 0; i < row; i++)
+ for (int j = 0; j < col; j++)
+ matrix[i][j] = (int) (Math.random() * row * col);
+ return matrix;
+}
+```
+```java
+// helper method for measuring the execution time of the passed code
+private static void benchmark(String title, int steps, Runnable runnable) {
+ long time, avg = 0;
+ System.out.print(title);
+ for (int i = 0; i < steps; i++) {
+ time = System.currentTimeMillis();
+ runnable.run();
+ time = System.currentTimeMillis() - time;
+ // execution time of one step
+ System.out.print(" | " + time);
+ avg += time;
+ }
+ // average execution time
+ System.out.println(" || " + (avg / steps));
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Helper methods" content=collapsed_md -%}
+
+Output depends on the execution environment, time in milliseconds:
+
+```
+The results match: true
+Hybrid algorithm | 196 | 177 | 156 | 205 | 154 | 165 | 133 | 118 | 132 | 134 || 157
+Nested loops | 165 | 164 | 168 | 167 | 168 | 168 | 170 | 179 | 173 | 168 || 169
+```
+
+## Comparing algorithms {#comparing-algorithms}
+
+On an eight-core Linux x64 computer, execute the above test 100 times instead of 10. Take the minimum
+block size `[brd=200]` elements. Change only `n` — sizes of both matrices `A=[n×n]` and `B=[n×n]`. Get
+a summary table of results. Time in milliseconds.
+
+```
+ n | 900 | 1000 | 1100 | 1200 | 1300 | 1400 | 1500 | 1600 | 1700 |
+-----------------|-----|------|------|------|------|------|------|------|------|
+Hybrid algorithm | 96 | 125 | 169 | 204 | 260 | 313 | 384 | 482 | 581 |
+Nested loops | 119 | 162 | 235 | 281 | 361 | 497 | 651 | 793 | 971 |
+```
+
+Results: the benefit of the Strassen algorithm becomes more evident on large matrices, when the
+size of the matrix itself is several times larger than the size of the minimal block, and depends
+on the execution environment.
+
+All the methods described above, including collapsed blocks, can be placed in one class.
+
+{% capture collapsed_md %}
+```java
+import java.util.Arrays;
+import java.util.stream.IntStream;
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Required imports" content=collapsed_md -%}
diff --git a/jekyll_site/en/index.md b/jekyll_site/en/index.md
new file mode 100644
index 0000000..93f6b72
--- /dev/null
+++ b/jekyll_site/en/index.md
@@ -0,0 +1,76 @@
+---
+title: Code with comments
+description: Notes about programming with code snippets and comments. Problem solutions and solution descriptions.
+sections: [Problem solutions and solution descriptions]
+tags: [java,algorithms,implementation,arrays,multidimensional arrays,matrices,loops,streams]
+canonical_url: /en/
+url_translated: /ru/
+title_translated: Код с комментариями
+lang: en
+---
+
+{%- assign articles = "" | split: "" %}
+{%- assign articles = articles | push: "Winograd — Strassen algorithm" %}
+{%- capture article_brief %}
+Consider a modification of Strassen's algorithm for square matrix multiplication with *fewer* number
+of summations between blocks than in the ordinary algorithm — 15 instead of 18 and the same number
+of multiplications as in the ordinary algorithm — 7. We will use Java Streams.
+
+Recursive partitioning of matrices into blocks during multiplication makes sense up to a certain
+limit, and then it loses its sense, since the Strassen's algorithm does not use cache of the execution
+environment. Therefore, for small blocks we will use a parallel version of nested loops, and for large
+blocks we will perform recursive partitioning in parallel.
+
+We determine the boundary between the two algorithms experimentally — we adjust it to the cache of the
+execution environment. The benefit of Strassen's algorithm becomes more evident on sizable matrices —
+the difference with the algorithm using nested loops becomes larger and depends on the execution
+environment. Let's compare the operating time of two algorithms.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- assign articles = articles | push: "Matrix multiplication in parallel streams" %}
+{%- capture article_brief %}
+Consider an algorithm for multiplying rectangular matrices using Java Streams. Let's take the
+*optimized version* of the algorithm using three nested loops and replace the outer loop with
+a stream. Let's compare the operating time of two algorithms.
+
+We process the rows of the resulting matrix in parallel mode, and populate each row layerwise. Due to
+the parallel use of cache of the execution environment on multi-core machines, the computation time can
+be reduced by more than two times. To check, let's take two rectangular matrices {`L×M`} and {`M×N`}.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- assign articles = articles | push: "Matrix rotation 180 degrees" %}
+{%- capture article_brief %}
+Consider the algorithm for rotating a matrix 180 degrees. Unlike the *transpose* algorithm,
+here in the resulting matrix the rows and columns are not swapped, but are mirrored.
+
+Let's write a method in Java to rotate a matrix {`m×n`} 180 degrees. As an example,
+let's take a rectangular matrix {`4×3`}.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- assign articles = articles | push: "Matrix rotation 90 degrees" %}
+{%- capture article_brief %}
+Consider the algorithm for rotating a matrix 90 degrees clockwise and anticlockwise. This
+algorithm is similar to *matrix transpose*, with the difference that here, for each point,
+one of the coordinates is mirrored.
+
+Let's write a method in Java to rotate a matrix {`m×n`} 90 degrees. The additional parameter
+sets the direction of rotation: clockwise or anticlockwise. As an example, let's take a
+rectangular matrix {`4×3`}.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- assign articles = articles | push: "Optimizing matrix multiplication" %}
+{%- capture article_brief %}
+Consider an algorithm for multiplying matrices using three nested loops. The complexity of such
+an algorithm by definition should be `O(n³)`, but there are particularities related to the execution
+environment — the speed of the algorithm depends on the sequence in which the loops are executed.
+
+Let's compare different permutations of nested loops and the execution time of the algorithms.
+Let's take two matrices: {`L×M`} and {`M×N`} → three loops → six permutations:
+`LMN`, `LNM`, `MLN`, `MNL`, `NLM`, `NML`.
+
+The algorithms that work faster than others are those that write data to the resulting matrix
+*row-wise in layers*: `LMN` and `MLN`, — the percentage difference to other algorithms is substantial
+and depends on the execution environment.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- include main_page.html articles = articles -%}
diff --git a/jekyll_site/img/block-matrices.svg b/jekyll_site/img/block-matrices.svg
new file mode 100644
index 0000000..4922908
--- /dev/null
+++ b/jekyll_site/img/block-matrices.svg
@@ -0,0 +1,50 @@
+
diff --git a/jekyll_site/img/products.svg b/jekyll_site/img/products.svg
new file mode 100644
index 0000000..4d45eb4
--- /dev/null
+++ b/jekyll_site/img/products.svg
@@ -0,0 +1,84 @@
+
diff --git a/jekyll_site/img/sums1.svg b/jekyll_site/img/sums1.svg
new file mode 100644
index 0000000..1551ed8
--- /dev/null
+++ b/jekyll_site/img/sums1.svg
@@ -0,0 +1,125 @@
+
diff --git a/jekyll_site/img/sums2.svg b/jekyll_site/img/sums2.svg
new file mode 100644
index 0000000..72abbac
--- /dev/null
+++ b/jekyll_site/img/sums2.svg
@@ -0,0 +1,34 @@
+
diff --git a/jekyll_site/img/sums3.svg b/jekyll_site/img/sums3.svg
new file mode 100644
index 0000000..2ee22df
--- /dev/null
+++ b/jekyll_site/img/sums3.svg
@@ -0,0 +1,63 @@
+
diff --git a/jekyll_site/robots.txt b/jekyll_site/robots.txt
new file mode 100644
index 0000000..49bee10
--- /dev/null
+++ b/jekyll_site/robots.txt
@@ -0,0 +1,7 @@
+User-agent: *
+Disallow: *404*
+
+Sitemap: https://pomodoro3.mircloud.ru/pagesmap.xml
+Sitemap: https://pomodoro3.mircloud.ru/color/pagesmap.xml
+
+Host: https://pomodoro3.mircloud.ru
diff --git a/jekyll_site/ru/2021/12/09/optimizing-matrix-multiplication.md b/jekyll_site/ru/2021/12/09/optimizing-matrix-multiplication.md
new file mode 100644
index 0000000..1b0d2a5
--- /dev/null
+++ b/jekyll_site/ru/2021/12/09/optimizing-matrix-multiplication.md
@@ -0,0 +1,246 @@
+---
+title: Оптимизация умножения матриц
+description: Рассмотрим алгоритм перемножения матриц с использованием трёх вложенных циклов. Сложность такого алгоритма по определению должна составлять O(n³), но есть...
+sections: [Перестановки,Вложенные циклы,Сравнение алгоритмов]
+tags: [java,массивы,многомерные массивы,матрицы,строки,колонки,слои,циклы]
+canonical_url: /ru/2021/12/09/optimizing-matrix-multiplication.html
+url_translated: /en/2021/12/10/optimizing-matrix-multiplication.html
+title_translated: Optimizing matrix multiplication
+date: 2021.12.09
+---
+
+Рассмотрим алгоритм перемножения матриц с использованием трёх вложенных циклов. Сложность такого
+алгоритма по определению должна составлять `O(n³)`, но есть особенности, связанные со средой
+выполнения — скорость работы алгоритма зависит от последовательности, в которой выполняются циклы.
+
+Сравним различные варианты перестановок вложенных циклов и время выполнения алгоритмов.
+Возьмём две матрицы: {`L×M`} и {`M×N`} → три цикла → шесть перестановок:
+`LMN`, `LNM`, `MLN`, `MNL`, `NLM`, `NML`.
+
+Быстрее других отрабатывают те алгоритмы, которые пишут данные в результирующую матрицу *построчно слоями*:
+`LMN` и `MLN`, — разница в процентах к другим алгоритмам значительная и зависит от среды выполнения.
+
+*Дальнейшая оптимизация: [Умножение матриц в параллельных потоках]({{ '/ru/2022/02/08/matrix-multiplication-parallel-streams.html' | relative_url }}).*
+
+## Построчный алгоритм {#row-wise-algorithm}
+
+Внешний цикл обходит строки первой матрицы `L`, далее идёт цикл по *общей стороне* двух матриц `M`
+и за ним цикл по колонкам второй матрицы `N`. Запись в результирующую матрицу происходит построчно,
+а каждая строка заполняется слоями.
+
+```java
+/**
+ * @param l строки матрицы 'a'
+ * @param m колонки матрицы 'a'
+ * и строки матрицы 'b'
+ * @param n колонки матрицы 'b'
+ * @param a первая матрица 'l×m'
+ * @param b вторая матрица 'm×n'
+ * @return результирующая матрица 'l×n'
+ */
+public static int[][] matrixMultiplicationLMN(int l, int m, int n, int[][] a, int[][] b) {
+ // результирующая матрица
+ int[][] c = new int[l][n];
+ // обходим индексы строк матрицы 'a'
+ for (int i = 0; i < l; i++)
+ // обходим индексы общей стороны двух матриц:
+ // колонок матрицы 'a' и строк матрицы 'b'
+ for (int k = 0; k < m; k++)
+ // обходим индексы колонок матрицы 'b'
+ for (int j = 0; j < n; j++)
+ // сумма произведений элементов i-ой строки
+ // матрицы 'a' и j-ой колонки матрицы 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+
+## Послойный алгоритм {#layer-wise-algorithm}
+
+Внешний цикл обходит *общую сторону* двух матриц `M`, далее идёт цикл по строкам первой матрицы
+`L` и за ним цикл по колонкам второй матрицы `N`. Запись в результирующую матрицу происходит слоями,
+а каждый слой заполняется построчно.
+
+```java
+/**
+ * @param l строки матрицы 'a'
+ * @param m колонки матрицы 'a'
+ * и строки матрицы 'b'
+ * @param n колонки матрицы 'b'
+ * @param a первая матрица 'l×m'
+ * @param b вторая матрица 'm×n'
+ * @return результирующая матрица 'l×n'
+ */
+public static int[][] matrixMultiplicationMLN(int l, int m, int n, int[][] a, int[][] b) {
+ // результирующая матрица
+ int[][] c = new int[l][n];
+ // обходим индексы общей стороны двух матриц:
+ // колонок матрицы 'a' и строк матрицы 'b'
+ for (int k = 0; k < m; k++)
+ // обходим индексы строк матрицы 'a'
+ for (int i = 0; i < l; i++)
+ // обходим индексы колонок матрицы 'b'
+ for (int j = 0; j < n; j++)
+ // сумма произведений элементов i-ой строки
+ // матрицы 'a' и j-ой колонки матрицы 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+
+### Прочие алгоритмы {#other-algorithms}
+
+Обход колонок второй матрицы `N` происходит перед обходом *общей стороны* двух матриц `M`
+и/или перед обходом строк первой матрицы `L`.
+
+{% capture collapsed_md %}
+```java
+public static int[][] matrixMultiplicationLNM(int l, int m, int n, int[][] a, int[][] b) {
+ int[][] c = new int[l][n];
+ for (int i = 0; i < l; i++)
+ for (int j = 0; j < n; j++)
+ for (int k = 0; k < m; k++)
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+```java
+public static int[][] matrixMultiplicationNLM(int l, int m, int n, int[][] a, int[][] b) {
+ int[][] c = new int[l][n];
+ for (int j = 0; j < n; j++)
+ for (int i = 0; i < l; i++)
+ for (int k = 0; k < m; k++)
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+```java
+public static int[][] matrixMultiplicationMNL(int l, int m, int n, int[][] a, int[][] b) {
+ int[][] c = new int[l][n];
+ for (int k = 0; k < m; k++)
+ for (int j = 0; j < n; j++)
+ for (int i = 0; i < l; i++)
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+```java
+public static int[][] matrixMultiplicationNML(int l, int m, int n, int[][] a, int[][] b) {
+ int[][] c = new int[l][n];
+ for (int j = 0; j < n; j++)
+ for (int k = 0; k < m; k++)
+ for (int i = 0; i < l; i++)
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Код без комментариев" content=collapsed_md -%}
+
+## Сравнение алгоритмов {#comparing-algorithms}
+
+Для проверки возьмём две матрицы `A=[500×700]` и `B=[700×450]`, заполненные случайными числами.
+Сначала сравниваем между собой корректность реализации алгоритмов — все полученные результаты
+должны совпадать. Затем выполняем каждый метод по 10 раз и подсчитываем среднее время выполнения.
+
+```java
+// запускаем программу и выводим результат
+public static void main(String[] args) throws Exception {
+ // входящие данные
+ int l = 500, m = 700, n = 450, steps = 10;
+ int[][] a = randomMatrix(l, m), b = randomMatrix(m, n);
+ // карта методов для сравнения
+ var methods = new TreeMap>(Map.of(
+ "LMN", () -> matrixMultiplicationLMN(l, m, n, a, b),
+ "LNM", () -> matrixMultiplicationLNM(l, m, n, a, b),
+ "MLN", () -> matrixMultiplicationMLN(l, m, n, a, b),
+ "MNL", () -> matrixMultiplicationMNL(l, m, n, a, b),
+ "NLM", () -> matrixMultiplicationNLM(l, m, n, a, b),
+ "NML", () -> matrixMultiplicationNML(l, m, n, a, b)));
+ int[][] last = null;
+ // обходим карту методов, проверяем корректность результатов,
+ // все полученные результаты должны быть равны друг другу
+ for (var method : methods.entrySet()) {
+ // следующий метод для сравнения
+ var next = methods.higherEntry(method.getKey());
+ // если текущий метод не последний — сравниваем результаты двух методов
+ if (next != null) System.out.println(method.getKey() + "=" + next.getKey() + ": "
+ // сравниваем результат выполнения текущего метода и следующего за ним
+ + Arrays.deepEquals(method.getValue().call(), next.getValue().call()));
+ // результат выполнения последнего метода
+ else last = method.getValue().call();
+ }
+ int[][] test = last;
+ // обходим карту методов, замеряем время работы каждого метода
+ for (var method : methods.entrySet())
+ // параметры: заголовок, количество шагов, исполняемый код
+ benchmark(method.getKey(), steps, () -> {
+ try { // выполняем метод, получаем результат
+ int[][] result = method.getValue().call();
+ // проверяем корректность результатов на каждом шаге
+ if (!Arrays.deepEquals(result, test)) System.out.print("error");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+}
+```
+
+{% capture collapsed_md %}
+```java
+// вспомогательный метод, возвращает матрицу указанного размера
+private static int[][] randomMatrix(int row, int col) {
+ int[][] matrix = new int[row][col];
+ for (int i = 0; i < row; i++)
+ for (int j = 0; j < col; j++)
+ matrix[i][j] = (int) (Math.random() * row * col);
+ return matrix;
+}
+```
+```java
+// вспомогательный метод для замера времени работы переданного кода
+private static void benchmark(String title, int steps, Runnable runnable) {
+ long time, avg = 0;
+ System.out.print(title);
+ for (int i = 0; i < steps; i++) {
+ time = System.currentTimeMillis();
+ runnable.run();
+ time = System.currentTimeMillis() - time;
+ // время выполнения одного шага
+ System.out.print(" | " + time);
+ avg += time;
+ }
+ // среднее время выполнения
+ System.out.println(" || " + (avg / steps));
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Вспомогательные методы" content=collapsed_md -%}
+
+Вывод зависит от среды выполнения, время в миллисекундах:
+
+```
+LMN=LNM: true
+LNM=MLN: true
+MLN=MNL: true
+MNL=NLM: true
+NLM=NML: true
+LMN | 191 | 109 | 105 | 106 | 105 | 106 | 106 | 105 | 123 | 109 || 116
+LNM | 417 | 418 | 419 | 416 | 416 | 417 | 418 | 417 | 416 | 417 || 417
+MLN | 113 | 115 | 113 | 115 | 114 | 114 | 114 | 115 | 114 | 113 || 114
+MNL | 857 | 864 | 857 | 859 | 860 | 863 | 862 | 860 | 858 | 860 || 860
+NLM | 404 | 404 | 407 | 404 | 406 | 405 | 405 | 404 | 403 | 404 || 404
+NML | 866 | 872 | 867 | 868 | 867 | 868 | 867 | 873 | 869 | 863 || 868
+```
+
+Все описанные выше методы, включая свёрнутые блоки, можно поместить в одном классе.
+
+{% capture collapsed_md %}
+```java
+import java.util.Arrays;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.Callable;
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Необходимые импорты" content=collapsed_md -%}
diff --git a/jekyll_site/ru/2021/12/12/matrix-rotation-90-degrees.md b/jekyll_site/ru/2021/12/12/matrix-rotation-90-degrees.md
new file mode 100644
index 0000000..0c57a95
--- /dev/null
+++ b/jekyll_site/ru/2021/12/12/matrix-rotation-90-degrees.md
@@ -0,0 +1,96 @@
+---
+title: Поворот матрицы на 90 градусов
+description: Рассмотрим алгоритм поворота матрицы на 90 градусов по часовой стрелке и против часовой стрелки. Этот алгоритм похож на транспонирование матрицы с тем...
+sections: [Транспонирование,Сравнение алгоритмов]
+tags: [java,массивы,многомерные массивы,матрицы,строки,колонки,циклы,вложенные циклы]
+canonical_url: /ru/2021/12/12/matrix-rotation-90-degrees.html
+url_translated: /en/2021/12/13/matrix-rotation-90-degrees.html
+title_translated: Matrix rotation 90 degrees
+date: 2021.12.12
+---
+
+Рассмотрим алгоритм поворота матрицы на 90 градусов по часовой стрелке и против часовой стрелки.
+Этот алгоритм похож на *транспонирование матрицы* с тем отличием, что здесь для каждой точки
+одна из координат отображается зеркально.
+
+```java
+// транспонирование матрицы — строки и колонки меняются местами
+swapped[j][i] = matrix[i][j];
+// по часовой стрелке ↻ — строки отображаются зеркально
+rotated[j][i] = matrix[m-i-1][j];
+// против часовой стрелки ↺ — колонки отображаются зеркально
+rotated[j][i] = matrix[i][n-j-1];
+```
+
+*Похожий алгоритм: [Поворот матрицы на 180 градусов]({{ '/ru/2021/12/16/matrix-rotation-180-degrees.html' | relative_url }}).*
+
+Напишем метод на Java для поворота матрицы {`m×n`} на 90 градусов. Дополнительный параметр задаёт
+направление поворота: по часовой стрелке или против часовой стрелки. Для примера возьмём
+прямоугольную матрицу {`4×3`}.
+
+```java
+/**
+ * @param m количество строк исходной матрицы
+ * @param n количество колонок исходной матрицы
+ * @param clock направление поворота: по часовой
+ * стрелке ↻ или против часовой стрелки ↺
+ * @param matrix исходная матрица
+ * @return повёрнутая матрица
+ */
+public static int[][] rotateMatrix(int m, int n, boolean clock, int[][] matrix) {
+ // новая матрица, количества строк и колонок меняются местами
+ int[][] rotated = new int[n][m];
+ // обходим строки исходной матрицы
+ for (int i = 0; i < m; i++)
+ // обходим колонки исходной матрицы
+ for (int j = 0; j < n; j++)
+ if (clock) // поворот по часовой стрелке ↻
+ rotated[j][i] = matrix[m-i-1][j];
+ else // поворот против часовой стрелки ↺
+ rotated[j][i] = matrix[i][n-j-1];
+ return rotated;
+}
+```
+{% raw %}
+```java
+// запускаем программу и выводим результат
+public static void main(String[] args) {
+ // исходные данные
+ int m = 4, n = 3;
+ int[][] matrix = {{11, 12, 13}, {14, 15, 16}, {17, 18, 19}, {20, 21, 22}};
+ // поворачиваем матрицу и выводим результат
+ outputMatrix("Исходная матрица:", matrix);
+ outputMatrix("По часовой стрелке ↻:", rotateMatrix(m, n, true, matrix));
+ outputMatrix("Против часовой стрелки ↺:", rotateMatrix(m, n, false, matrix));
+}
+```
+{% endraw %}
+```java
+// вспомогательный метод, выводит матрицу в консоль построчно
+public static void outputMatrix(String title, int[][] matrix) {
+ System.out.println(title);
+ for (int[] row : matrix) {
+ for (int el : row)
+ System.out.print(" " + el);
+ System.out.println();
+ }
+}
+```
+
+Вывод:
+
+```
+Исходная матрица:
+ 11 12 13
+ 14 15 16
+ 17 18 19
+ 20 21 22
+По часовой стрелке ↻:
+ 20 17 14 11
+ 21 18 15 12
+ 22 19 16 13
+Против часовой стрелки ↺:
+ 13 16 19 22
+ 12 15 18 21
+ 11 14 17 20
+```
diff --git a/jekyll_site/ru/2021/12/16/matrix-rotation-180-degrees.md b/jekyll_site/ru/2021/12/16/matrix-rotation-180-degrees.md
new file mode 100644
index 0000000..7ba6f47
--- /dev/null
+++ b/jekyll_site/ru/2021/12/16/matrix-rotation-180-degrees.md
@@ -0,0 +1,84 @@
+---
+title: Поворот матрицы на 180 градусов
+description: Рассмотрим алгоритм разворота матрицы на 180 градусов. В отличие от алгоритма транспонирования, здесь в результирующей матрице строки и колонки не меняются...
+sections: [Транспонирование,Сравнение алгоритмов]
+tags: [java,массивы,многомерные массивы,матрицы,строки,колонки,циклы,вложенные циклы]
+canonical_url: /ru/2021/12/16/matrix-rotation-180-degrees.html
+url_translated: /en/2021/12/17/matrix-rotation-180-degrees.html
+title_translated: Matrix rotation 180 degrees
+date: 2021.12.16
+---
+
+Рассмотрим алгоритм разворота матрицы на 180 градусов. В отличие от алгоритма *транспонирования*,
+здесь в результирующей матрице строки и колонки не меняются местами, но отображаются зеркально.
+
+```java
+// строки и колонки меняются местами
+swapped[j][i] = matrix[i][j];
+// строки и колонки отображаются зеркально
+rotated[i][j] = matrix[m-i-1][n-j-1];
+```
+
+*Похожий алгоритм: [Поворот матрицы на 90 градусов]({{ '/ru/2021/12/12/matrix-rotation-90-degrees.html' | relative_url }}).*
+
+Напишем метод на Java для поворота матрицы {`m×n`} на 180 градусов. Для примера возьмём
+прямоугольную матрицу {`4×3`}.
+
+```java
+/**
+ * @param m количество строк исходной матрицы
+ * @param n количество колонок исходной матрицы
+ * @param matrix исходная матрица
+ * @return повёрнутая матрица
+ */
+public static int[][] rotateMatrix(int m, int n, int[][] matrix) {
+ // новая матрица
+ int[][] rotated = new int[m][n];
+ // обходим строки исходной матрицы
+ for (int i = 0; i < m; i++)
+ // обходим колонки исходной матрицы
+ for (int j = 0; j < n; j++)
+ // строки и колонки отображаются зеркально
+ rotated[i][j] = matrix[m-i-1][n-j-1];
+ return rotated;
+}
+```
+{% raw %}
+```java
+// запускаем программу и выводим результат
+public static void main(String[] args) {
+ // исходные данные
+ int m = 4, n = 3;
+ int[][] matrix = {{11, 12, 13}, {14, 15, 16}, {17, 18, 19}, {20, 21, 22}};
+ // поворачиваем матрицу и выводим результат
+ outputMatrix("Исходная матрица:", matrix);
+ outputMatrix("Поворот на 180°:", rotateMatrix(m, n, matrix));
+}
+```
+{% endraw %}
+```java
+// вспомогательный метод, выводит матрицу в консоль построчно
+public static void outputMatrix(String title, int[][] matrix) {
+ System.out.println(title);
+ for (int[] row : matrix) {
+ for (int el : row)
+ System.out.print(" " + el);
+ System.out.println();
+ }
+}
+```
+
+Вывод:
+
+```
+Исходная матрица:
+ 11 12 13
+ 14 15 16
+ 17 18 19
+ 20 21 22
+Поворот на 180°:
+ 22 21 20
+ 19 18 17
+ 16 15 14
+ 13 12 11
+```
diff --git a/jekyll_site/ru/2022/02/08/matrix-multiplication-parallel-streams.md b/jekyll_site/ru/2022/02/08/matrix-multiplication-parallel-streams.md
new file mode 100644
index 0000000..6c0b3fb
--- /dev/null
+++ b/jekyll_site/ru/2022/02/08/matrix-multiplication-parallel-streams.md
@@ -0,0 +1,182 @@
+---
+title: Умножение матриц в параллельных потоках
+description: Рассмотрим алгоритм перемножения прямоугольных матриц с использованием потоков Java Stream. Возьмём оптимизированный вариант алгоритма на трёх вложенных...
+sections: [Многопоточность,Вложенные циклы,Сравнение алгоритмов]
+tags: [java,потоки,массивы,многомерные массивы,матрицы,строки,циклы]
+canonical_url: /ru/2022/02/08/matrix-multiplication-parallel-streams.html
+url_translated: /en/2022/02/09/matrix-multiplication-parallel-streams.html
+title_translated: Matrix multiplication in parallel streams
+date: 2022.02.08
+---
+
+Рассмотрим алгоритм перемножения прямоугольных матриц с использованием потоков Java Stream. Возьмём
+*оптимизированный вариант* алгоритма на трёх вложенных циклах и заменим внешний цикл на поток. Сравним
+время работы двух алгоритмов.
+
+Строки результирующей матрицы обрабатываем в параллельном режиме, а каждую строку заполняем слоями.
+За счёт параллельного использования кеша среды выполнения на многоядерных машинах время вычислений
+можно сократить более чем в два раза. Для проверки возьмём две прямоугольные матрицы: {`L×M`} и {`M×N`}.
+
+*Дальнейшая оптимизация: [Алгоритм Винограда — Штрассена]({{ '/ru/2022/02/10/winograd-strassen-algorithm.html' | relative_url }}).*
+
+## Параллельный поток {#parallel-stream}
+
+Строки первой матрицы `L` обходим в параллельном режиме. Внутри каждого потока два вложенных цикла:
+по *общей стороне* двух матриц `M` и по колонкам второй матрицы `N`. Обработка строк результирующей
+матрицы происходит независимо друг от друга в параллельных потоках.
+
+```java
+/**
+ * @param l строки матрицы 'a'
+ * @param m колонки матрицы 'a'
+ * и строки матрицы 'b'
+ * @param n колонки матрицы 'b'
+ * @param a первая матрица 'l×m'
+ * @param b вторая матрица 'm×n'
+ * @return результирующая матрица 'l×n'
+ */
+public static int[][] parallelMatrixMultiplication(int l, int m, int n, int[][] a, int[][] b) {
+ // результирующая матрица
+ int[][] c = new int[l][n];
+ // обходим индексы строк матрицы 'a' в параллельном режиме
+ IntStream.range(0, l).parallel().forEach(i -> {
+ // обходим индексы общей стороны двух матриц:
+ // колонок матрицы 'a' и строк матрицы 'b'
+ for (int k = 0; k < m; k++)
+ // обходим индексы колонок матрицы 'b'
+ for (int j = 0; j < n; j++)
+ // сумма произведений элементов i-ой строки
+ // матрицы 'a' и j-ой колонки матрицы 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ });
+ return c;
+}
+```
+
+## Три вложенных цикла {#three-nested-loops}
+
+Возьмём оптимизированный вариант алгоритма, который лучше прочих использует кеш среды выполнения.
+Внешний цикл обходит строки первой матрицы `L`, далее идёт цикл по *общей стороне* двух матриц `M`
+и за ним цикл по колонкам второй матрицы `N`. Запись в результирующую матрицу происходит построчно,
+а каждая строка заполняется слоями.
+
+```java
+/**
+ * @param l строки матрицы 'a'
+ * @param m колонки матрицы 'a'
+ * и строки матрицы 'b'
+ * @param n колонки матрицы 'b'
+ * @param a первая матрица 'l×m'
+ * @param b вторая матрица 'm×n'
+ * @return результирующая матрица 'l×n'
+ */
+public static int[][] sequentialMatrixMultiplication(int l, int m, int n, int[][] a, int[][] b) {
+ // результирующая матрица
+ int[][] c = new int[l][n];
+ // обходим индексы строк матрицы 'a'
+ for (int i = 0; i < l; i++)
+ // обходим индексы общей стороны двух матриц:
+ // колонок матрицы 'a' и строк матрицы 'b'
+ for (int k = 0; k < m; k++)
+ // обходим индексы колонок матрицы 'b'
+ for (int j = 0; j < n; j++)
+ // сумма произведений элементов i-ой строки
+ // матрицы 'a' и j-ой колонки матрицы 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ return c;
+}
+```
+
+## Тестирование {#testing}
+
+Для проверки возьмём две матрицы: `A=[900×1000]` и `B=[1000×750]`, заполненные случайными числами.
+Сначала сравниваем между собой корректность реализации двух алгоритмов — произведения матриц
+должны совпадать. Затем выполняем каждый метод по 10 раз и подсчитываем среднее время выполнения.
+
+```java
+// запускаем программу и выводим результат
+public static void main(String[] args) {
+ // входящие данные
+ int l = 900, m = 1000, n = 750, steps = 10;
+ int[][] a = randomMatrix(l, m), b = randomMatrix(m, n);
+ // произведения матриц
+ int[][] c1 = parallelMatrixMultiplication(l, m, n, a, b);
+ int[][] c2 = sequentialMatrixMultiplication(l, m, n, a, b);
+ // проверяем корректность результатов
+ System.out.println("Результаты совпадают: " + Arrays.deepEquals(c1, c2));
+ // замеряем время работы двух методов
+ benchmark("Потоки", steps, () -> {
+ int[][] c = parallelMatrixMultiplication(l, m, n, a, b);
+ if (!Arrays.deepEquals(c, c1)) System.out.print("ошибка");
+ });
+ benchmark("Циклы", steps, () -> {
+ int[][] c = sequentialMatrixMultiplication(l, m, n, a, b);
+ if (!Arrays.deepEquals(c, c2)) System.out.print("ошибка");
+ });
+}
+```
+
+{% capture collapsed_md %}
+```java
+// вспомогательный метод, возвращает матрицу указанного размера
+private static int[][] randomMatrix(int row, int col) {
+ int[][] matrix = new int[row][col];
+ for (int i = 0; i < row; i++)
+ for (int j = 0; j < col; j++)
+ matrix[i][j] = (int) (Math.random() * row * col);
+ return matrix;
+}
+```
+```java
+// вспомогательный метод для замера времени работы переданного кода
+private static void benchmark(String title, int steps, Runnable runnable) {
+ long time, avg = 0;
+ System.out.print(title);
+ for (int i = 0; i < steps; i++) {
+ time = System.currentTimeMillis();
+ runnable.run();
+ time = System.currentTimeMillis() - time;
+ // время выполнения одного шага
+ System.out.print(" | " + time);
+ avg += time;
+ }
+ // среднее время выполнения
+ System.out.println(" || " + (avg / steps));
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Вспомогательные методы" content=collapsed_md -%}
+
+Вывод зависит от среды выполнения, время в миллисекундах:
+
+```
+Результаты совпадают: true
+Потоки | 113 | 144 | 117 | 114 | 114 | 117 | 120 | 125 | 111 | 113 || 118
+Циклы | 1357 | 530 | 551 | 569 | 535 | 538 | 525 | 517 | 518 | 514 || 615
+```
+
+## Сравнение алгоритмов {#comparing-algorithms}
+
+На восьмиядерном компьютере Linux x64 создаём для тестов виртуальную машину Windows x64. При
+прочих равных условиях в настройках меняем количество процессоров. Запускаем вышеописанный
+тест 100 раз вместо 10 — получаем сводную таблицу результатов. Время в миллисекундах.
+
+```
+ ЦПУ | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
+-------|-----|-----|-----|-----|-----|-----|-----|-----|
+Потоки | 522 | 276 | 179 | 154 | 128 | 112 | 110 | 93 |
+Циклы | 519 | 603 | 583 | 571 | 545 | 571 | 559 | 467 |
+```
+
+Результаты: при большом количестве процессоров в системе многопоточный алгоритм отрабатывает
+заметно быстрее, чем три вложенных цикла. Время вычислений можно сократить более чем в два раза.
+
+Все описанные выше методы, включая свёрнутые блоки, можно поместить в одном классе.
+
+{% capture collapsed_md %}
+```java
+import java.util.Arrays;
+import java.util.stream.IntStream;
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Необходимые импорты" content=collapsed_md -%}
diff --git a/jekyll_site/ru/2022/02/10/winograd-strassen-algorithm.md b/jekyll_site/ru/2022/02/10/winograd-strassen-algorithm.md
new file mode 100644
index 0000000..845646f
--- /dev/null
+++ b/jekyll_site/ru/2022/02/10/winograd-strassen-algorithm.md
@@ -0,0 +1,293 @@
+---
+title: Алгоритм Винограда — Штрассена
+description: Рассмотрим модификацию алгоритма Штрассена для умножения квадратных матриц с меньшим количеством сложений между блоками, чем в обычном алгоритме — 15 вместо...
+sections: [Многопоточность,Блочные матрицы,Сравнение алгоритмов]
+tags: [java,потоки,массивы,многомерные массивы,матрицы,рекурсия,циклы,вложенные циклы]
+canonical_url: /ru/2022/02/10/winograd-strassen-algorithm.html
+url_translated: /en/2022/02/11/winograd-strassen-algorithm.html
+title_translated: Winograd — Strassen algorithm
+date: 2022.02.10
+---
+
+Рассмотрим модификацию алгоритма Штрассена для умножения квадратных матриц с *меньшим* количеством
+сложений между блоками, чем в обычном алгоритме — 15 вместо 18 и таким же количеством умножений как
+в обычном алгоритме — 7. Будем использовать потоки Java Stream.
+
+Рекурсивное дробление матриц на блоки при перемножении имеет смысл до определенного предела, а дальше
+теряет смысл, так как алгоритм Штрассена не использует кеш среды выполнения. Поэтому для малых блоков
+будем использовать параллельный вариант вложенных циклов, а для больших блоков параллельно будем
+выполнять рекурсивное дробление.
+
+Границу между двумя алгоритмами определяем экспериментально — подстраиваем под кеш среды выполнения.
+Выгода алгоритма Штрассена становится заметнее на больших матрицах — отличие от алгоритма на вложенных
+циклах становится больше и зависит от среды выполнения. Сравним время работы двух алгоритмов.
+
+*Алгоритм на трёх вложенных циклах: [Оптимизация умножения матриц]({{ '/ru/2021/12/09/optimizing-matrix-multiplication.html' | relative_url }}).*
+
+## Описание алгоритма {#algorithm-description}
+
+Матрицы должны быть одинакового размера. Разделяем каждую матрицу на 4 равных блока. Блоки должны быть
+квадратными, поэтому если это не так, тогда сначала дополняем матрицы нулевыми строками и столбцами,
+а после этого разделяем на блоки. Лишние строки и столбцы потом уберём из результирующей матрицы.
+
+{% include image_svg.html src="/img/block-matrices.svg" style="width:221pt; height:33pt;"
+alt="{\displaystyle A={\begin{pmatrix}A_{11}&A_{12}\\A_{21}&A_{22}\end{pmatrix}},\quad B={\begin{pmatrix}B_{11}&B_{12}\\B_{21}&B_{22}\end{pmatrix}}.}" %}
+
+Суммируем блоки.
+
+{% include image_svg.html src="/img/sums1.svg" style="width:101pt; height:148pt;"
+alt="{\displaystyle{\begin{aligned}S_{1}&=(A_{21}+A_{22});\\S_{2}&=(S_{1}-A_{11});\\S_{3}&=(A_{11}-A_{21});\\S_{4}&=(A_{12}-S_{2});\\S_{5}&=(B_{12}-B_{11});\\S_{6}&=(B_{22}-S_{5});\\S_{7}&=(B_{22}-B_{12});\\S_{8}&=(S_{6}-B_{21}).\end{aligned}}}" %}
+
+Умножаем блоки.
+
+{% include image_svg.html src="/img/products.svg" style="width:75pt; height:127pt;"
+alt="{\displaystyle{\begin{aligned}P_{1}&=S_{2}S_{6};\\P_{2}&=A_{11}B_{11};\\P_{3}&=A_{12}B_{21};\\P_{4}&=S_{3}S_{7};\\P_{5}&=S_{1}S_{5};\\P_{6}&=S_{4}B_{22};\\P_{7}&=A_{22}S_{8}.\end{aligned}}}" %}
+
+Суммируем блоки.
+
+{% include image_svg.html src="/img/sums2.svg" style="width:78pt; height:31pt;"
+alt="{\displaystyle{\begin{aligned}T_{1}&=P_{1}+P_{2};\\T_{2}&=T_{1}+P_{4}.\end{aligned}}}" %}
+
+Блоки результирующей матрицы.
+
+{% include image_svg.html src="/img/sums3.svg" style="width:240pt; height:33pt;"
+alt="{\displaystyle{\begin{pmatrix}C_{11}&C_{12}\\C_{21}&C_{22}\end{pmatrix}}={\begin{pmatrix}P_{2}+P_{3}&T_{1}+P_{5}+P_{6}\\T_{2}-P_{7}&T_{2}+P_{5}\end{pmatrix}}.}" %}
+
+## Гибридный алгоритм {#hybrid-algorithm}
+
+Каждую матрицу `A` и `B` делим на 4 *равных* блока, при необходимости дополняем недостающие части
+нулями. Выполняем 15 сложений и 7 умножений над блоками — получаем 4 блока матрицы `C`. Убираем
+лишние нули, если добавляли, и возвращаем результирующую матрицу. Рекурсивное дробление больших
+блоков запускаем в параллельном режиме, а для малых блоков вызываем алгоритм на вложенных циклах.
+
+```java
+/**
+ * @param n размер матрицы
+ * @param brd минимальный размер матрицы
+ * @param a первая матрица 'n×n'
+ * @param b вторая матрица 'n×n'
+ * @return результирующая матрица 'n×n'
+ */
+public static int[][] multiplyMatrices(int n, int brd, int[][] a, int[][] b) {
+ // малые блоки перемножаем с помощью алгоритма на вложенных циклах
+ if (n < brd) return simpleMultiplication(n, a, b);
+ // серединная точка матрицы, округляем в большую сторону — блоки должны
+ // быть квадратными, при необходимости добавляем нулевые строки и столбцы
+ int m = n - n / 2;
+ // блоки первой матрицы
+ int[][] a11 = getQuadrant(m, n, a, true, true);
+ int[][] a12 = getQuadrant(m, n, a, true, false);
+ int[][] a21 = getQuadrant(m, n, a, false, true);
+ int[][] a22 = getQuadrant(m, n, a, false, false);
+ // блоки второй матрицы
+ int[][] b11 = getQuadrant(m, n, b, true, true);
+ int[][] b12 = getQuadrant(m, n, b, true, false);
+ int[][] b21 = getQuadrant(m, n, b, false, true);
+ int[][] b22 = getQuadrant(m, n, b, false, false);
+ // суммируем блоки
+ int[][] s1 = sumMatrices(m, a21, a22, true);
+ int[][] s2 = sumMatrices(m, s1, a11, false);
+ int[][] s3 = sumMatrices(m, a11, a21, false);
+ int[][] s4 = sumMatrices(m, a12, s2, false);
+ int[][] s5 = sumMatrices(m, b12, b11, false);
+ int[][] s6 = sumMatrices(m, b22, s5, false);
+ int[][] s7 = sumMatrices(m, b22, b12, false);
+ int[][] s8 = sumMatrices(m, s6, b21, false);
+ int[][][] p = new int[7][][];
+ // перемножаем блоки в параллельных потоках
+ IntStream.range(0, 7).parallel().forEach(i -> {
+ switch (i) { // рекурсивные вызовы
+ case 0: p[i] = multiplyMatrices(m, brd, s2, s6); break;
+ case 1: p[i] = multiplyMatrices(m, brd, a11, b11); break;
+ case 2: p[i] = multiplyMatrices(m, brd, a12, b21); break;
+ case 3: p[i] = multiplyMatrices(m, brd, s3, s7); break;
+ case 4: p[i] = multiplyMatrices(m, brd, s1, s5); break;
+ case 5: p[i] = multiplyMatrices(m, brd, s4, b22); break;
+ case 6: p[i] = multiplyMatrices(m, brd, a22, s8); break;
+ }
+ });
+ // суммируем блоки
+ int[][] t1 = sumMatrices(m, p[0], p[1], true);
+ int[][] t2 = sumMatrices(m, t1, p[3], true);
+ // блоки результирующей матрицы
+ int[][] c11 = sumMatrices(m, p[1], p[2], true);
+ int[][] c12 = sumMatrices(m, t1, sumMatrices(m, p[4], p[5], true), true);
+ int[][] c21 = sumMatrices(m, t2, p[6], false);
+ int[][] c22 = sumMatrices(m, t2, p[4], true);
+ // собираем результирующую матрицу из блоков,
+ // убираем нулевые строки и столбцы, если добавляли
+ return putQuadrants(m, n, c11, c12, c21, c22);
+}
+```
+
+{% capture collapsed_md %}
+```java
+// вспомогательный метод для суммирования матриц
+private static int[][] sumMatrices(int n, int[][] a, int[][] b, boolean sign) {
+ int[][] c = new int[n][n];
+ for (int i = 0; i < n; i++)
+ for (int j = 0; j < n; j++)
+ c[i][j] = sign ? a[i][j] + b[i][j] : a[i][j] - b[i][j];
+ return c;
+}
+```
+```java
+// вспомогательный метод, получает блок матрицы
+private static int[][] getQuadrant(int m, int n, int[][] x,
+ boolean first, boolean second) {
+ int[][] q = new int[m][m];
+ if (first) for (int i = 0; i < m; i++)
+ if (second) System.arraycopy(x[i], 0, q[i], 0, m); // x11
+ else System.arraycopy(x[i], m, q[i], 0, n - m); // x12
+ else for (int i = m; i < n; i++)
+ if (second) System.arraycopy(x[i], 0, q[i - m], 0, m); // x21
+ else System.arraycopy(x[i], m, q[i - m], 0, n - m); // x22
+ return q;
+}
+```
+```java
+// вспомогательный метод, собирает матрицу из блоков
+private static int[][] putQuadrants(int m, int n,
+ int[][] x11, int[][] x12,
+ int[][] x21, int[][] x22) {
+ int[][] x = new int[n][n];
+ for (int i = 0; i < n; i++)
+ if (i < m) {
+ System.arraycopy(x11[i], 0, x[i], 0, m);
+ System.arraycopy(x12[i], 0, x[i], m, n - m);
+ } else {
+ System.arraycopy(x21[i - m], 0, x[i], 0, m);
+ System.arraycopy(x22[i - m], 0, x[i], m, n - m);
+ }
+ return x;
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Вспомогательные методы" content=collapsed_md -%}
+
+## Вложенные циклы {#nested-loops}
+
+Для дополнения предыдущего алгоритма и для сравнения с ним же будем использовать *оптимизированный*
+вариант вложенных циклов, который лучше прочих использует кеш среды выполнения — обработка строк
+результирующей матрицы выполняется независимо друг от друга в параллельных потоках. Для малых матриц
+будем использовать этот алгоритм — большие матрицы делим на малые блоки и используем этот же алгоритм.
+
+```java
+/**
+ * @param n размер матрицы
+ * @param a первая матрица 'n×n'
+ * @param b вторая матрица 'n×n'
+ * @return результирующая матрица 'n×n'
+ */
+public static int[][] simpleMultiplication(int n, int[][] a, int[][] b) {
+ // результирующая матрица
+ int[][] c = new int[n][n];
+ // обходим индексы строк матрицы 'a' в параллельном режиме
+ IntStream.range(0, n).parallel().forEach(i -> {
+ // обходим индексы общей стороны двух матриц:
+ // колонок матрицы 'a' и строк матрицы 'b'
+ for (int k = 0; k < n; k++)
+ // обходим индексы колонок матрицы 'b'
+ for (int j = 0; j < n; j++)
+ // сумма произведений элементов i-ой строки
+ // матрицы 'a' и j-ой колонки матрицы 'b'
+ c[i][j] += a[i][k] * b[k][j];
+ });
+ return c;
+}
+```
+
+## Тестирование {#testing}
+
+Для проверки возьмём две квадратные матрицы `A=[1000×1000]` и `B=[1000×1000]`, заполненные случайными
+числами. Минимальный размер блока возьмём `[200×200]` элементов. Сначала сравниваем между собой
+корректность реализации двух алгоритмов — произведения матриц должны совпадать. Затем выполняем каждый
+метод по 10 раз и подсчитываем среднее время выполнения.
+
+```java
+// запускаем программу и выводим результат
+public static void main(String[] args) {
+ // входящие данные
+ int n = 1000, brd = 200, steps = 10;
+ int[][] a = randomMatrix(n, n), b = randomMatrix(n, n);
+ // произведения матриц
+ int[][] c1 = multiplyMatrices(n, brd, a, b);
+ int[][] c2 = simpleMultiplication(n, a, b);
+ // проверяем корректность результатов
+ System.out.println("Результаты совпадают: " + Arrays.deepEquals(c1, c2));
+ // замеряем время работы двух методов
+ benchmark("Гибридный алгоритм", steps, () -> {
+ int[][] c = multiplyMatrices(n, brd, a, b);
+ if (!Arrays.deepEquals(c, c1)) System.out.print("ошибка");
+ });
+ benchmark("Вложенные циклы ", steps, () -> {
+ int[][] c = simpleMultiplication(n, a, b);
+ if (!Arrays.deepEquals(c, c2)) System.out.print("ошибка");
+ });
+}
+```
+
+{% capture collapsed_md %}
+```java
+// вспомогательный метод, возвращает матрицу указанного размера
+private static int[][] randomMatrix(int row, int col) {
+ int[][] matrix = new int[row][col];
+ for (int i = 0; i < row; i++)
+ for (int j = 0; j < col; j++)
+ matrix[i][j] = (int) (Math.random() * row * col);
+ return matrix;
+}
+```
+```java
+// вспомогательный метод для замера времени работы переданного кода
+private static void benchmark(String title, int steps, Runnable runnable) {
+ long time, avg = 0;
+ System.out.print(title);
+ for (int i = 0; i < steps; i++) {
+ time = System.currentTimeMillis();
+ runnable.run();
+ time = System.currentTimeMillis() - time;
+ // время выполнения одного шага
+ System.out.print(" | " + time);
+ avg += time;
+ }
+ // среднее время выполнения
+ System.out.println(" || " + (avg / steps));
+}
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Вспомогательные методы" content=collapsed_md -%}
+
+Вывод зависит от среды выполнения, время в миллисекундах:
+
+```
+Результаты совпадают: true
+Гибридный алгоритм | 196 | 177 | 156 | 205 | 154 | 165 | 133 | 118 | 132 | 134 || 157
+Вложенные циклы | 165 | 164 | 168 | 167 | 168 | 168 | 170 | 179 | 173 | 168 || 169
+```
+
+## Сравнение алгоритмов {#comparing-algorithms}
+
+На восьмиядерном компьютере Linux x64 запускаем вышеописанный тест 100 раз вместо 10. Минимальный
+размер блока берём `[brd=200]`. Изменяем только `n` — размеры обеих матриц `A=[n×n]` и `B=[n×n]`.
+Получаем сводную таблицу результатов. Время в миллисекундах.
+
+```
+ n | 900 | 1000 | 1100 | 1200 | 1300 | 1400 | 1500 | 1600 | 1700 |
+-------------------|-----|------|------|------|------|------|------|------|------|
+Гибридный алгоритм | 96 | 125 | 169 | 204 | 260 | 313 | 384 | 482 | 581 |
+Вложенные циклы | 119 | 162 | 235 | 281 | 361 | 497 | 651 | 793 | 971 |
+```
+
+Результаты: выгода алгоритма Штрассена становится заметнее на больших матрицах, когда размер
+самой матрицы в несколько раз превышает размер минимального блока, и зависит от среды выполнения.
+
+Все описанные выше методы, включая свёрнутые блоки, можно поместить в одном классе.
+
+{% capture collapsed_md %}
+```java
+import java.util.Arrays;
+import java.util.stream.IntStream;
+```
+{% endcapture %}
+{%- include collapsed_block.html summary="Необходимые импорты" content=collapsed_md -%}
diff --git a/jekyll_site/ru/index.md b/jekyll_site/ru/index.md
new file mode 100644
index 0000000..f38e77e
--- /dev/null
+++ b/jekyll_site/ru/index.md
@@ -0,0 +1,73 @@
+---
+title: Код с комментариями
+description: Заметки на тему программирования с примерами кода и комментариями. Решения задач и описания решений.
+sections: [Решения задач и описания решений]
+tags: [java,алгоритмы,реализация,массивы,многомерные массивы,матрицы,циклы,потоки]
+canonical_url: /
+url_translated: /en/
+title_translated: Code with comments
+---
+
+{%- assign articles = "" | split: "" %}
+{%- assign articles = articles | push: "Алгоритм Винограда — Штрассена" %}
+{%- capture article_brief %}
+Рассмотрим модификацию алгоритма Штрассена для умножения квадратных матриц с *меньшим* количеством
+сложений между блоками, чем в обычном алгоритме — 15 вместо 18 и таким же количеством умножений как
+в обычном алгоритме — 7. Будем использовать потоки Java Stream.
+
+Рекурсивное дробление матриц на блоки при перемножении имеет смысл до определенного предела, а дальше
+теряет смысл, так как алгоритм Штрассена не использует кеш среды выполнения. Поэтому для малых блоков
+будем использовать параллельный вариант вложенных циклов, а для больших блоков параллельно будем
+выполнять рекурсивное дробление.
+
+Границу между двумя алгоритмами определяем экспериментально — подстраиваем под кеш среды выполнения.
+Выгода алгоритма Штрассена становится заметнее на больших матрицах — отличие от алгоритма на вложенных
+циклах становится больше и зависит от среды выполнения. Сравним время работы двух алгоритмов.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- assign articles = articles | push: "Умножение матриц в параллельных потоках" %}
+{%- capture article_brief %}
+Рассмотрим алгоритм перемножения прямоугольных матриц с использованием потоков Java Stream. Возьмём
+*оптимизированный вариант* алгоритма на трёх вложенных циклах и заменим внешний цикл на поток. Сравним
+время работы двух алгоритмов.
+
+Строки результирующей матрицы обрабатываем в параллельном режиме, а каждую строку заполняем слоями.
+За счёт параллельного использования кеша среды выполнения на многоядерных машинах время вычислений
+можно сократить более чем в два раза. Для проверки возьмём две прямоугольные матрицы: {`L×M`} и {`M×N`}.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- assign articles = articles | push: "Поворот матрицы на 180 градусов" %}
+{%- capture article_brief %}
+Рассмотрим алгоритм разворота матрицы на 180 градусов. В отличие от алгоритма *транспонирования*,
+здесь в результирующей матрице строки и колонки не меняются местами, но отображаются зеркально.
+
+Напишем метод на Java для поворота матрицы {`m×n`} на 180 градусов. Для примера возьмём
+прямоугольную матрицу {`4×3`}.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- assign articles = articles | push: "Поворот матрицы на 90 градусов" %}
+{%- capture article_brief %}
+Рассмотрим алгоритм поворота матрицы на 90 градусов по часовой стрелке и против часовой стрелки.
+Этот алгоритм похож на *транспонирование матрицы* с тем отличием, что здесь для каждой точки
+одна из координат отображается зеркально.
+
+Напишем метод на Java для поворота матрицы {`m×n`} на 90 градусов. Дополнительный параметр задаёт
+направление поворота: по часовой стрелке или против часовой стрелки. Для примера возьмём
+прямоугольную матрицу {`4×3`}.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- assign articles = articles | push: "Оптимизация умножения матриц" %}
+{%- capture article_brief %}
+Рассмотрим алгоритм перемножения матриц с использованием трёх вложенных циклов. Сложность такого
+алгоритма по определению должна составлять `O(n³)`, но есть особенности, связанные со средой
+выполнения — скорость работы алгоритма зависит от последовательности, в которой выполняются циклы.
+
+Сравним различные варианты перестановок вложенных циклов и время выполнения алгоритмов.
+Возьмём две матрицы: {`L×M`} и {`M×N`} → три цикла → шесть перестановок:
+`LMN`, `LNM`, `MLN`, `MNL`, `NLM`, `NML`.
+
+Быстрее других отрабатывают те алгоритмы, которые пишут данные в результирующую матрицу *построчно слоями*:
+`LMN` и `MLN`, — разница в процентах к другим алгоритмам значительная и зависит от среды выполнения.
+{%- endcapture %}
+{%- assign articles = articles | push: article_brief %}
+{%- include main_page.html articles = articles -%}
diff --git a/package.sh b/package.sh
new file mode 100755
index 0000000..7fc78a1
--- /dev/null
+++ b/package.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+echo "Подготовка архива для последующего развёртывания."
+cd _site || exit
+rm -rf ../pomodoro3.zip
+7z a ../pomodoro3.zip ./*
diff --git a/serve.sh b/serve.sh
new file mode 100755
index 0000000..55b689c
--- /dev/null
+++ b/serve.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+echo "Локальное развёртывание для проверки корректности сборки."
+jekyll serve --skip-initial-build --disable-disk-cache --host localhost
+echo "Адрес сервера: http://localhost:4000"