Библиотека Parallel Extensions, изначально разработанная как исследовательский проект вошла в состав .Net 4.0. Целью её создания было упростить программирование под многоядерные архитектуры.
Данная библиотека содержит множество средств для автоматического распараллеливания кода. Остановимся поподробнее на методе Parallel.For.
Это статический метод с 3-мя агрументами. Библиотека содержит сложные алгоритмы динамического распределения работы и автоматически приспосабливается к конкретной машине. В то же время примитивы библиотеки позволяют только указать на возможный параллелизм, но не гарантируют параллельного исполнения. Например, на однопроцессорной машине параллельный цикл будет выполнен последовательно. Скорость его выполнения будет очень близка к скорости выполнения последовательного кода. Однако на двухъядерном компьютере библиотека задействует два потока для параллельного выполнения цикла. Можно сразу включить инструкции параллельного выполнения в код, тогда при наличии нескольких процессоров приложение автоматически начнет их использовать. При этом код будет быстро работать и на однопроцессорных машинах.
Для примера рассмотрим классическую задачу из теории чисел - т.н. гипотезу Колатца. Она заключается в слелующем:
Рассмотрим следующий алгоритм генерации последовательности чисел. Начнем с целого числа n. Если n четно, то разделим n на 2.Если n нечетно, то умножим на 3 и добавим 1. Будем повторять этот процесс с новыми полученным n, пока n не станет равным 1. Например, для n = 22 будет сгенерирована следующая последовательность чисел:
22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1
Длина этой последовательности равна 16. До сих пор не доказано, что для любого числа такая последовательность закончится единицей.
Попробуем найти число в пределах от 1 до 1000000, которое генерирует последовательность наибольшей длины:
using System;
using System.Collections.Generic;
using System.Text;
namespace _14
{
class Program
{
static long Max;
static long MaxNum;
static void calc(long a)
{
long ans = 1;
long tmp_a = a;
while (a != 1)
{
if (a % 2 == 0)
a /= 2;
else
a = 3 * a + 1;
ans++;
}
if (Max < ans)
{
MaxNum = tmp_a;
Max = ans;
}
}
static void Main(string[] args)
{
Max = -1;
MaxNum = 0;
DateTime startTime = DateTime.Now;
for (int i = 1; i <= 1000000; i++)
calc(i);
DateTime stopTime = DateTime.Now;
TimeSpan elapsed = stopTime - startTime;
Console.WriteLine("elapsed seconds: {0}", elapsed.Seconds);
Console.WriteLine("elapsed milliseconds: {0}", elapsed.Milliseconds);
Console.WriteLine(MaxNum);
}
}
}
Вышеприведённый код использует для вычислений тип long чтобы избежать переполнения(элементы последовательности могут быть гораздо больше 1000000).
Время работы на моей машине с процессором Core 2 Duo 2,6 Ггц - 3 секунды и 232 миллисекунды.
Заметим, что вычисление длины последовательности для конкретного числа не зависит ни от каких других вычислений, поэтому эту задачу можно эффективно распараллелить. Для этого воспользуемся методом Parallel.For:
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace _14
{
class Program
{
static long Max;
static long MaxNum;
static void calc(long a)
{
long ans = 1;
long tmp_a = a;
while (a != 1)
{
if (a % 2 == 0)
a /= 2;
else
a = 3 * a + 1;
ans++;
}
if (Max < ans)
{
MaxNum = tmp_a;
Max = ans;
}
}
static void Main(string[] args)
{
Max = -1;
MaxNum = 0;
DateTime startTime = DateTime.Now;
System.Threading.Tasks.Parallel.For(1, 1000000, i =>
{
calc(i);
}
);
DateTime stopTime = DateTime.Now;
TimeSpan elapsed = stopTime - startTime;
Console.WriteLine("elapsed seconds: {0}", elapsed.Seconds);
Console.WriteLine("elapsed milliseconds: {0}", elapsed.Milliseconds);
Console.WriteLine(MaxNum);
Console.ReadKey();
}
}
}
Понадобилось немного изменить всего одну строчку и время работы уменьшилось до 1 секунды и 738 миллисекунд, т.е. примерно в 2 раза.