Производительность двухъядерного процессора хуже, чем одноядерного?

В следующем модульном тесте сравнивается производительность между запуском одного потока и запуском двух потоков на двухъядерной машине. В частности, это двухъядерная виртуальная машина VMWare с Windows 7, работающая на четырехъядерном хосте Linux SLED с Dell Inspiron 503.

Каждый поток просто выполняет цикл и увеличивает 2 счетчика, addCounter и readCounter. Этот тест изначально тестировал реализацию Queue, которая, как выяснилось, хуже работает на многоядерной машине. Итак, сужая проблему до небольшого воспроизводимого кода, вы имеете здесь без очереди только инкрементирование переменных и, к шоку и ужасу, это намного медленнее с двумя потоками, чем с одним.

При выполнении первого теста диспетчер задач показывает, что одно из ядер занято на 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 миллиона приращений чтения. Что, ради бога, делают эти 2 процессора в течение этих 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;
            }
        }


    }
}

Edit: I tested the above on a quad core laptop w/o vmware and got similar degraded performance. Поэтому я написал другой тест, похожий на приведенный выше, но в котором каждый метод потока находится в отдельном классе. Моей целью было протестировать 4 ядра.

Этот тест показал отличные результаты, которые улучшались почти линейно с 1, 2, 3 или 4 ядрами.

После некоторых экспериментов на обеих машинах выяснилось, что надлежащая производительность достигается только в том случае, если методы главного потока работают с разными экземплярами, а не с одним и тем же экземпляром.

Другими словами, если несколько потоков выполняют основной метод на одном и том же экземпляре конкретного класса, то производительность на многоядерной машине будет хуже для каждого добавляемого потока, а не лучше, как можно было бы предположить.

Кажется, что CLR "синхронизируется", так что только один поток одновременно может работать с этим методом. Однако мое тестирование показало, что это не так. Так что все еще неясно, что происходит.

Но моя собственная проблема, похоже, решается просто путем создания отдельных экземпляров методов для запуска потоков в качестве начальной точки.

Искренне, Wayne

EDIT:

Вот обновленный модульный тест, который тестирует 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();
        }
    }
}
7
задан Wayne 26 December 2011 в 04:05
поделиться