Следующий тест nunit сравнивает производительность между запуском одного потока и запуском 2 потоков на двухъядерном компьютере. В частности, это двухъядерный виртуальный компьютер с Windows 7 VMWare, работающий на четырехъядерном узле SLED Linux с Dell Inspiron 503.
Каждый поток просто выполняет цикл и увеличивает 2 счетчика, addCounter и readCounter. Этот тест был оригинальным тестированием реализации очереди, которая, как было обнаружено, хуже работает на многоядерной машине. Итак, сужая проблему до небольшого воспроизводимого кода, у вас здесь нет очереди, только увеличивающие переменные, и, к шоку и тревоге, это намного медленнее с двумя потоками, чем с одним.
При запуске первого теста диспетчер задач показывает, что одно ядро занято на 100%, а другое ядро почти бездействует. Вот результаты теста для однопоточного теста:
readCounter 360687000
readCounter2 0
total readCounter 360687000
addCounter 360687000
addCounter2 0
Вы видите более 360 миллионов приращений!
Затем двухпоточный тест показывает, что оба ядра загружены на 100% в течение всех 5 секунд теста. Однако его вывод показывает только:
readCounter 88687000
readCounter2 134606500
totoal readCounter 223293500
addCounter 88687000
addCounter2 67303250
addFailure0
Это всего лишь 223 миллиона приращений чтения. Что это за творение бога, если эти два процессора делают за эти 5 секунд меньше работы?
Любая возможная подсказка? И можете ли вы запустить тесты на своей машине, чтобы увидеть, есть ли у вас другие результаты? Одна идея состоит в том, что, возможно, двухъядерная производительность VMWare - это не то, на что вы надеялись.
using System;
using System.Threading;
using NUnit.Framework;
namespace TickZoom.Utilities.TickZoom.Utilities
{
[TestFixture]
public class ActiveMultiQueueTest
{
private volatile bool stopThread = false;
private Exception threadException;
private long addCounter;
private long readCounter;
private long addCounter2;
private long readCounter2;
private long addFailureCounter;
[SetUp]
public void Setup()
{
stopThread = false;
addCounter = 0;
readCounter = 0;
addCounter2 = 0;
readCounter2 = 0;
}
[Test]
public void TestSingleCoreSpeed()
{
var speedThread = new Thread(SpeedTestLoop);
speedThread.Name = "1st Core Speed Test";
speedThread.Start();
Thread.Sleep(5000);
stopThread = true;
speedThread.Join();
if (threadException != null)
{
throw new Exception("Thread failed: ", threadException);
}
Console.Out.WriteLine("readCounter " + readCounter);
Console.Out.WriteLine("readCounter2 " + readCounter2);
Console.Out.WriteLine("total readCounter " + (readCounter + readCounter2));
Console.Out.WriteLine("addCounter " + addCounter);
Console.Out.WriteLine("addCounter2 " + addCounter2);
}
[Test]
public void TestDualCoreSpeed()
{
var speedThread1 = new Thread(SpeedTestLoop);
speedThread1.Name = "Speed Test 1";
var speedThread2 = new Thread(SpeedTestLoop2);
speedThread2.Name = "Speed Test 2";
speedThread1.Start();
speedThread2.Start();
Thread.Sleep(5000);
stopThread = true;
speedThread1.Join();
speedThread2.Join();
if (threadException != null)
{
throw new Exception("Thread failed: ", threadException);
}
Console.Out.WriteLine("readCounter " + readCounter);
Console.Out.WriteLine("readCounter2 " + readCounter2);
Console.Out.WriteLine("totoal readCounter " + (readCounter + readCounter2));
Console.Out.WriteLine("addCounter " + addCounter);
Console.Out.WriteLine("addCounter2 " + addCounter2);
Console.Out.WriteLine("addFailure" + addFailureCounter);
}
private void SpeedTestLoop()
{
try
{
while (!stopThread)
{
for (var i = 0; i < 500; i++)
{
++addCounter;
}
for (var i = 0; i < 500; i++)
{
readCounter++;
}
}
}
catch (Exception ex)
{
threadException = ex;
}
}
private void SpeedTestLoop2()
{
try
{
while (!stopThread)
{
for (var i = 0; i < 500; i++)
{
++addCounter2;
i++;
}
for (var i = 0; i < 500; i++)
{
readCounter2++;
}
}
}
catch (Exception ex)
{
threadException = ex;
}
}
}
}
Изменить: я тестировал вышеуказанное на четырехъядерном ноутбуке без vmware и получил аналогичное снижение производительности. Поэтому я написал еще один тест, аналогичный приведенному выше, но в котором каждый метод потока находится в отдельном классе. Моей целью было протестировать 4 ядра.
Что ж, этот тест показал отличные результаты, которые улучшились почти линейно с 1, 2, 3 или 4 ядрами.
После некоторых экспериментов на обеих машинах выяснилось, что надлежащая производительность достигается только в том случае, если методы основного потока находятся в разных экземплярах, а не в одном экземпляре.
Другими словами, если основной метод ввода нескольких потоков включен в один и тот же экземпляр определенного класса, то производительность на многоядерном сервере будет хуже для каждого добавляемого вами потока, а не лучше, как вы могли предположить.
Кажется, что CLR «синхронизируется», поэтому только один поток одновременно может работать с этим методом. Однако мое тестирование показывает, что это не так. Так что до сих пор неясно, что происходит.
Но моя собственная проблема, похоже, решается просто путем создания отдельных экземпляров методов для запуска потоков в качестве отправной точки.
С уважением, Уэйн
РЕДАКТИРОВАТЬ:
Вот обновленный модульный тест, который тестирует 1, 2, 3 и 4 потока с их всеми в одном экземпляре класса. Использование массивов с переменными использует в цикле потока не менее 10 элементов друг от друга. И производительность по-прежнему значительно снижается для каждого добавленного потока.
using System;
using System.Threading;
using NUnit.Framework;
namespace TickZoom.Utilities.TickZoom.Utilities
{
[TestFixture]
public class MultiCoreSameClassTest
{
private ThreadTester threadTester;
public class ThreadTester
{
private Thread[] speedThread = new Thread[400];
private long[] addCounter = new long[400];
private long[] readCounter = new long[400];
private bool[] stopThread = new bool[400];
internal Exception threadException;
private int count;
public ThreadTester(int count)
{
for( var i=0; i<speedThread.Length; i+=10)
{
speedThread[i] = new Thread(SpeedTestLoop);
}
this.count = count;
}
public void Run()
{
for (var i = 0; i < count*10; i+=10)
{
speedThread[i].Start(i);
}
}
public void Stop()
{
for (var i = 0; i < stopThread.Length; i+=10 )
{
stopThread[i] = true;
}
for (var i = 0; i < count * 10; i += 10)
{
speedThread[i].Join();
}
if (threadException != null)
{
throw new Exception("Thread failed: ", threadException);
}
}
public void Output()
{
var readSum = 0L;
var addSum = 0L;
for (var i = 0; i < count; i++)
{
readSum += readCounter[i];
addSum += addCounter[i];
}
Console.Out.WriteLine("Thread readCounter " + readSum + ", addCounter " + addSum);
}
private void SpeedTestLoop(object indexarg)
{
var index = (int) indexarg;
try
{
while (!stopThread[index*10])
{
for (var i = 0; i < 500; i++)
{
++addCounter[index*10];
}
for (var i = 0; i < 500; i++)
{
++readCounter[index*10];
}
}
}
catch (Exception ex)
{
threadException = ex;
}
}
}
[SetUp]
public void Setup()
{
}
[Test]
public void SingleCoreTest()
{
TestCores(1);
}
[Test]
public void DualCoreTest()
{
TestCores(2);
}
[Test]
public void TriCoreTest()
{
TestCores(3);
}
[Test]
public void QuadCoreTest()
{
TestCores(4);
}
public void TestCores(int numCores)
{
threadTester = new ThreadTester(numCores);
threadTester.Run();
Thread.Sleep(5000);
threadTester.Stop();
threadTester.Output();
}
}
}
i
счетчик, он же делает i ++ дважды, это сделано намеренно. могли бы мы сохранить++
постоянным образом, ++ counter vs counter ++, чтобы код было легче читать, если следовать шаблону. - person Seabizkit   schedule 31.01.2020private volatile bool stopThread = false;
, два потока просматривают это vs 1, что означает 0 состязаний. но все же интересно ... как вы и не подумали, это будет такая неуверенность. - person Seabizkit   schedule 31.01.2020