Вопрос по multithreading, c#, synchronization, concurrency – Программа Threadpool работает намного медленнее на гораздо более быстром сервере

4

upd Теперь я думаю, что корень моей проблемы не в «многопоточности», потому что я наблюдаю замедление в любой точке моей программы. Я думаю, что при использовании двух процессоров моя программа выполняется медленнее, вероятно, потому что два процессора должны "общаться" между друг другом. Мне нужно сделать несколько тестов. Я постараюсь отключить один из процессоров и посмотрю, что произойдет.

====================================

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

Я использовал дешевый сервер DL120 и решил перейти на гораздо более дорогой сервер DL360p с двумя процессорами. Неожиданно моя программа на C # работает примерно в 2 раза медленнее на новом сервере, который должен быть в несколько раз быстрее.

Я обработалБЫСТРО данные для около 60 инструментов. Я создал отдельную задачу для каждого инструмента следующим образом:

        BlockingCollection<OrderUpdate> updatesQuery;
        if (instrument2OrderUpdates.ContainsKey(instrument))
        {
            updatesQuery = instrument2OrderUpdates[instrument];
        } else
        {
            updatesQuery = new BlockingCollection<OrderUpdate>();
            instrument2OrderUpdates[instrument] = updatesQuery;
            ScheduleFastOrdersProcessing(updatesQuery);
        }
        orderUpdate.Checkpoint("updatesQuery.Add");
        updatesQuery.Add(orderUpdate);
    }

    private void ScheduleFastOrdersProcessing(BlockingCollection<OrderUpdate> updatesQuery)
    {
        Task.Factory.StartNew(() =>
        {
            Instrument instrument = null;
            OrderBook orderBook = null;
            int lastRptSeqNum = -1;
            while (!updatesQuery.IsCompleted)
            {
                OrderUpdate orderUpdate;
                try
                {
                    orderUpdate = updatesQuery.Take();
                } catch(InvalidOperationException e)
                {
                    Log.Push(LogItemType.Error, e.Message);
                    continue;
                }
                orderUpdate.Checkpoint("received from updatesQuery.Take()");
                ......................
                ...................... // long not interesting processing code
        }, TaskCreationOptions.LongRunning);

Поскольку у меня есть около 60 задач, которые могут выполняться параллельно, я ожидаю, что 2 * E5-2640 (24 виртуальных потока, 12 реальных потоков) должны работать намного быстрее, чем 1 * E3-1220 (4 реальных потока). Кажется, что с помощью DL360p я нашел 95 потоков в диспетчере задач. Использование DL120 у меня только 55 потоков.

Но время выполнения на DL120G7 в 2 (!!) раза быстрее! E3-1220 имеет немного лучшую тактовую частоту, чем E5-2640 (3,1 ГГц против 2,5 ГГц), однако я все еще ожидаю, что мой код должен работать быстрее на 2 * E5-2640, потому что он может быть параллелизован намного лучше, и я абсолютно не ожидаю что это работает в 2 раза медленнее!

HP DL120G7 E3-1220

~ 50 потоков в диспетчере задач лучше всего = в среднем ~ 80 микросекунд

 calling market.UpdateFastOrder = 23 updatesQuery.Add = 25 received from updatesQuery.Take() = 67 in orderbook = 80
 calling market.UpdateFastOrder = 30 updatesQuery.Add = 32 received from updatesQuery.Take() = 64 in orderbook = 73
 calling market.UpdateFastOrder = 31 updatesQuery.Add = 32 received from updatesQuery.Take() = 195 in orderbook = 204
 calling market.UpdateFastOrder = 31 updatesQuery.Add = 32 received from updatesQuery.Take() = 74 in orderbook = 86
 calling market.UpdateFastOrder = 18 updatesQuery.Add = 21 received from updatesQuery.Take() = 65 in orderbook = 78
 calling market.UpdateFastOrder = 29 updatesQuery.Add = 32 received from updatesQuery.Take() = 76 in orderbook = 88
 calling market.UpdateFastOrder = 30 updatesQuery.Add = 32 received from updatesQuery.Take() = 80 in orderbook = 92
 calling market.UpdateFastOrder = 20 updatesQuery.Add = 21 received from updatesQuery.Take() = 65 in orderbook = 78
 calling market.UpdateFastOrder = 21 updatesQuery.Add = 24 received from updatesQuery.Take() = 68 in orderbook = 81
 calling market.UpdateFastOrder = 12 updatesQuery.Add = 13 received from updatesQuery.Take() = 58 in orderbook = 72
 calling market.UpdateFastOrder = 22 updatesQuery.Add = 23 received from updatesQuery.Take() = 51 in orderbook = 59
 calling market.UpdateFastOrder = 16 updatesQuery.Add = 16 received from updatesQuery.Take() = 20 in orderbook = 24
 calling market.UpdateFastOrder = 28 updatesQuery.Add = 31 received from updatesQuery.Take() = 82 in orderbook = 94
 calling market.UpdateFastOrder = 18 updatesQuery.Add = 21 received from updatesQuery.Take() = 65 in orderbook = 77
 calling market.UpdateFastOrder = 29 updatesQuery.Add = 29 received from updatesQuery.Take() = 259 in orderbook = 264
 calling market.UpdateFastOrder = 49 updatesQuery.Add = 52 received from updatesQuery.Take() = 99 in orderbook = 113
 calling market.UpdateFastOrder = 22 updatesQuery.Add = 23 received from updatesQuery.Take() = 50 in orderbook = 60
 calling market.UpdateFastOrder = 29 updatesQuery.Add = 32 received from updatesQuery.Take() = 76 in orderbook = 88
 calling market.UpdateFastOrder = 16 updatesQuery.Add = 19 received from updatesQuery.Take() = 63 in orderbook = 75
 calling market.UpdateFastOrder = 27 updatesQuery.Add = 27 received from updatesQuery.Take() = 226 in orderbook = 231
 calling market.UpdateFastOrder = 15 updatesQuery.Add = 16 received from updatesQuery.Take() = 35 in orderbook = 42
 calling market.UpdateFastOrder = 18 updatesQuery.Add = 21 received from updatesQuery.Take() = 66 in orderbook = 78

HP DL360p G8 2 * E5-2640

~ 95 потоков в диспетчере задач; лучший = 40 в среднем ~ 150 микросекунд

 calling market.UpdateFastOrder = 62 updatesQuery.Add = 64 received from updatesQuery.Take() = 144 in orderbook = 205
 calling market.UpdateFastOrder = 27 updatesQuery.Add = 32 received from updatesQuery.Take() = 101 in orderbook = 154
 calling market.UpdateFastOrder = 45 updatesQuery.Add = 50 received from updatesQuery.Take() = 124 in orderbook = 187
 calling market.UpdateFastOrder = 46 updatesQuery.Add = 51 received from updatesQuery.Take() = 127 in orderbook = 162
 calling market.UpdateFastOrder = 63 updatesQuery.Add = 68 received from updatesQuery.Take() = 137 in orderbook = 174
 calling market.UpdateFastOrder = 53 updatesQuery.Add = 55 received from updatesQuery.Take() = 133 in orderbook = 171
 calling market.UpdateFastOrder = 44 updatesQuery.Add = 46 received from updatesQuery.Take() = 131 in orderbook = 158
 calling market.UpdateFastOrder = 37 updatesQuery.Add = 39 received from updatesQuery.Take() = 102 in orderbook = 140
 calling market.UpdateFastOrder = 45 updatesQuery.Add = 50 received from updatesQuery.Take() = 115 in orderbook = 154
 calling market.UpdateFastOrder = 50 updatesQuery.Add = 55 received from updatesQuery.Take() = 133 in orderbook = 160
 calling market.UpdateFastOrder = 26 updatesQuery.Add = 50 received from updatesQuery.Take() = 99 in orderbook = 111
 calling market.UpdateFastOrder = 14 updatesQuery.Add = 30 received from updatesQuery.Take() = 36 in orderbook = 40   <-- best one I can find among thousands

Можете ли вы понять, почему моя программа работает в 2 раза медленнее на сервере в несколько раз быстрее? Вероятно, я не должен создавать ~ 60 Task? Вероятно, я должен сказать .NET не использовать 95 потоков, а ограничить его 50 или даже 24? Вероятно, это проблема 2 процессоров против 1 конфигурации процессора? Возможно, просто отключение одного из процессоров на моем DL360P Gen8 значительно ускорит программу?

Added

calling market.UpdateFastOrder - orderUpdate object is created updatesQuery.Add - orderUpdate is put into BlockingCollection received from updatesQuery.Take() - orderUpdate ejected from BlockingCollection in orderbook - orderUpdated is parsed and applied to orderBook
Вы не нуждаетесь в VS; есть куча профилировщиков, таких как один изJetBrains, Это, вероятно, единственное жизнеспособное решение, чтобы найти виновника производительности. Lucero
Вы также можете создавать профили на своей машине - думаю, вполне справедливо предположить, что в настоящее время большинство машин имеют несколько процессоров. Carsten
@ Lucero, какой профиль мне нужно запустить и как? У меня нет VS или чего-то подобного, установленного на сервере ... javapowered
Запустите профилировщик, чтобы увидеть, где тратится время. Тем не менее, реальный параллелизм требует надлежащего разделения вещей, например, очень осторожное использование синхронизации. Если вы используете блокировки для синхронизации, это может быть тем, что на самом деле замедляет вас с дополнительными ядрами больше, чем ускоряет. Lucero
@javapowered Не делайте предположений, используйте эмпирические данные. Узнайте, что занимает так много времени и ожидают ли потоки друг друга (используя профилировщик). И если вы думаете, что речь идет не о потоках, то напишите тестовую программу и выясните, действительно ли это так. svick

Ваш Ответ

1   ответ
0

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

Когда я обновлял процессор Quadcore на i7 (виртуальные 8 ядер), я заметил, что установка, использующая больше потоков, чем ядер, приводит к тому, что потоки блокируют друг друга на некоторое время, что приводит к общему замедлению работы системы.

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

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

может быть, вам просто нужно найти оптимальное количество потоков.

как я могу управлять количеством потоков? я просто создаю задачи, а .NET решает, сколько потоков использовать ... javapowered
@ Корсар Или ты могconfigure the number of threads in the pool, Но обычно есть лучшее решение, которое решает основную проблему. И рассматриваемый код на самом деле не использует пул потоков.
Вы должны были бы создать пул потоков вручную. Если вы дадите мне немного времени, я мог бы также привести пример кода, но на самом деле у меня здесь нет кода моего приложения, а вот msdn:A quick example for a manual ThreadPool
@ svick, которым я пользуюсьTask поэтому я позволю .NET решить, сколько потоков следует создать. .NET должен использовать «оптимальный» количество потоков в зависимости от того, сколько ресурсов доступно. Так что .NET использует внутренний пул потоков. Но, во всяком случае, теперь я думаю, что это «2 процессора» проблема конфигурации. Я хочу отключить один из процессоров и проверить, что происходит. javapowered
@javapowered ИспользованиеTask не означает автоматически, что он используетThreadPool, Обычно это так, но вы указываете, что хотите создать новый поток (вне пула) для каждогоTask если вы укажетеLongRunning вариант.Which is exactly what you do.

Похожие вопросы