Информация об изменениях

Сообщение Re[16]: .net core и async lock от 12.04.2021 10:20

Изменено 12.04.2021 10:34 vdimas

Re[16]: .net core и async lock
Здравствуйте, artelk, Вы писали:

A>А чем таски тежеловесны? Слишком много полей? Методы слишком много делают?


"Методы делают много" — хорошее определение. ))

Дотнетный Task — это в одном лице и promise, и future, и task, и task_group с т.з. дизайна аналогичных сущностей в других языках/фреймворках.
А так же одновременно "ответная часть" шедулера, которую условно можно назвать "work item", содержащая в себе настройку параметров исполнения этого "work item" шедуллером.

Соответственно, простейшие операции сопровождаются длинющщими цепочками if-ов по куче признаков, в сравнении с дизайном, где упомянутые сущности разделены.

Еще содержат в себе хелперы сугубо для отладки (для представления flow асинхронных задач в Студии), но эти хелперы живы так же в релизе и тоже потребляют свои процессорные тики.

Еще часто оперируют TLS-хранилищем, перетасовывая стек ExecutionContext, что например, порой требует даже постоянной переустановки культуры текущему потоку перед каждым чихом.
(в нейтиве культура привязана к "физическому потоку", то бишь потоку уровня ОС, а в дотнете — к логическому).



A>Полагаю, ты завернешь колбак в тип, реализующий IValueTaskSource.


Это требуется, только если брать готовый awaitable ValueTask.
Но вообще реализовать свой из пары методов будет, наверно, правильней.
Потому что ValueTask тоже продирается через приличный заслон if-ов на каждый чих, потому что его устройство схематично такое: { obj: object, token: short, value: T}.
Если операция закончилась синхронно, то obj равен null, value содержит результат.
Иначе obj — это или Task, или IValueTaskSource, т.е. значение этого поля проверяется на null, потом еще выясняется тип этого поля.

В общем, использовать ValueTask — экономить на спичках.


A>Какую-то очередь тебе в любом случае нужно будет делать.


Разумеется.


A>SemaphoreSlim работает как FIFO (правда, это свойство не отраженов документации, но лучше бы его воспроизвести).


ХЗ.
В недрах ОС базовая очередь потоков одинакового приоритета к ресурсу всегда FIFO.

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


A>Это будет либо очередь на связном списке, либо что-то с массивами.


Связанный список требует дополнительной аллокации, но в случае попыток сделать lock-free реализацию такого семафора в любом случае будет связанный список.

Если же начать с блокируемых операций (как оно есть сейчас в SemaphoreSlim), то можно и на массиве, тут пофик.
На массиве тоже разные техники есть — от циклического буфера до связанного списка слотов (т.е. массив служит чем-то вроде страницы памяти, в которой выделяются элементы одинакового размера).


A>Если есть основания считать, что твоя очередь лучше — можешь пул реквест сделать.


Тут тоже ХЗ.
В SemaphoreSlim это очередь прокси-задач, полностью захватывающих текущий контекст.
И хотя в обычном случае это не требуется (например, однородный контекст серверной или фоновой программы), в системе смешанных контекстов это может иметь значение (WinForm, плюс FPW, плюс пул потоков).

В общем, техника реализации собсно очереди — это меньший из вопросов.
Если когда-нить дойдут руки, можно будет попробовать и на lock-free очереди это сделать.
Там необходимо будет выполнить специфический lock-free алгоритм — захватить ресурс или встать в очередь, а потом опять попытаться захватить ресурс и выполнить задачу из очереди, бо пока мы ставили задачу в очередь, ресурс могли освободить и просто некому будет проверить очередь до следующей блокировки доступа к ресурсу.

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

Поэтому, там только через тестирования различных по нагруженности сценариев.
Или через моделирование, типа как описано здесь:
https://cyberleninka.ru/article/n/matematicheskie-modeli-dlya-kachestvennoy-otsenki-proizvoditelnosti-semaforov-mnogoprotsessornyh-vychislitelnyh-sistem/viewer


A>Это деталь реализации SemaphoreSlim, спрятанная от клиентов. Есть большой шанс, что примут.


А я настороженно отношусь с рассуждениям в духе "подменять текущую реализацию".

Прикинь радость товарищей, когда у них после обновления фреймворка вдруг перестанет работать некий код.
И пусть даже вина будет в их собственном кривом коде (или слишком компромиссном из-за компромиссности дотнета), но раньше-то он работал!!!111 ))



A>Еще вариант придумался: попробовать сделать свой собственный хитрый MyValueTask, не требующий боксинга.


Там ничего хитрого.


A>Очередь будет реализована через связный список массивов (примерно как реализован ConcurrentQueue), в которых по значению будут лежать нужные структуры. MyValueTask будет хранить ссылку на внутренний массив и индекс в нем.


Если очередь сделать на массиве, то индекс элемента массива хранить придётся, ес-но.


A>И от объектов в куче мы не избавились — мы массивы создаем.


Я бы ограничился одним массивом.
В реальной жизни конкурентность к ресурсу обычно невысока — от силы десятки или единицы сотен "заявок".

Если же на одном ресурсе сталкиваются тысячи одновременных "заявок" — тут уже стоит думать над дизайном через мат.аппарат СМО (системы массового обслуживания).
В этих случаях обычно очередь выносится выше по архитектуре и обслуживается явно, через знания о характере задач (приоритетах, их взаимных блокировках и прочее).
Какая-нить простейшая очередь семафора этими знаниями не обладает.


A>Переиспользование по кругу массивов (в которых все Task-и completed) не вариант, т.к. мы не знаем, живет ли еще хоть один MyValueTask, ссылающийся на наш массив. Не знаю, будет ли все это заметно эффективней, чем то, как сейчас SemaphoreSlim сделан.


Вскрытие тестирование покажет. ))
Обычно самописные показываемые мною приблуды давали повышение эффективности до 2-3-х раз от стандартных вещей.
И зачастую на ровном месте вызывали ненависть споры до пены (от одного только факта демонстрации более быстрого решения), мол, ускорение конкретно этой операции мало что даёт в целом.

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

Такие споры обычно быстро доходят до точки "в любом случае всё упирается в долгое обращение к БД, поэтому, там нет смысла экономить на спичках" и поклонники религии "память не ресурс" удаляются с победным видом. ))

Что однажды вылилось в кровавый спор насчёт того, насколько быстродейственны могут быть специализированные сетевые БД, когда оппонентам просто подкинули реальные цифры задержек ответов от тех же бирж, поддерживающих высокочастотный трейдинг (а все ведущие поддерживают).

Ну и, меня уже утомило много лет объяснять фундаментальную ошибку рассуждения "всё-равно мы ждём БД".
Ждать можно по-разному.

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

Т.е., пока текущая задача ждёт отклик БД, проц вполне может быть занят чем-то еще.
Но это, походу, какое-то недоступное простым смертным прям откровение или я ХЗ насчёт причин ответной упоротости.
Re[16]: .net core и async lock
Здравствуйте, artelk, Вы писали:

A>А чем таски тежеловесны? Слишком много полей? Методы слишком много делают?


"Методы делают много" — хорошее определение. ))

Дотнетный Task — это в одном лице и promise, и future, и task, и task_group с т.з. дизайна аналогичных сущностей в других языках/фреймворках.
А так же одновременно "ответная часть" шедулера, которую условно можно назвать "work item", содержащая в себе настройку параметров исполнения этого "work item" шедуллером.

Соответственно, простейшие операции сопровождаются длинющщими цепочками if-ов по куче признаков, в сравнении с дизайном, где упомянутые сущности разделены.

Еще содержат в себе хелперы сугубо для отладки (для представления flow асинхронных задач в Студии), но эти хелперы живы так же в релизе и тоже потребляют свои процессорные тики.

Еще часто оперируют TLS-хранилищем, перетасовывая стек ExecutionContext, что например, порой требует даже постоянной переустановки культуры текущему потоку перед каждым чихом.
(в нейтиве культура привязана к "физическому потоку", то бишь потоку уровня ОС, а в дотнете — к логическому).



A>Полагаю, ты завернешь колбак в тип, реализующий IValueTaskSource.


Это требуется, только если брать готовый awaitable ValueTask.
Но вообще реализовать свой из пары методов будет, наверно, правильней.
Потому что ValueTask тоже продирается через приличный заслон if-ов на каждый чих, потому что его устройство схематично такое: { obj: object, token: short, value: T}.
Если операция закончилась синхронно, то obj равен null, value содержит результат.
Иначе obj — это или Task, или IValueTaskSource, т.е. значение этого поля проверяется на null, потом еще выясняется тип этого поля.

В общем, использовать ValueTask — экономить на спичках.


A>Какую-то очередь тебе в любом случае нужно будет делать.


Разумеется.


A>SemaphoreSlim работает как FIFO (правда, это свойство не отраженов документации, но лучше бы его воспроизвести).


ХЗ.
В недрах ОС базовая очередь потоков одинакового приоритета к ресурсу всегда FIFO.

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


A>Это будет либо очередь на связном списке, либо что-то с массивами.


Связанный список требует дополнительной аллокации, но в случае попыток сделать lock-free реализацию такого семафора в любом случае будет связанный список.

Если же начать с блокируемых операций (как оно есть сейчас в SemaphoreSlim), то можно и на массиве, тут пофик.
На массиве тоже разные техники есть — от циклического буфера до связанного списка слотов (т.е. массив служит чем-то вроде страницы памяти, в которой выделяются элементы одинакового размера).


A>Если есть основания считать, что твоя очередь лучше — можешь пул реквест сделать.


Тут тоже ХЗ.
В SemaphoreSlim это очередь прокси-задач, полностью захватывающих текущий контекст.
И хотя в обычном случае это не требуется (например, однородный контекст серверной или фоновой программы), в системе смешанных контекстов это может иметь значение (WinForm, плюс FPW, плюс пул потоков).

В общем, техника реализации собсно очереди — это меньший из вопросов.
Если когда-нить дойдут руки, можно будет попробовать и на lock-free очереди это сделать.
Там необходимо будет выполнить специфический lock-free алгоритм — захватить ресурс или встать в очередь, а потом опять попытаться захватить ресурс и выполнить задачу из очереди, бо пока мы ставили задачу в очередь, ресурс могли освободить и просто некому будет проверить очередь до следующей блокировки доступа к ресурсу.

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

Поэтому, там только через тестирования различных по нагруженности сценариев.
Или через моделирование, типа как описано здесь:
https://cyberleninka.ru/article/n/matematicheskie-modeli-dlya-kachestvennoy-otsenki-proizvoditelnosti-semaforov-mnogoprotsessornyh-vychislitelnyh-sistem/viewer


A>Это деталь реализации SemaphoreSlim, спрятанная от клиентов. Есть большой шанс, что примут.


А я настороженно отношусь с рассуждениям в духе "подменять текущую реализацию".

Прикинь радость неких товарищей, когда у них после обновления фреймворка вдруг перестанет работать некий код.
И пусть даже вина будет в их собственном кривом коде (или слишком компромиссном из-за компромиссности дотнета), но раньше-то он работал!!!111 ))



A>Еще вариант придумался: попробовать сделать свой собственный хитрый MyValueTask, не требующий боксинга.


Там ничего хитрого.


A>Очередь будет реализована через связный список массивов (примерно как реализован ConcurrentQueue), в которых по значению будут лежать нужные структуры. MyValueTask будет хранить ссылку на внутренний массив и индекс в нем.


Если очередь сделать на массиве, то индекс элемента массива хранить придётся, ес-но.


A>И от объектов в куче мы не избавились — мы массивы создаем.


Я бы ограничился одним массивом.
В реальной жизни конкурентность к ресурсу обычно невысока — от силы десятки или единицы сотен "заявок".

Если же на одном ресурсе сталкиваются тысячи одновременных "заявок" — тут уже стоит думать над дизайном через мат.аппарат СМО (системы массового обслуживания).
В этих случаях обычно очередь выносится выше по архитектуре и обслуживается явно, через знания о характере задач (приоритетах, их взаимных блокировках и прочее).
Какая-нить простейшая очередь семафора этими знаниями не обладает.


A>Переиспользование по кругу массивов (в которых все Task-и completed) не вариант, т.к. мы не знаем, живет ли еще хоть один MyValueTask, ссылающийся на наш массив. Не знаю, будет ли все это заметно эффективней, чем то, как сейчас SemaphoreSlim сделан.


Вскрытие тестирование покажет. ))
Обычно самописные показываемые мною приблуды давали повышение эффективности до 2-3-х раз от стандартных вещей.
И зачастую на ровном месте вызывали ненависть споры до пены (от одного только факта демонстрации более быстрого решения), мол, ускорение конкретно этой операции мало что даёт в целом.

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

Такие споры обычно быстро доходят до точки "в любом случае всё упирается в долгое обращение к БД, поэтому, там нет смысла экономить на спичках" и поклонники религии "память не ресурс" удаляются с победным видом. ))

Что однажды вылилось в кровавый спор насчёт того, насколько быстродейственны могут быть специализированные сетевые БД, когда оппонентам просто подкинули реальные цифры задержек ответов от тех же бирж, поддерживающих высокочастотный трейдинг (а все ведущие поддерживают).

Ну и, меня уже утомило много лет объяснять фундаментальную ошибку рассуждения "всё-равно мы ждём БД".
Ждать можно по-разному.

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

Т.е., пока текущая задача ждёт отклик БД, проц вполне может быть занят чем-то еще.
Но это, походу, какое-то недоступное простым смертным прям откровение или я ХЗ насчёт причин ответной упоротости.