Формат хранения CSC (CCS)

advertisement
Министерство образования и науки Российской Федерации
Государственное образовательное учреждение
высшего профессионального образования
«Нижегородский государственный университет им. Н.И. Лобачевского»
Факультет вычислительной математики и кибернетики
Кафедра математического обеспечения ЭВМ
Отчет по лабораторной работе
Алгоритм умножения разреженных матриц. Формат хранения
столбцовый (CCS).
Выполнили: студенты группы 85М21
Майоров А. Ю., Морозова М.В.
Нижний Новгород
2012
Оглавление
Оглавление .....................................................................................................................................2
Постановка задачи .........................................................................................................................3
Формат хранения CSC (CCS) .......................................................................................................4
Описание алгоритма ......................................................................................................................5
Алгоритм транспонирования ....................................................................................................6
Алгоритм вычисления скалярного произведения разреженных векторов ...........................8
Алгоритм формирования результирующей матрицы ............................................................8
Последовательная реализация алгоритма ...................................................................................9
Описание программы ................................................................................................................9
Результаты экспериментов........................................................................................................9
Листинг .....................................................................................................................................10
Параллельная реализация алгоритма (OpenMP) ......................................................................12
Параллельная реализация алгоритма (TBB) .............................................................................13
Параллельная реализация алгоритма (OpenCL) .......................................................................14
Параллельная реализация алгоритма (Cilk Plus) ......................................................................15
2
Постановка задачи
Пусть A и B разреженные1 квадратные матрицы размера N x N2, формат хранения
CCS (CSC)3. Элементы матриц вещественные числа (далее в программной реализации будем использовать числа с плавающей точкой одинарной точности). Требуется найти матрицу C = A * B, где символ * соответствует матричному умножению. В процессе умножения двух разреженных матриц нередко может получиться плотная матрица. В рамках
данной работы предполагается, что матрица C также является разреженной.
1
Разреженная матрица – матрица с преимущественно с нулевыми элементами
2
В данной лабораторной работе для упрощения рассматривается задача для слу-
чая квадратных матриц.
3
CompressedColumnStorage(CCS) или CompressedSparseColumn (CSC) – формат
хранения матриц, описание данного формата будет дано в разделе “Формат хранения
CCS (CSС)”
3
Формат хранения CSC (CCS)
Формат хранения, широко распространенный под названием CCS (CompressedColumnStorage) или CSC (CompressedSparseColumn), представлен тремя массивами: первый
массив хранит значения элементов матрицы по столбцам (столбцы рассматриваются слева
направо, элементы в столбцах – по порядку сверху вниз), второй – номера строк для каждого элемента, а третий – индекс начала каждого столбца (рис. 1).
Структура хранения:
A
1
4
9
1
5
7
2
2
3
4
7
5
9
6
5
0
5
Value
0
5
3
5
7
0
5
4
0
3
5
8
9
Rows
3
7
1
6
3
4
5
ColumnIndexes
Рис. 1 Формат хранения CCS (CSC)
Количество элементов массива ColumnIndexes равно N + 1 (N x N – размер матрицы). При этом элементы столбца i в массиве Values находятся по индексам от
ColumnIndexes[i] до ColumnIndexes[i + 1] – 1 включительно. Данный формат является эффективным, с точки зрения объема используемой памяти.
Плотное распределение: M = 4N2 байт.
В формате CCS (CSC): M = 4NZ + 4NZ + 4(N+1) байт, где NZ – количество ненулевых элементов в матрице.
Недостатком рассмотренного представления являются перепаковки, к которым приводит выполнение операций вставки/удаления элементов.
Для хранения матрицы в данной лабораторной работе используются следующая
структура данных:
struct Matrix
{
int Size;
int NonZero;
float * Values;
int * Rows;
int * ColumnIndexes;
};
//
//
//
//
//
Количество строк и столбцов матрицы
Количество ненулевых элементов
Массив значений размера NonZero
Массив номеров строк размера NonZero
Массив индексов столбцов размера Size + 1
4
Описание алгоритма
Разреженное представление матриц вносит ряд особенностей в стандартный алгоритм умножения:
1. Необходимо уметь выделять вектора в матрицах A и B (A * B = C): строки
матрицы A и столбцы матрицы B. Выделить столбец матрицы в формате
CCS (CSC) не представляет труда: j-ый столбец может быть легко найден,
так как его элементами являются элементы в массиве Values с
ColumnIndex[j] по ColIndexes[j+1] - 1. Проблема возникает с выделением
строки. Чтобы найти элементы строки i необходимо просмотреть массив
Rows и выделить все элементы, у которых в соответствующей ячейке массива Rows записано число i. Одним из возможных решением описанной
проблемы является транспонирование матрицы. В данной работе реализован именно такой подход.
2. Используемая структура данных, построенная на основе формата CCS
(CSC), предполагает хранение только ненулевых элементов, что несколько
усложняет программирование вычисления скалярного произведения, но
одновременно уменьшает количество арифметических операций. Требуется выполнить сопоставление номеров ненулевых элементов с целью обнаружения пар значений, которые необходимо перемножить и накопить в частичную сумму.
3. Необходимо также уметь записывать посчитанные элементы в результирующую матрицу C. Учитывая, что C хранится в формате CCS (CSC), важно избежать перепаковок. Для этого нужно обеспечить пополнение матрицы C ненулевыми элементами последовательно, по столбцам – сверху
вниз, слева направо.
Рассмотрим подробнее алгоритм реализации каждой из них.
5
Алгоритм транспонирования
Пусть дана разреженная матрица A в формате CCS (CSC). Требуется найти транспонированную матрицу AT. В данной лабораторной работе реализован алгоритм транспонирования, использующий структуры данных матрицы AT для промежуточных вычислений:
1. Обнулить массив ColumnIndexes матрицы AT. Просмотреть массив Rows матрицы A и сосчитать количество элементов в каждой строке матрицы A (столбце
матрицы AT), сохраняя результаты в массиве ColumnIndexes матрицы AT. Пусть
при этом AT.ColumnIndexes[i] хранит количество элементов в строке i – 1 матрицы А (i меняется от 1 до N включительно).
memset(AT.ColumnIndexes, 0, (A.Size + 1) * sizeof(int));
for (int i = 0; i < A.NonZero; i++)
{
AT.ColumnIndexes[in.Rows[i] + 1]++;
}
Структура храненияA:
A
5
5
5
1
8
1
5
6
Value
0
6
8
3
AT.Value
2
1
3
Rows
0
Структура хранения AT:
AT.Rows
2
3
5
5
ColumnIndexes
0
1
1
1
2
AT.ColumnIndexes
Рис. 2
2. Подсчитать индексы начала каждого столбца в матрице AT так, что элемент
AT.ColumnIndexes[i] хранит индекс начала (i–1)-ого столбца.
int sum = 0;
for (int i = 1; i < AT.Size + 1; i++)
{
int index = A.ColumnIndexes[i];
AT.ColumnIndexes[i] = sum;
sum += index;
}
3. Правильно расположить в структуре результирующей матрицы значения из
исходной матрицы. Сами значения и их номера столбцов (теперь строк)
известны, необходимо «лишь» поместить их в корректную позицию. Для этого
испрользуется массив AT.ColumnIndexes. Взяв очередной элемент матрицы A,
6
имеющий значение V, координаты (i, j), мы должны добавить его в i-ый столбец
матрицы AT. Зная, что в настоящий момент i-ый столбец матрицы AT начинается
с элемента AT.ColumnIndexes[i+1], будем добавлять V и j в массивы AT.Values и
AT.Rows соответственно по адресу AT.ColumnIndexes[i+1], после чего увеличим
AT.ColumnIndexes[i+1] на единицу. В конце элемент AT.ColumnIndexes[i+1]
будет хранить индекс начала (i+1)-ого столбца, что и требуется.
for (int i = 0; i < A.Size; i++)
{
for (int j = A.ColumnIndexes[i]; j <A.ColumnIndexes[i + 1]; j++)
{
int index = AT.ColumnIndexes[in.Rows[j] + 1];
AT.Values[index] = in.Values[j];
AT.Rows[index] = i;
AT.ColumnIndexes[in.Rows[j] + 1]++;
}
}
Приведем полную реализацию функции транспонирования Transpose():
double Transpose(Matrix & in, Matrix & out)
{
clock_t start = clock();
Initialize(out, in.Size, in.NonZero);
memset(out.ColumnIndexes, 0, (in.Size + 1) * sizeof(int));
for (int i = 0; i < in.NonZero; i++)
{
out.ColumnIndexes[in.Rows[i] + 1]++;
}
int sum = 0;
for (int i = 1; i < in.Size + 1; i++)
{
int index = out.ColumnIndexes[i];
out.ColumnIndexes[i] = sum;
sum += index;
}
for (int i = 0; i < in.Size; i++)
{
for (int j = in.ColumnIndexes[i]; j < in.ColumnIndexes[i + 1]; j++)
{
int index = out.ColumnIndexes[in.Rows[j] + 1];
out.Values[index] = in.Values[j];
out.Rows[index] = i;
out.ColumnIndexes[in.Rows[j] + 1]++;
}
}
clock_t finish = clock();
return (double)(finish - start) / CLK_TCK;
}
7
Алгоритм вычисления скалярного произведения разреженных векторов
Пусть даны разреженные матрицы A и B размера N x N, представленные в формате
CCS (CSC). Требуется найти скалярное произведение векторов (V1, V2), где V1 и V2
столбцы матриц А и B соответственно.
Для решения описанной задачи в данной лабораторной работе реализован следующий алгоритм:
1. Создать целочисленный массив Y длины N и инициализировать его элементы
значением -1.Обнулить вещественную переменную SUM.
2. Просмотреть в цикле все ненулевые элементы первого вектора V1. Пусть
такой элемент с порядковым номером j расположен в строке с номером i =
A.Rows[j], тогда записать j в i-ю ячейку массива Y.
3. Просмотреть в цикле все ненулевые элементы второго вектора V2. Пусть элемент с порядковым номером t расположен в строке с номером p = B.Rows[t].
Проверить значение Y[k]. Если оно равно -1, в V1 нет соответствующего элемента, т.е. умножение выполнять не нужно. Иначе умножаем B.Values[t] и
A.Values[Y[p]] и накапливаем в SUM.
Алгоритм формирования результирующей матрицы
Для того чтобы обеспечить пополнение матрицы C ненулевыми элементами последовательно, по столбцам – сверху вниз, слева направо и избежать перепаковок в данной
лабораторной работе реализован следующий алгоритм:
1. Создать структуру для хранения результирующей матрицы С: массивы (векторы) Values, Rows, ColumnIndexes. Размер массива ColumnIndexes – N+1, размер
массивов Values и Rows пока неизвестен.
2. В цикле по i от 0 до N – 1 перебирать все столбцы матрицы B. Для каждого i в
цикле по j от 0 до N – 1 перебирать все столбцы матрицы AT. Вычислять скалярное произведение векторов-столбцов AT[j] и B[i]. Пусть оно равно S. Если
S отлично от нуля, добавлять в вектор Values матрицы С элемент S, в вектор
Rows – элемент j. При окончании цикла по j скорректировать значение
ColumnIndexes[i+1], записав туда текущее значение числа ненулевых элементов S.
8
Последовательная реализация алгоритма
Описание программы
В соответствие с описанными выше алгоритмами была реализована последовательная версия алгоритма.
Данная программа позволяет автоматически генерировать разреженные матрицы в
формате CCS с заданным числом ненулевых элементов в каждом столбце, производить их
перемножение, проверять корректность результата и замерять время выполнения алгоритма. Пример работы программы приведен на рисунке ниже.
При запуске программа попросит ввести размер квадратных матриц и количество ненулевых элементов в каждом столбце. После перемножения будет выведено время работы алгоритма транспонирования и умножения. Далее программа предложит проверить корректность расчетов. После этого она предложит распечатать на экран сгенерированные
матрицы и результат их перемножения.
Результаты экспериментов
Проведем ряд экспериментов на реализованной программной системе. Исследуем
зависимость времени выполнения алгоритма от размера входных матриц. Будем увеличивать размер от 5000 до 35000 с шагом 5000. Количество ненулевых элементов в каждом
столбце примем равным 50.
9
Размер матриц
Время умножения, секунды
5000
2,777
10000
12,448
15000
28,298
20000
49,577
25000
76,952
30000
111,340
35000
151,071
Таблица 1. Время работы последовательного алгоритма
На основе полученных результатов построим график, отражающий зависимость времени
выполнения алгоритма от размера входных матриц.
160
140
120
100
80
60
40
20
0
5000
10000
15000
20000
25000
30000
35000
Последовательный алгоритм
График 1. Время работы последовательного алгоритма
Листинг
Приведем реализацию последовательного алгоритма умножения матриц.
double Multiply(Matrix left, Matrix right, Matrix & result)
{
int size = left.Size;
clock_t start = clock();
vector<float> values;
vector<int> rows;
vector<int> columnIndexes;
10
int * temp = newint[size];
int nonZero = 0;
columnIndexes.push_back(0);
for (int i = 0; i < size; i++)
{
memset(temp, -1, size * sizeof(int));
for (int j = right.ColumnIndexes[i]; j < right.ColumnIndexes[i +
1]; j++)
{
int row = right.Rows[j]; temp[row] = j;
}
for (int j = 0; j < size; j++)
{
float sum = 0;
for (int k = left.ColumnIndexes[j]; k < left.ColumnIndexes[j
+ 1]; k++)
{
int row = left.Rows[k];
int index = temp[row];
if (index != -1)
{
sum += left.Values[k] * right.Values[index];
}
}
if (sum != 0)
{
rows.push_back(j);
values.push_back(sum);
nonZero++;
}
}
columnIndexes.push_back(nonZero);
}
delete[] temp;
Initialize(result, size, nonZero);
for(int i = 0; i < nonZero; i++)
{
result.Rows[i] = rows[i];
result.Values[i] = values[i];
}
for(int i = 0; i < size + 1; i++)
{
result.ColumnIndexes[i] = columnIndexes[i];
}
clock_t finish = clock();
return (double)(finish - start) / CLK_TCK;
}
11
Параллельная реализация алгоритма (OpenMP)
12
Параллельная реализация алгоритма (TBB)
13
Параллельная реализация алгоритма (OpenCL)
14
Параллельная реализация алгоритма (Cilk Plus)
15
Download