Итак,
для примера многопоточного приложения воспользуемся применением фильтра
размытие. Хорошая статья про матричные фильтры есть на хабре. Здесь на
алгоритме фильтра я останавливаться не буду, кто захочет, почитает по ссылке
или разберется сам по коду. Мы же сегодня посмотрим, как количество потоков
влияет на производительность приложения.
Итак, для размытия будем применять вот такую матрицу с нормальным распределением Гаусса:
Ну и основная программа, применяющая последний метод для всех столбцов:
Собственно в данном примере мест для распараллеливания достаточно много. Мы, например, можем параллельно вычислять значения цветов в методе MulMatrix (3-мя потоками), мы можем реализовать копирование матриц (да и само умножение) хоть 25 потоками, но распараллеливать мы будем на уровне столбцов изображения. То есть в отдельных потоках будем запускать метод ColumnFilter. В связи с тем, что одиночный метод выполняется очень быстро, и в этом случае потери на переключение будут больше чем сама работа, я буду в потоке запускать расчет для полосы в 30 пикселей.Для многопоточного запуска, мы введем дополнительную константу, которая будет задавать количество одновременно запущенных потоков. Каждый вызов набора из 30 методов ColumnFilter будет запускаться в новом потоке, но мы будем следить за тем, чтобы количество одновременно запущенных потоков не превышало нашу константу. Также, в связи с тем, что Bitmap является потоко-безопасным (блокирует одновременное обращение из нескольких потоков), то мы наши картинки заменим массивами в которые скопируем цвета пикселей. Здесь же, необходимо понимать, что из первого массива мы будем только читать, а во второй (результирующий массив) хотя потоки и будут писать, они будут писать в разные его ячейки, т.е. гонок не будет. Таким образом, метод Main примет вид:
Ну, и собственно зависимости (по оси абсцисс количество потоков, по оси ординат время работы в секундах): Ладно, в следующий раз обсудим, еще один пример, но посмотрим еще один пример, но уже такой, где потребуется синхронизация потоков между собой.
Итак, для размытия будем применять вот такую матрицу с нормальным распределением Гаусса:
static double[,] filterMatrix = {
{ 0.000789,
0.006581, 0.013347, 0.006581, 0.000789},
{ 0.006581,
0.054901, 0.111345, 0.054901, 0.006581},
{ 0.013347,
0.111345, 0.225821, 0.111345, 0.013347},
{ 0.006581,
0.054901, 0.111345, 0.054901, 0.006581},
{ 0.000789,
0.006581, 0.013347, 0.006581, 0.000789}
};
Собственно
алгоритм состоит из нескольких методов. Метод умножения матриц для получения
цвета в заданной точке:
private static Color
MulMatrix(Color[,] imageMatrix)
{
double red = 0;
for (int col = 0; col
< 5; col++)
{
for (int
row = 0; row < 5; row++)
{
red += imageMatrix[col, row].R * filterMatrix[row, col];
}
}
double green = 0;
for (int col = 0; col
< 5; col++)
{
for (int
row = 0; row < 5; row++)
{
green += imageMatrix[col, row].G * filterMatrix[row, col];
}
}
double blue = 0;
for (int col = 0; col
< 5; col++)
{
for (int
row = 0; row < 5; row++)
{
blue += imageMatrix[col, row].B * filterMatrix[row, col];
}
}
Color distColor = Color.FromArgb((int)red, (int)green,
(int)blue);
return distColor;
}Метод
извлечения из исходной картинки матрицы цветов 5х5 с центром в заданных
координатах:
private static Color[,]
GetMatrixByCenter(Bitmap source, int i, int j)
{
Color[,] imageMatrix = new
Color[5, 5];
for (int x = -2; x
< 3; x++)
{
for (int
y = -2; y < 3; y++)
{
imageMatrix[x + 2, y + 2] = source.GetPixel(i + x, j + y);
}
}
return
imageMatrix;
}
Метод
пробегающий по столбцу картинки и применяющий ко всем точкам размытие:
private static void
ColumnFilter(Bitmap source, Bitmap dist, int
i)
{
for (int j = 2; j
< source.Height - 2; j++)
{
Color[,] imageMatrix =
GetMatrixByCenter(source, i, j);
Color distColor =
MulMatrix(imageMatrix);
dist.SetPixel(i, j, distColor);
}
}Ну и основная программа, применяющая последний метод для всех столбцов:
static void Main(string[]
args)
{
Bitmap source = new
Bitmap("source.jpg");
Bitmap dist = new Bitmap(source.Width, source.Height);
DateTime begin = DateTime.Now;
for (int i = 2; i
< source.Width - 2; i++)
{
ColumnFilter(source, dist, i);
}
DateTime end = DateTime.Now;
dist.Save("dist.jpg");
Console.WriteLine(end.Subtract(begin).TotalSeconds);
Console.ReadKey();
}На
моем тестовом примере, программа работала 17,3 секунды, ну и выдала вот такой
результат (надо смотреть крупно, так как картинка достаточно большая, а размер матрицы
достаточно маленький):Собственно в данном примере мест для распараллеливания достаточно много. Мы, например, можем параллельно вычислять значения цветов в методе MulMatrix (3-мя потоками), мы можем реализовать копирование матриц (да и само умножение) хоть 25 потоками, но распараллеливать мы будем на уровне столбцов изображения. То есть в отдельных потоках будем запускать метод ColumnFilter. В связи с тем, что одиночный метод выполняется очень быстро, и в этом случае потери на переключение будут больше чем сама работа, я буду в потоке запускать расчет для полосы в 30 пикселей.Для многопоточного запуска, мы введем дополнительную константу, которая будет задавать количество одновременно запущенных потоков. Каждый вызов набора из 30 методов ColumnFilter будет запускаться в новом потоке, но мы будем следить за тем, чтобы количество одновременно запущенных потоков не превышало нашу константу. Также, в связи с тем, что Bitmap является потоко-безопасным (блокирует одновременное обращение из нескольких потоков), то мы наши картинки заменим массивами в которые скопируем цвета пикселей. Здесь же, необходимо понимать, что из первого массива мы будем только читать, а во второй (результирующий массив) хотя потоки и будут писать, они будут писать в разные его ячейки, т.е. гонок не будет. Таким образом, метод Main примет вид:
static void Main(string[]
args)
{
Bitmap source = new
Bitmap("source.jpg");
Color[,] sourceColors = new
Color[source.Width, source.Height];
for (int col = 0; col
< source.Width; col++)
{
for (int
row = 0; row < source.Height; row++)
{
sourceColors[col, row] = source.GetPixel(col, row);
}
}
Color[,] distColors = new
Color[source.Width, source.Height];
DateTime begin = DateTime.Now;
int maxThread = 3; // Максимальное
количество
потоков
Console.WriteLine("Количество потоков: {0}", maxThread);
// Массив запущенных
потоков
Thread[] threads = new
Thread[maxThread];
int i = 2;
while (i < sourceColors.GetLength(0) - 32)
{
// Пробегаем по
массиву потоков и заменяем закончившие новыми
for
(int threadIndex = 0; threadIndex <
maxThread && i < sourceColors.GetLength(0) - 32; threadIndex++)
{
if (threads[threadIndex] == null || threads[threadIndex].ThreadState == ThreadState.Stopped)
{
int
index = i;
threads[threadIndex] = new Thread(new ThreadStart(
() =>
{
// Это код запускаемого
потока
for (int col = index; col <
index + 30; col++)
{
ColumnFilter(sourceColors, distColors, col);
}
}
));
threads[threadIndex].Start();
i = i + 30;
}
}
}
DateTime end = DateTime.Now;
Bitmap dist = new Bitmap(source.Width, source.Height);
for (int col = 0; col
< source.Width; col++)
{
for (int
row = 0; row < source.Height; row++)
{
dist.SetPixel(col, row, distColors[col, row]);
}
}
dist.Save("dist.jpg");
Console.WriteLine(end.Subtract(begin).TotalSeconds);
Console.ReadKey();
}Я
запускал приложение несколько раз, для разного количества потоков. Перед тем,
как перейти к графику зависимости скорости работы от количества потоков,
небольшой комментарий. Приложение работало для файла размером 4,5 МБ, разрешение
4288х2848, т.е. у нас матрицы каждого цвета умножались 12.212.224 раз. И все
равно, время копирования из картинки в массив и из массива в результирующую
картинку было сопоставимо со временем работы. Обратите внимание, что после
запуска приложения нагружено одно ядро (идет копирование картинки), а потом все
(применение фильтра):Ну, и собственно зависимости (по оси абсцисс количество потоков, по оси ординат время работы в секундах): Ладно, в следующий раз обсудим, еще один пример, но посмотрим еще один пример, но уже такой, где потребуется синхронизация потоков между собой.
Комментариев нет:
Отправить комментарий