Производитель / Потребитель с C # структурирует? - PullRequest
0 голосов
/ 15 октября 2018

У меня есть одноэлементный объект, который обрабатывает запросы.Каждый запрос занимает около одной миллисекунды, обычно меньше.Этот объект не является потокобезопасным и ожидает запросы в определенном формате, инкапсулированные в классе Request, и возвращает результат как Response.У этого процессора есть другой производитель / потребитель, который отправляет / получает через сокет.

Я реализовал подход производителя / потребителя для быстрой работы:

  • Клиент готовит объект команды RequestCommand,который содержит TaskCompletionSource<Response> и предполагаемый Request.
  • Клиент добавляет команду в «очередь запросов» (Queue<>) и ожидает command.Completion.Task.
  • Другой поток (и фактический фон Thread) извлекает команду из «очереди запросов», обрабатывает command.Request, генерирует Response и сигнализирует о выполнении команды, используя command.Completion.SetResult(response).
  • Клиент продолжает работать.

Но, выполняя небольшой тест памяти, я вижу МНОЖЕСТВО этих объектов, которые создаются и возглавляют список наиболее распространенных объектов в памяти.Обратите внимание, что утечки памяти нет, GC может аккуратно очищать все при каждом срабатывании, но очевидно, что очень много быстро создаваемых объектов делает Gen 0 очень большим.Интересно, может ли лучшее использование памяти привести к лучшей производительности.

Я рассматривал возможность преобразования некоторых из этих объектов в структуры, чтобы избежать выделения, особенно теперь, когда есть некоторые новые функции для работы с ними C # 7.1.Но я не вижу способа сделать это.

  • Типы значений могут быть созданы в стеке, но если они передаются из потока в поток, они должны быть скопированы в stackA-> heap and heap-> StackB, я думаю.Кроме того, при постановке в очередь он переходит из стека в кучу.
  • Объект singleton действительно асинхронный.Существует некоторая обработка в памяти, но в 90% случаев ей нужно звонить извне и проходить через внутреннего производителя / потребителя.
  • ValueTask<>, кажется, здесь не подходит, потому что все асинхронно.
  • TaskCompletionSource<> имеет состояние, но оно object, поэтому оно должно быть в штучной упаковке.
  • Команда также переходит из потока в поток.
  • Получение объектов работает толькодля самой команды ее содержимое не может быть переработано (TaskCompletionSource<> и string).

Можно ли каким-либо образом использовать структуры для уменьшения использования памяти и / или повышения производительности?Любой другой вариант?

1 Ответ

0 голосов
/ 15 октября 2018

Типы значений могут быть созданы в стеке, но если они передаются из потока в поток, они должны быть скопированы в stackA-> heap и heap-> stackB, я полагаю.

Нет, это совсем не так.Но у вас есть более глубокая проблема в вашем мышлении:

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

Правда в том, что стек против кучи не имеет ничего общего с типами значений.Вместо «стек и куча» начните говорить «пул краткосрочного распределения» и «пул долгосрочного распределения». Переменные с коротким временем жизни выделяются из пула краткосрочного выделения , независимо от того, содержит ли эта переменная int или ссылку на объект.Как только вы начинаете думать о переменном времени жизни, ваши рассуждения становятся совершенно простыми. Очевидно, что краткосрочные объекты живут в краткосрочном пуле.

Итак: когда вы передаете структуру из одного потока в другой, она когда-нибудь живет "в куче"? Вопрос бессмысленный, потому что ценности - это не вещи, которые живут в куче . Переменные - это вещи, которые являются хранилищем;переменные store value.

Итак: это тот случай, когда преобразование классов в структуры улучшит производительность, потому что "эти структуры могут жить в стеке"?Нет, конечно нет. Соответствующая разница между ссылочными типами и типами значений заключается не в том, где они живут, а в том, как они копируются .Типы значений копируются по значению, ссылочные типы копируются по ссылкам, а эталонные копии являются самыми быстрыми копиями.

Я вижу множество создаваемых объектов и возглавляющих список самых распространенных объектов в памяти.Обратите внимание, что утечки памяти нет, GC может аккуратно очищать все при каждом срабатывании, но очевидно, что очень много быстро создаваемых объектов делает Gen 0 очень большим.Интересно, может ли лучшее использование памяти дать лучшую производительность.

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

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

Предположим, вы обнаружили, что коллекции поколения 0 действительно снижают вашу производительность;что ты можешь сделать?Является ли ответ, чтобы сделать больше вещей структур?Это может сработать, но вы должны быть очень осторожны:

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

Когда мы столкнулись с этой проблемой в Рослине, мы очень тщательно обдумали ее и провели лот экспериментов.В целом мы использовали стратегию , а не для перемещения вещей в стек.Скорее, мы определили, сколько небольших, недолговечных объектов было активными в памяти одновременно, каждого типа - с использованием профилировщика - и затем реализовали стратегию объединения этих объектов.Вам нужен маленький предмет, вы вынимаете его из бассейна.Когда вы закончите, вы положите его обратно в бассейн.В результате вы получаете O (количество активных одновременно объектов) в пуле, которое быстро перемещается в кучу второго поколения;Затем вы значительно снижаете нагрузку на коллекцию Gen 0 и увеличиваете стоимость сравнительно редких коллекций Gen 2.

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

...