Я экспериментирую с новыми инструментами параллелизма в .NET 4, вычисляя Pi с использованием методов Монте-Карло.
(Фактический алгоритм не так важен, но для ясности, вот он:
- Выбор
numIterations
случайных точек внутри единичного квадрата.
- Подсчитайте количество этих точек, которые лежат внутри круга, ограниченного этим квадратом (то есть точек, расстояние от центра которых меньше 0,5)
- Тогда для очень больших
numIterations
, PI=4 * iterationsInsideCircle / numIterations
.)
У меня есть метод int ThrowDarts(int numDarts)
, который выбирает numDarts
случайные точки внутри единичного квадрата (описано выше) и возвращает количество точек, лежащих в единичном круге:
protected static int ThrowDarts(int iterations)
{
int dartsInsideCircle = 0;
Random random = new Random();
for (int iteration = 0; iteration < iterations; iteration++)
{
double pointX = random.NextDouble() - 0.5;
double pointY = random.NextDouble() - 0.5;
double distanceFromOrigin = Math.Sqrt(pointX*pointX + pointY*pointY);
bool pointInsideCircle = distanceFromOrigin <= 0.5;
if (pointInsideCircle)
{
dartsInsideCircle++;
}
}
return dartsInsideCircle;
}
По сути, в каждой из моих разных реализаций (каждая из которых использует разные параллельные механизмы) я пишу разные способы броска и подсчета дротиков внутри круга.
Например, моя однопоточная реализация просто:
protected override int CountInterationsInsideCircle()
{
return ThrowDarts(_numInterations);
}
У меня также есть этот метод для одного из моих параллельных алгоритмов:
protected override int CountInterationsInsideCircle()
{
Task<int>[] tasks = new Task<int>[_numThreads];
for (int i = 0; i < _numThreads; i++)
{
tasks[i] = Task.Factory.StartNew(() => ThrowDarts(_numInterations/_numThreads));
}
int iterationsInsideCircle = 0;
for (int i = 0; i < _numThreads; i++)
{
iterationsInsideCircle += tasks[i].Result;
}
return iterationsInsideCircle;
}
Надеюсь, вы получите картину.
Здесь я подхожу к своей головоломке. Версия Parallel.For
, которую я пишу, вызывает огромное количество переключений контекста. Код ниже:
protected override int CountInterationsInsideCircle()
{
ConcurrentBag<int> results = new ConcurrentBag<int>();
int result = 0;
Parallel.For(0, _numInterations,
// initialise each thread by setting it's hit count to 0
() => 0,
//in the body, we throw one dart and see whether it hit or not
(iteration, state, localState) => localState + ThrowDarts(1),
// finally, we sum (in a thread-safe way) all the hit counts of each thread together
results.Add);
foreach(var threadresult in results)
{
result+=threadresult;
}
return result;
}
Версия, использующая Parallel.For
, работает, но очень, очень медленно, из-за вышеупомянутого переключения контекста (что не происходит в предыдущих двух методах).
Может ли кто-нибудь объяснить мне, почему это может происходить?