Вы находитесь на странице: 1из 8

МИНИСТЕРСТВО НАУКИ И ВЫСШЕГО ОБРАЗОВАНИЯ

РОССИЙСКОЙ ФЕДЕРАЦИИ
федеральное государственное автономное образовательное учреждение
высшего образования
«НАЦИОНАЛЬНЫЙ ИССЛЕДОВАТЕЛЬСКИЙ
ТОМСКИЙ ПОЛИТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ»

Инженерная школа информационных технологий и робототехники


Направление: 09.03.01
Информатика и вычислительная техника
Отделение информационных технологий

Отчет по лабораторной работе №3 по дисциплине


«ОРГАНИЗАЦИЯ ЭВМ»

Выполнил:

Студент группы _____ С.Р. Лобенко

Проверил:

Ассистент _____ С.А. Андреев


ОИТ

Томск 2021
Цель работы
Изучить понятие SIMD. Рассмотреть принцип работы SIMD-инструкций
процессора на примере программы, выполняющей обработку однотипных
данных.

Задание
Разработать программу на языке C++ для работы с SIMD-инструкциями
процессора. Выполнить следующие пункты:
1. Сгенерировать 2 матрицы (A и B), размерностью 4096х4096, состоящие
из произвольных чисел заданного (согласно варианту) типа.
2. Вычислить произведение матриц с использованием SIMD-инструкций
согласно варианту задания (таблица 1) и с использованием скалярных
вычислений.
3. Сравнить быстродействие векторной и скалярной реализаций. Сделать
выводы.
4. Убедиться, что векторное умножение работает правильно путем
сравнения с результатами скалярного умножения.
5. Рассчитать размер памяти, требуемый для хранения матриц. Сравнить
рассчитанный размер с памятью, выделенной под программу в
диспетчере задач. Сделать выводы.
Таблица 1 – Варианты заданий

2
Листинг программы
Вариант 7 (AVX, с плавающей точкой одинарной точности)
#include <iostream>
#include <immintrin.h>
#include <chrono>
using namespace std;

const int M = 1024, N = 1024;

float scalar_mul(float** a, float** b, float** c);


float vector_mul(float** a, float** b, float** c);
float print_m(float** matrix);
float set_zero(float** matrix);

int main()
{
setlocale(LC_ALL, "Russian");

float** a = new float* [M];


float** b = new float* [M];
float** c = new float* [M];

for (int i = 0; i < M; i++) {


a[i] = new float[N];
b[i] = new float[N];
c[i] = new float[N];
}

for (int i = 0; i < M; i++) {


for (int j = 0; j < N; j++) {
a[i][j] = j;
b[i][j] = j;
c[i][j] = 0;
}
}

printf("---Скалярное умножение матриц %dx%d---\n", M, N);


auto start1 = chrono::high_resolution_clock::now();
scalar_mul(a, b, c);
auto end1 = chrono::high_resolution_clock::now();
//print_m(c);
chrono::duration<float> duration1 = end1 - start1;
printf("Время выполнения: %f секунд\n\n", duration1.count());

set_zero(c);

printf("---Векторное умножение матриц %dx%d---\n", M, N);


auto start2 = chrono::high_resolution_clock::now();
vector_mul(a, b, c);
auto end2 = chrono::high_resolution_clock::now();
//print_m(c);
chrono::duration<float> duration2 = end2 - start2;
printf("Время выполнения: %f секунд\n\n", duration2.count());

printf("Done!\n\n");
}

float scalar_mul(float** a, float** b, float** c) {


/* СКАЛЯРНОЕ УМНОЖЕНИЕ */

3
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < M; k++) {
c[i][j] = c[i][j] + a[i][k] * b[k][j];
}
}
}
return **c;
}

float vector_mul(float** a, float** b, float** c) {


/* ВЕКТОРНОЕ УМНОЖЕНИЕ */
// транспонирование матрицы B
float** bt = new float* [M];

for (int i = 0; i < M; i++) {


bt[i] = new float[N];
}

for (int i = 0; i < M; i++) {


for (int j = 0; j < N; j++) {
bt[i][j] = b[j][i];
}
}
// умножение матриц
__m256 c_line = _mm256_setzero_ps();
int step = sizeof(c_line) / sizeof(a[0][0]);

for (int i = 0; i < M; i++) {


for (int j = 0; j < N; j++) {
__m256 c_line = _mm256_setzero_ps();
for (int k = 0; k < N; k += step) {

__m256 a_line = _mm256_load_ps(&a[i][k]);

__m256 b_line = _mm256_load_ps(&bt[j][k]);


__m256 tmp_line = _mm256_mul_ps(a_line, b_line);
c_line = _mm256_add_ps(tmp_line, c_line);
}

__m256 t1 = _mm256_hadd_ps(c_line, c_line);


__m256 t2 = _mm256_hadd_ps(t1, t1);
__m128 t3 = _mm256_extractf128_ps(t2, 1);
__m128 t4 = _mm_add_ss(_mm256_castps256_ps128(t2), t3);

_mm_store_ps(&c[i][j], t4);
}
}
return **c;
}

float print_m(float** matrix) {


/* ВЫВОД МАТРИЦЫ НА ЭКРАН */
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
cout << endl;
return 0;
}

4
float set_zero(float** matrix) {
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
matrix[i][j] = 0;
}
}
return **matrix;
}

5
Результаты работы программы

Результат работы программы представлен на рисунке 1. Как мы видим,


умножение матриц с использованием SIMD-инструкций происходит быстрее,
чем скалярным способом почти в 6,5 раз.

Рисунок 1 – Результат работы программы

Доработаем программу, добавив вывод на экран матриц, рассчитанных


разными методами (рисунок 2). Произведем умножение двух матриц размером
16х16. Видим, что матрицы, рассчитанные векторным и скалярным способом
совпадают.

6
Рисунок 2 – Проверка правильности результата умножения матриц

Рассчитаем размер памяти, требуемый для хранения матриц. У нас три


матрицы размером 4096х4096, в каждой ячейке которых хранятся данные типа
float. Тип данных float занимает 4 байта памяти, таким образом, одна матрица
занимает 64 МБ памяти, а три таблицы, соответственно – 192 МБ.
Теперь проверим в диспетчере задач, сколько памяти было выделено под
программу (рисунок 3). Как мы видим, матрицы занимают практически весь
объем памяти, выделенный под программу.

7
Рисунок 3 – Диспетчер задач

Вывод

В ходе лабораторной работы был рассмотрен принцип работы SIMD-


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