Научная статья на тему 'МЕТОД ОПТИМИЗАЦИИ КОДА ДЛЯ ПРОЦЕССОРА QUALCOMM HEXAGON, ПОДДЕРЖИВАЮЩИЙ ПАРАЛЛЕЛИЗМ НА УРОВНЕ КОМАНД И ПОСТРОЕННЫЙ ПО АРХИТЕКТУРЕ VLIW (VERY LONG INSTRUCTION WORD)'

МЕТОД ОПТИМИЗАЦИИ КОДА ДЛЯ ПРОЦЕССОРА QUALCOMM HEXAGON, ПОДДЕРЖИВАЮЩИЙ ПАРАЛЛЕЛИЗМ НА УРОВНЕ КОМАНД И ПОСТРОЕННЫЙ ПО АРХИТЕКТУРЕ VLIW (VERY LONG INSTRUCTION WORD) Текст научной статьи по специальности «Компьютерные и информационные науки»

CC BY
52
9
i Надоели баннеры? Вы всегда можете отключить рекламу.
Ключевые слова
ILP-ПРОЦЕССОРЫ / ПРОЦЕССОРЫ С ОЧЕНЬ ДЛИННЫМ КОМАНДНЫМ СЛОВОМ (VERY LONG INSTRUCTION WORD / VLIW) / ПРОЦЕССОР QUALCOMM HEXAGON

Аннотация научной статьи по компьютерным и информационным наукам, автор научной работы — Горин Дмитрий Игоревич, Романова Татьяна Николаевна

Предложен метод оптимизации заполнения машинного слова независимыми инструкциями, который позволяет увеличить производительность программ путем укладки максимального количества независимых команд в пакет. В работе также подтверждена гипотеза о том, что с переходом на случайное выделение регистров компилятором, повысится плотность пакетов, из чего будет следовать уменьшение времени работы программы.

i Надоели баннеры? Вы всегда можете отключить рекламу.
iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.
i Надоели баннеры? Вы всегда можете отключить рекламу.

CODE OPTIMIZATION METHOD FOR QUALCOMM HEXAGON PROCESSOR, SUPPORTING INSTRUCTION LEVEL PARALLELISM AND BUILT WITH VLIW (VERY LONG INSTRUCTION WORD) ARCHITECTURE

A method for optimizing the filling of a machine word with independent instructions is proposed, which allows to increase the performance of programs by stacking the maximum number of independent commands in a package. The paper also confirms the hypothesis that with the transition to random register allocation by the compiler, the packet density will increase, which will result in a decrease in the program's running time.

Текст научной работы на тему «МЕТОД ОПТИМИЗАЦИИ КОДА ДЛЯ ПРОЦЕССОРА QUALCOMM HEXAGON, ПОДДЕРЖИВАЮЩИЙ ПАРАЛЛЕЛИЗМ НА УРОВНЕ КОМАНД И ПОСТРОЕННЫЙ ПО АРХИТЕКТУРЕ VLIW (VERY LONG INSTRUCTION WORD)»

УДК 004.4'416 ГРНТИ 20.15.05

Д.И. Горин, Т.Н. Романова

МГТУ им. Н.Э. Баумана

DOI: 10.47501/ITNOU.2021.1.105-115

МЕТОД ОПТИМИЗАЦИИ КОДА ДЛЯ ПРОЦЕССОРА QUALCOMM HEXAGON, ПОДДЕРЖИВАЮЩИЙ ПАРАЛЛЕЛИЗМ НА УРОВНЕ КОМАНД И ПОСТРОЕННЫЙ ПО АРХИТЕКТУРЕ VLIW (Very Long Instruction Word)

Предложен метод оптимизации заполнения машинного слова независимыми инструкциями, который позволяет увеличить производительность программ путем укладки максимального количества независимых команд в пакет. В работе также подтверждена гипотеза о том, что с переходом на случайное выделение регистров компилятором, повысится плотность пакетов, из чего будет следовать уменьшение времени работы программы.

Ключевые слова: ILP-процессоры, процессоры с очень длинным командным словом (Very Long Instruction Word, VLIW), процессор Qualcomm Hexagon.

CODE OPTIMIZATION METHOD FOR QUALCOMM HEXAGON PROCESSOR, SUPPORTING INSTRUCTION LEVEL PARALLELISM AND BUILT WITH VLIW (Very Long Instruction Word) ARCHITECTURE

A method for optimizing the filling of a machine word with independent instructions is proposed, which allows to increase the performance of programs by stacking the maximum number of independent commands in a package. The paper also confirms the hypothesis that with the transition to random register allocation by the compiler, the packet density will increase, which will result in a decrease in the program's running time.

Keywords: ILP-processors, VLIW processors (Very Long Instruction Word), Qualcomm Hexagon processor.

Процессоры, которые могут одновременно выполнять независимо друг от друга сразу несколько команд, называют ILP-процессорами, поскольку они поддерживают параллелизм на уровне команд (Instruction Level Parallelism, ILP). Процессоры с очень длинным командным словом (Very Long Instruction Word, VLIW), а также многие модели цифровых процессоров обработки сигналов (ЦПОС) принадлежат классу ILP-процессоров. Они обладают большим потенциалом по производительности и активно применяются в настоящее время. ILP-процессоры позволяют осуществлять программный параллелизм автоматически на уровне аппаратуры или компилятора. Вследствие этого не требуется переписывать коды пользовательских приложений, что сокращает время разработки и количество возможных ошибок.

В настоящее время наблюдается тенденция увеличения производительности не с помощью уменьшения соответствующих элементов чипов, а с помощью других техник. Одной из таких техник является использование очень длинного командного слова. Активно разрабатываются специальные методы оптимизации программ для ILP-процессоров с целью увеличения программного параллелизма на уровне команд. Применение одних лишь традиционных методов оптимизации кода оказывается совершенно недостаточным [1]. Для оптимизации кода на VLIW-платформах применяются следующие подходы, позволяющие сократить время исполнения программы.

D.I. Gorin, T.N. Romanova

Bauman Moscow State Technical University

Введение

Преобразования циклов

К преобразованиям циклов относятся: развертка циклов, слияние циклов, подгонка циклов. Все они имеют смысл независимо от наличия параллелизма в целевом процессоре, поскольку позволяют уменьшить общее число проверок завершения цикла и операций перехода [2].

Развертка цикла. Вместо включения тела цикла в целевой код как итерационного блока, цикл «разворачивается», и в код подставляется тело цикла, продублированное n раз, где n - количество итераций цикла. Минус данной техники заключается в том, что размер целевой программы увеличивается многократно.

Слияние циклов. Если два цикла идут один последовательно один за другим, то их можно слить, если количество итераций в них совпадает, и между ними нет зависимостей по данным.

Подгонка циклов. В данной технике количество итераций цикла изменяется, в основном этот прием используется для того, чтобы можно было произвести слияние циклов.

Встраивание функций

Встраивание функций широко применяется как метод оптимизации и в традиционных технологиях компиляции, поскольку при этом экономится время, затрачиваемое на передачу параметров, выполнение пролога и эпилога [3]. Проблемой этого вида оптимизации, как и в случае с разверткой цикла, является многократное увеличение размера кода.

Устранение зависимостей по данным

Для корректного исполнения пакетов в процессоре, в общем случае необходима независимость по данным всех инструкций, входящих в этот пакет [4]. Например, в одном пакете не могут содержаться две команды, в одной из которой производится запись в n-й регистр, а во второй происходит чтение значения из этого же регистра. В подавляющем большинстве случаев данная оптимизация работает лишь на чтение и запись в регистры, без учета чтения и записи значений по адресу, хранящемуся в регистре, поскольку компиляторы не содержат информации о том, какое значение хранится в регистрах в тот или иной момент.

Процессор Qualcomm Hexagon

Qualcomm Hexagon является процессором, построенным по архитектуре VLIW (Very Long Instruction Word), то есть имеет широкое машинное слово, что позволяет выполнять несколько инструкций за один такт. При этом команды укладываются в пакет, который вмещает в себя 4 инструкции. Программист сам должен указать (на уровне ассемблерного кода) какие инструкции должны укладываться в пакет. Например, конструкция {r1 = r0; r2 = add(r3,r4)} указывает процессору, что в пакет должны быть уложены две команды: первая - по копированию значения из r0 в r1, а вторая - по присвоению регистру r2 суммы значений из регистров r3 и r4.

Выполнение пакета происходит в следующем порядке:

1. Все инструкции из пакета параллельно считывают значение регистров-источников (справа от знака '=').

2. Все инструкции из пакета параллельно выполняются.

3. Все инструкции в пакете параллельно заполняют регистр-приемник (слева от знака '=').

Например, в результате выполнения пакета {r2 = r3; r3 = r2} старое значение регистра r2 запишется в r3, а старое значение r3 запишется в регистр r2, то есть произойдет замена значений между r2 и r3.

Существует ограничение на укладку инструкций в разные слоты пакета. Например, операции над вещественными числами нельзя уложить в первые два слота, а операции чтения-записи в память нельзя укладывать в последние два слота. Все ограничения целевого процессора Qualcomm Hexagon [5] приведены на Рис.1.

Slot о

LD Instructions ST Instructions ALU32 Instructions MEMOP Instructions NV Instructions SYSTEM Instructions

Slot 1

LD Instructions ST Instructions ALU32 Instructions

Slot 2

XTYPE Instructions ALU32 Instructions J Instructions JR Instructions

Slot 3

XTYPE Instructions ALU32 Instructions J Instructions CR Instructions

XTYPE Instructions (32/64 bit)

Arithmetic, Logical, Bit Manipulation

Multiply (Integer, Fractional, Complex)

Floating-point Operations

Permute / Vector Permute Operations

Predicate Operations

Shift / Shift with Add/Sub/Logical

Vector Byte ALU

Vector Halfword (ALU, Shift, Multiply) Vector Word (ALU, Shift)

ALU32 Instructions

Arithmetic / Logical (32 bit) Vector H alfword

CR Instructions

Control-Register Transfers Hardware Loop Setup Predicate Logicals & Reductions

N\f Instructions

New-value Jumps New-value Stores

J Instructions

Jump/Call PC-relative

JR Instructions

Jump/Call Register LD Instructions

Loads (a/1 6/32/64 bit) D eallocfram e

ST Instructions

Stores (8/16/32/64 bit) Allocfra m e

MEMOP Instructions

Operation on memory {8/16/32 bit)

SYSTEM Instructions

Prefetch

Cache Maintenance Bus Operations

Рис. 3. Ограничения на укладку инструкций в слоты пакета

В некоторых случаях машинное слово может быть заполнено не полностью. В этом случае преимущества этих подходов могут и не проявиться. Метод оптимизации, предлагаемый в данной работе, позволяет устранить зависимости по данным между командами, укладываемыми в пакеты. Для этого используются некоторые правила, характерные только для процессоров семейства Qualcomm Hexagon, который выбран в качестве целевого. Это позволит генерировать наиболее производительный код для процессоров данного типа.

Математическая постановка задачи

Поскольку все инструкции в пакете выполняются параллельно, то есть прямая зависимость скорости выполнения программы от степени наполненности пакетов - чем больше он содержит инструкций, тем быстрее выполняется код. Приведенная ниже оптимизационная модель основана на этой гипотезе.

Инструкции ассемблерного кода без пакетов можно записать как кортеж:

.

Пакет также можно представить как кортеж вида:

Ограничение длины пакета в четыре инструкции обусловлено тем, что пакеты в Qualcomm Hexagon могут содержать до четырех ассемблерных команд, то есть:

.

Ассемблерный Код после упаковки команд в пакеты также можно записать в виде кортежа ИЗ m пакетов :

При этом будем полагать, что операция, не включенная в пакет, равносильна пакету, содержащему только эту операцию. Это обусловлено тем, что процессор, при подаче на него пакета с единственной инструкцией, исполнит только ее, точно так же, как если бы эту же инструкцию подали без упаковки в пакет.

Обозначим процесс упаковки исходного ассемблерного кода, не содержащего пакетов инструкций, как функцию: О — fXc). Критерием оптимальности в данном случае будет условие максимального заполнения пакетов:

? — ■ "..;'. ? -Е ^ . = 1 ■■■. .

Математическая модель оптимизации может быть описана следующей системой:

i IР I — тах, где р Е 0,i = 1, m

Алгоритм, реализующий описанный метод оптимизации

Алгоритм обрабатывает уже скомпилированный неупакованный ассемблерный код, который в программе-компиляторе содержится виде промежуточного представления. Укладывание в пакеты выполняется для каждой функции исходной программы (то есть от метки старта оптимизируемой функции до метки конца функции в ассемблерном коде). Алгоритм по очереди просматривает ассемблерные инструкции из списка, принимая решение о возможности помещения просматриваемой инструкции в пакет. Этот цикл продолжается пока пакет не будет заполнен, либо когда алгоритм дойдет до конца списка. Алгоритм заканчивает свою работу, когда список операций (инструкций) станет пустым. Решение о возможности помещения инструкции в пакет принимается путем анализа левой и правой части операции, а также анализом типа операции. Более подробно алгоритм можно записать следующим образом:

Шаг 1. Сначала рассматривается левый операнд операции:

• если операция выполняет запись в память (например, memw(r0) = r2), то по адресу, который хранится в регистре r0, не должно было быть записей в ранее просмотренном коде.

• если операция выполняет запись в регистр (например, r2 = r0), то проверяются следующие условия:

> в регистре r2 не должно было быть записей в ранее просмотренном коде; ^ регистр r2 не использовался в ранее просмотренном коде как регистр для

хранения адреса, по которому проводилась запись.

Шаг 2. Если проверка левого операнда прошла успешно, то начинается анализ правой части операции:

• если операция выполняет чтение из памяти (например, r0 = memw(r2)), то по адресу, который хранится в регистре r2, не должно было быть записей в ранее просмотренном коде;

• во все регистры, находящиеся в правом операнде операции (например, в случае операции r0 = r2 в регистр r2, или в случае операции r0 = add(r2, r3) в регистры r2 и r3), не должно было быть записей в ранее просмотренном коде.

Шаг 3. Если проверка обоих операндов прошла успешно, то анализируется тип операции и свободные слоты в пакете. Если остались свободные слоты для данного типа операции, то операция помещается в пакет.

Шаг 4. Если операция не может быть помещена в пакет (из-за невыполнения критериев, указанных в шагах 1,2,3), то алгоритм переходит к следующей команде в списке.

Шаг 5. Если пакет в данный момент пустой, то операция помещается в результирующий код сразу, без упаковки в пакет.

Шаг 6. Если список операций не пустой, то алгоритм переходит к анализу следующей инструкции из списка, то есть снова выполняется шаг 1. Когда список операций (инструкций) становиться пустым выполнение алгоритма заканчивается.

На основе описанного алгоритма реализован компилятор для процессоров семейства Qualcomm Hexagon, который позволяет генерировать код в 2 режимах:

1. С последовательным выделением регистров операциям (то есть если на данный момент не используются регистры r2,r3,.. ,,r29, а операции необходим регистр, то компилятор выделит регистр r2).

2. Со случайным выделением регистров.

С помощью алгоритма случайного выделения регистров, была проверена гипотеза о том, что если выделять регистры не последовательно, то количество инструкций, которые можно положить в текущий пакет будет больше, чем при последовательном выделении, поскольку операций с независимыми операндами типа регистр будет больше. Это позволит упаковывать пакеты максимально плотно.

Пример генерации кода

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

В данном разделе приведены примеры исходных кодов программ и их сгенерированный ассемблерный код до и после упаковки инструкций по пакетам. Строки неупакованного ассемблерного кода будут пронумерованы для того, чтобы показать какие команды были помещены в различные пакеты.

Неупакованный исходный код Программы № 1 представлен на Листинге 1.

1. int main(void) {

2. int x = 0;

3. int y = 3;

4.

5. return x + y;

6. }

Листинг 1. Неупакованный исходный код Программы №1.

Неупакованный код с последовательным выделением регистров представлен на Листинге 2.

1. .section .data

2.

3. .section .text

4. .global asm_func;

5. .type asm_func @function;

6. asm_func:

7. jump func_main_start; // 1

8. func_main_start: // 2

9. allocframe(#4000); // 3

10. memw(SP + #0) = #0; // 4

11. r0 = #0; // 5

12. r1 = add(SP, #0); // 6

13. memw(r1) = r0; // 7

14. memw(SP + #4) = #0; // 8

15. r0 = #3; // 9

16. r1 = add(SP, #4); // 10

17. memw(r1) = r0; // 11

18. r0 = add(SP, #4); // 12

19. r1 = memw(r0); // 13

20. r0 = add(SP, #0); // 14

21. r2 = memw(r0); // 15

22. r0 = add(r2, r1); // 16

23. r0 = r0; // 17

24. jump func_main_end; // 18

25. func_main_end: // 19

26. dealloc_return;

Листинг 2. Неупакованный код с последовательным выделением регистров.

Упакованный код с последовательным выделением регистров представлен на Листинге 3

1. 2. .section .data

Z. 3. .section .text

4. .global asm_func;

5. .type asm_func @function;

6. asm_func:

7. jump func_main_start; // 1

8. func main start: // 2

9. allocframe(#4000); // 3

10. {

11. memw(SP + #0) = #0; // 4

12. r0 = #0; // 5

13. r1 = add(SP, #0); // 6

14. nop;

15. }

16.

17. {

18. memw(rl) = r0; // 7

19. memw(SP + #4) = #0; // 8

20. r0 = #3; // 9

21. r1 = add(SP, #4); // 10

22. }

23.

24.

25.

26.

27.

28.

29.

30.

31.

32.

33.

34.

35.

36.

37.

38.

39.

40.

41.

42.

43.

44.

45.

46.

47.

48.

49.

50.

51.

52.

53.

54.

55.

56.

57.

58.

59.

60. 61.

memw(r1) = r0; // 11 r0 = add(SP, #4); // 12 nop; nop;

r1 = memw(r0); // 13 r0 = add(SP, #0); // 14 nop; nop;

r2 = memw(r0); // 15

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

nop;

nop;

nop;

r0 = add(r2, r1); // 16

nop;

nop;

nop;

r0 = r0; // 17 nop; nop; nop;

}

jump func_main_end; // 18 func_main_end: // 19 dealloc return;

Листинг 3. Упакованный код с последовательным выделением регистров

Неупакованный код со случайным выделением регистров представлен на Листинге 4.

1.

2.

3.

4.

5.

6.

7.

8.

9.

10. 11. 12. 13.

.section .data

.section .text .global asm_func; .type asm_func @function; asm_func:

jump func_main_start; // 1 func_main_start: // 2 allocframe(#4000); // 3 memw(SP + #0) = #0; // 4 r6 = #0; // 5 r25 = add(SP, #0); // 6 memw(r25) = r6; // 7

{

14. memw(SP + #4) = #0; // 8

15. r7 = #3; // 9

16. r15 = add(SP, #4); // 10

17. memw(r15) = r7; // 11

18. r26 = add(SP, #4); // 12

19. r20 = memw(r26); // 13

20. r9 = add(SP, #0); // 14

21. r19 = memw(r9); // 15

22. r7 = add(r19, r20); // 16

23. r0 = r7; // 17

24. jump func_main_end; // 18

25. func_main_end: // 19

26. dealloc_return;

Листинг 4. Неупакованный код со случайным выделением регистров

Упакованный код со случайным выделением регистров показан на Листинге 5.

1. .section .data

2.

3. .section .text

4. .global asm_func;

5. .type asm_func @function;

6. asm_func:

7. jump func_main_start; // 1

8. func_main_start: // 2

9. allocframe(#4000); // 3

10. {

11. memw(SP + #0) = #0; // 4

12. r6 = #0; // 5

13. r25 = add(SP, #0); // 6

14. r7 = #3; // 9

15. }

16.

17. {

18. memw(r25) = r6; // 7

19. memw(SP + #4) = #0; // 8

20. r15 = add(SP, #4); // 10

21. r26 = add(SP, #4); // 12

22. }

23.

24. {

25. memw(r15) = r7; // 11

26. r9 = add(SP, #0); // 14

27. nop;

28. nop;

29. }

30.

31. {

32. r20 = memw(r26); // 13

33. r19 = memw(r9); // 15

34. nop;

35. nop;

36. }

37.

38. {

39. r7 = add(r19, r20); // 16

40. nop;

41. nop;

42. nop;

43. }

44.

45. {

46. r0 = r7; // 17

47. nop;

48. nop;

49. nop;

50. }

51.

52. jump func_main_end; // 18

53. func_main_end: // 19

54. dealloc_return;

Листинг 5. Упакованный код со случайным выделением регистров Исследование времени выполнения программ

В данном разделе приведены замеры времени выполнения программ до и после упаковки инструкций по пакетам с последовательным и случайным выделением регистров. Все замеры времени проводятся в специализированной среде IDE Hexagon, построенной на базе Eclipse, которая эмулирует работу процессора Qualcomm Hexagon. Время работы программ измеряется в тиках процессора. Код программы, замеряющей время работы, представлен на Листинге 6.

1. #include <stdio.h>

2. #include <time.h>

3. #include <stdlib.h>

iНе можете найти то, что вам нужно? Попробуйте сервис подбора литературы.

4. #include <sys/times.h>

5.

6. extern "C" int asm_func();

7.

8. int main(void) {

9. struct tms begin_times;

10. struct tms end_times;

11.

12. times(&begin_times);

13. asm_func();

14. times(&end_times);

15.

16. printf("Code ran successfully\n\n");

17.

18. printf("begin utime: %lld\n", begin_times.tms_utime);

19. printf("end utime: %lld\n", end_times.tms_utime);

20. long long dif = (long long) end_times.tms_utime - (long long) begin_times.tms_utime;

21. dif = dif > 0 ? dif : -dif;

22. printf("dif utime: %lld\n\n", dif);

23.

124. return 0;

25. }

Листинг 6. Код программы, которая определяет время работы программ

Неупакованный исходный код Программы №1, представленный на Листинге 1, запускается на выполнение до и после упаковки инструкций по пакетам с последовательным и случайным выделением регистров. В Таблице 1 представлены результаты сравнения для Программы №1.

Таблица 1. Результаты сравнения для Программы №1

Последовательное выделение регистров Случайное выделение регистров

Неупакованный код 190 190

Упакованный код 176 174

Исходный код Программы №2 показан на Листинге 8. Для этой программы также выполнены замеры времени выполнения, которые представлены в Таблице 2.

1. int main(void) {

2. int a = 1;

3. int b = -4;

4. int c = 4;

5. int x = 2;

6.

7. Q int zero = a *

8. 9. return 0;

10. }

х + Ь * х + с;

Листинг 8. Исходный код Программы №2 Таблица 2. Результаты сравнения для Программы №2

Последовательное выделение регистров Случайное выделение регистров

Неупакованный код 240 240

Упакованный код 198 190

Заключение

Авторы считают, что в данной работе были достигнуты следующие результаты:

1. Разработана математическая модель для оптимизации ассемблерного кода с использованием некоторых правил, характерных только для процессоров семейства Qualcomm Hexagon.

2. На основе этой оптимизационной модели разработан алгоритм заполнения машинного слова независимыми инструкциями, который позволяет увеличить производительность программ путем укладки максимального количества независимых команд в пакет.

3. В работе подтверждена гипотеза о том, что с переходом на случайное выделение регистров компилятором, повысится плотность пакетов, из чего будет следовать уменьшение времени работы программы.

Литература

1. Шумаков С.М. Обзор методов оптимизации кода для процессоров с поддержкой параллелизма на уровне команд // URL:

http://citforum.ru/programming/digest/20030430/ (дата обращения 01.11.2020).

2. Ким, А. К., Перекатов, В. И., Ермаков, С. Г. Микропроцессоры и вычислительные комплексы семейства «Эльбрус». СПб: Питер, 2013.

3. Ермолицкий А.В., Нейманзаде М.И., Четверина О.А., Маркин А.Л., Волконский В.Ю. Агрессивная инлайн-подстановка функций для VLIW-архитектур // Труды ИСП РАН. 2015. № 6. URL: https://cyberleninka.ru/article/n/agressivnaya-inlayn-podstanovka-funktsiy-dlya-vliw-arhitektur (дата обращения: 01.11.2020).

4. Intel Corporation. Intel® Itanium® Architecture Software Developer's Manual [Книга]. - San Diego, CA : Intel Corporation, 2010.

5. Qualcomm Technologies, Inc. Hexagon V5x Programmer's Reference Manual [Книга]. - San Diego, C. A.: Qualcomm Technologies, 2016.

Сведения об авторах Татьяна Николаевна Романова

Канд. физ.-мат. наук, доцент, факультет Информатика и системы управления МГТУ им. Н.Э. Баумана, Россия, Москва Эл. почта: rtn@bmstu.ru Дмитрий Игоревич Горин студент магистратуры, факультет Информатика и системы управления МГТУ им. Н.Э. Баумана, Россия, Москва

Эл. почта: gorindi@student.bmstu.ru

Information about authors Tatiana Nikolaevna Romanova

Ph.D. Physical and mathematical sciences, Associate Professor

Bauman Moscow State Technical University Russia, Moscow E-mail: rtn@bmstu.ru Dmitry Igorevich Gorin

Master's degree student, Department of Computer Science and Control Systems

Bauman Moscow State Technical University, Russia, Moscow

E-mail: gorindi@student.bmstu.ru

i Надоели баннеры? Вы всегда можете отключить рекламу.