Я собрал быстрый и грязный тест в C #, используя простой генератор в качестве теста.Тест генерирует простые числа до постоянного предела (я выбрал 500000), используя простую реализацию Sieve of Eratosthenes, и повторяет тест 800 раз, распараллеливаясь с определенным числом потоков, используя .NET ThreadPool
или отдельные потоки.
Тест проводился на четырехъядерном Q6600 под управлением Windows Vista (x64).Это не использует Task Parallel Library, просто простые потоки.Он был запущен для следующих сценариев:
- Последовательное выполнение (без потоков)
- 4 потока (то есть по одному на ядро) с использованием
ThreadPool
- 40потоки, использующие
ThreadPool
(для проверки эффективности самого пула) - 4 автономных потока
- 40 автономных потоков, для имитации давления переключения контекста
Результаты были:
Test | Threads | ThreadPool | Time
-----+---------+------------+--------
1 | 1 | False | 00:00:17.9508817
2 | 4 | True | 00:00:05.1382026
3 | 40 | True | 00:00:05.3699521
4 | 4 | False | 00:00:05.2591492
5 | 40 | False | 00:00:05.0976274
Выводы, которые можно сделать из этого:
Распараллеливание не является идеальным (как и ожидалось - никогда не бывает, независимо от среды), но распределение нагрузки по 4 ядрам приводит к увеличению пропускной способности примерно в 3,5 раза, и вряд ли на что жаловаться.
Между 4 и 40 потоками, использующими ThreadPool
, была незначительная разница, что означает, что с пулом не происходит никаких существенных затрат, даже если вы бомбардируете его запросами.
Между версиями ThreadPool
и бесплатной резьбой была незначительная разница, что означаетчто ThreadPool
не имеет значительных «постоянных» расходов;
Существовала незначительная разница между версиями с 4 и 40 потоками без потоков, что означает, что .NET не выполняет никакиххуже, чем можно было бы ожидать при интенсивном переключении контекста.
Нужен ли нам даже эталонный тест C ++ для сравнения?Результаты довольно ясны: потоки в .NET не медленные.Если вы , программист, не напишете плохой многопоточный код и не закончите с истощением ресурсов или блокировкой конвоев, вам действительно не придется беспокоиться.
С .NET 4.0 и TPLа также улучшения ThreadPool
, очереди на кражу работы и все эти интересные вещи, у вас есть еще больше возможностей для написания «сомнительного» кода и при этом эффективная работа.Вы не получаете эти функции вообще из C ++.
Для справки вот тестовый код:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace ThreadingTest
{
class Program
{
private static int PrimeMax = 500000;
private static int TestRunCount = 800;
static void Main(string[] args)
{
Console.WriteLine("Test | Threads | ThreadPool | Time");
Console.WriteLine("-----+---------+------------+--------");
RunTest(1, 1, false);
RunTest(2, 4, true);
RunTest(3, 40, true);
RunTest(4, 4, false);
RunTest(5, 40, false);
Console.WriteLine("Done!");
Console.ReadLine();
}
static void RunTest(int sequence, int threadCount, bool useThreadPool)
{
TimeSpan duration = Time(() => GeneratePrimes(threadCount, useThreadPool));
Console.WriteLine("{0} | {1} | {2} | {3}",
sequence.ToString().PadRight(4),
threadCount.ToString().PadRight(7),
useThreadPool.ToString().PadRight(10),
duration);
}
static TimeSpan Time(Action action)
{
Stopwatch sw = new Stopwatch();
sw.Start();
action();
sw.Stop();
return sw.Elapsed;
}
static void GeneratePrimes(int threadCount, bool useThreadPool)
{
if (threadCount == 1)
{
TestPrimes(TestRunCount);
return;
}
int testsPerThread = TestRunCount / threadCount;
int remaining = threadCount;
using (ManualResetEvent finishedEvent = new ManualResetEvent(false))
{
for (int i = 0; i < threadCount; i++)
{
Action testAction = () =>
{
TestPrimes(testsPerThread);
if (Interlocked.Decrement(ref remaining) == 0)
{
finishedEvent.Set();
}
};
if (useThreadPool)
{
ThreadPool.QueueUserWorkItem(s => testAction());
}
else
{
ThreadStart ts = new ThreadStart(testAction);
Thread th = new Thread(ts);
th.Start();
}
}
finishedEvent.WaitOne();
}
}
[MethodImpl(MethodImplOptions.NoOptimization)]
static void IteratePrimes(IEnumerable<int> primes)
{
int count = 0;
foreach (int prime in primes) { count++; }
}
static void TestPrimes(int testRuns)
{
for (int t = 0; t < testRuns; t++)
{
var primes = Primes.GenerateUpTo(PrimeMax);
IteratePrimes(primes);
}
}
}
}
А вот основной генератор:
using System;
using System.Collections.Generic;
using System.Linq;
namespace ThreadingTest
{
public class Primes
{
public static IEnumerable<int> GenerateUpTo(int maxValue)
{
if (maxValue < 2)
return Enumerable.Empty<int>();
bool[] primes = new bool[maxValue + 1];
for (int i = 2; i <= maxValue; i++)
primes[i] = true;
for (int i = 2; i < Math.Sqrt(maxValue + 1) + 1; i++)
{
if (primes[i])
{
for (int j = i * i; j <= maxValue; j += i)
primes[j] = false;
}
}
return Enumerable.Range(2, maxValue - 1).Where(i => primes[i]);
}
}
}
Если вы видите какие-либо явные недостатки в тесте, дайте мне знать.За исключением каких-либо серьезных проблем с самим тестом, я думаю, что результаты говорят сами за себя, и сообщение ясно:
Не слушайте тех, кто делает слишком широкие и безоговорочные заявления о том, как производительность.NET или любой другой язык / среда "плохи" в какой-то конкретной области, потому что они, вероятно, говорят из своих ... задних частей.