Re[3]: Горутины и потоки
От: Cyberax Марс  
Дата: 01.07.21 08:23
Оценка:
Здравствуйте, vdimas, Вы писали:

G>>1) Переключение контекста не бесплатное

V>Для потоков одного процесса должно быть бесплатным.
Переключение в режим ядра и обратно никогда не бывает бесплатным.

G>>2) Каждый поток кушает 1МБ под стек минимум

V>Настраивается.
С учётом служебных структур и прочего — в Линуксе около 32Кб. Но это экстремально мало.

V>И эта же проблема присутствует в любой реализации "зеленых потоков".

В Го стек занимает 2048 байт при создании, и потом по необходимости растёт.
Sapienti sat!
Re[9]: Горутины и потоки
От: Sharov Россия  
Дата: 01.07.21 11:36
Оценка:
Здравствуйте, netch80, Вы писали:


S>>Зачем тогда async/await

N>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление.
N>С точки зрения исходного кода — чтобы писать его максимально линейно.
N>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах.

async\await и есть по сути promise.


S>>Ну и замена всяких Lock на ManualResetValueTaskSource

S>>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask
N>Я что-то не могу это раскурить. Какой смысл в его применении?

Вероятно речь о том, что раньше тип task был классом, то теперь может быть (или есть всегда) структура,
соотв. меньше напряг на gc. Например, когда запускаем задачу, которая выполняется синхронно, т.е.
в том же потоке и получается, что task создали зря, лишний напряг на gc. Какое отношение это имеет к дискуссии --
Кодом людям нужно помогать!
Re[10]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 01.07.21 15:31
Оценка:
Здравствуйте, Sharov, Вы писали:

S>>>Зачем тогда async/await

N>>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление.
N>>С точки зрения исходного кода — чтобы писать его максимально линейно.
N>>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах.

S>async\await и есть по сути promise.


Да. Но в случае await просто пишете x = await y(), а для промисов (я имею в виду стиля JS) надо конструировать что-то вроде

p = new Promise(function() { y(); }).then(function(result) { x = result; });


в общем, громоздко.

S>>>Ну и замена всяких Lock на ManualResetValueTaskSource

S>>>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S>>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask
N>>Я что-то не могу это раскурить. Какой смысл в его применении?

S>Вероятно речь о том, что раньше тип task был классом, то теперь может быть (или есть всегда) структура,

S>соотв. меньше напряг на gc. Например, когда запускаем задачу, которая выполняется синхронно, т.е.
S> в том же потоке и получается, что task создали зря, лишний напряг на gc. Какое отношение это имеет к дискуссии --

Ну меньше напряг — всегда полезно но таки да, не в тему.
The God is real, unless declared integer.
Re[10]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 02.07.21 05:10
Оценка:
Здравствуйте, Serginio1, Вы писали:

N>>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу.

S> Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.

Так передавать когда? До него или после? В случае удачного захвата или неудачного?

Не хочу искать, что в Windows. В Linux это выглядит (для внутрипроцессных мьютексов): читаем переменную, которая собственно представляет мьютекс для процесса. Могут быть варианты: 0, 1, 2. 2 значит, что мьютекс залочен и кто-то уже стоит в очереди на него, тогда сразу идём в ядерный вызов становиться в хвост. Если 0 или 1, делаем сколько-то (грубо говоря, 100) попыток захватить его через команду процессора (считай, cmpxchg), если получилось (было 0 и мы записали 1) — ok, работаем, иначе кончились попытки — снова становимся в очередь уже через ядерный вызов.
Освобождение похоже: читаем ячейку; 2 — значит, за нами стоят в очереди и ядерный вызов необходим, чтобы его разбудить. 1 — записываем туда 0 и знаем, что если кто-то захочет, он придёт без дополнительной синхронизации.
Так вот пока крутится цикл с cmpxchg — ядро не дёргаем и ему нет причины задуматься, переключать ли на кого-то ещё, процесс себе продолжает работать. Условие "не стоит передавать управление", насколько я вижу, выполнено идеально.

S>https://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%BD%D1%82%D0%B5%D0%BA%D1%81%D1%82%D0%B0

S>

S>Синхронизирующие примитивы ядра. Мьютексы, Семафоры и т. д. Это и есть основной источник проблем с производительностью. Недостаточно продуманная работа с синхронизирующими примитивами может приводить к десяткам тысяч, а в особо запущенных случаях — и к сотням тысяч переключений контекста в секунду. [источник не указан 2477 дней]


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

S>>>Для примера в эпоху до async/await поток ждет выполнения асинхронной операции.

S>>>async/await берет на себя сохранения данных внутри класса (стек не нужен) и строит автомат и тот же поток который выполнял данную задачу, запускает другую. Нет никаких переключений.

N>>А что, вот эти все "берёт на себя сохранения данных внутри класса" и аналогичное восстановление, по-вашему, не переключение? А что оно такое тогда?

S> Там нет восстаеовления ибо данные хранятся в куче.

Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.

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

N>>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление.

N>>С точки зрения исходного кода — чтобы писать его максимально линейно.
S> Линейно то и раньше писали, только внутри были всякие евенты и остановка потока.

Я говорю про варианты без такой остановки (если переключение, то оно минимально видно в ОС).

N>>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах.

S> Ну в итоге то колбеки запускаются из пула потоков.

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

S>>>Ну и замена всяких Lock на ManualResetValueTaskSource

S>>>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S>>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask

N>>Я что-то не могу это раскурить. Какой смысл в его применении?

S> Смысл в том, что замена lock на lockAsync. То есть нет никакого ожидания потока

Если оно сделано умно (с попытками захвата до переключения) — отлично.

S>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.

N>>Тоже не понимаю, при чём тут эта реплика.
S> В том, что с использованием пула потоков сводит к минимуму переключение потоков

Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.
The God is real, unless declared integer.
Re[9]: Горутины и потоки
От: Cyberax Марс  
Дата: 02.07.21 06:12
Оценка:
Здравствуйте, netch80, Вы писали:

S>>В таком случае зеленые потоки будут просто не нужны. Ну т.е. в ядре ОС выполнять код соотв. стороннего планировщика.

N>И кто пустит посторонний код в ядро?
Можно eBPF использовать

N>Похожие разработки как раз были. Во FreeBSD игрались с подходом "scheduler activations". Это выглядело так: если нить блокируется в ядре, ядро запускает указанный процессом пользовательский код с указанием "а подумай, не переключиться ли на что-то ещё". Пользовательский код управляет тем, какие из видимых ниток должны исполняться.

N>Разработка шла приблизительно в 1999-2002. После отказались и сделали тупо 1:1, все нити процесса известны ядру и оно их переключает. Вот не справились со сложностью.
Проблема в том, что переключения контекстов уж слишком дорогие.
Sapienti sat!
Re[11]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 02.07.21 07:25
Оценка:
Здравствуйте, netch80, Вы писали:

N>Здравствуйте, Serginio1, Вы писали:


N>>>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу.

S>> Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.

N>Так передавать когда? До него или после? В случае удачного захвата или неудачного?

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


N>Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация.


Я тебе привел пример аналога LockAsync. Поток ничего не ждет, а по событию запускается из пула потоков. Вот пример асинхронной очереди
AsyncProducerConsumerCollection
Просто привел пример с использованием ValueTask вместо Task

S>>>>Для примера в эпоху до async/await поток ждет выполнения асинхронной операции.

S>>>>async/await берет на себя сохранения данных внутри класса (стек не нужен) и строит автомат и тот же поток который выполнял данную задачу, запускает другую. Нет никаких переключений.

N>>>А что, вот эти все "берёт на себя сохранения данных внутри класса" и аналогичное восстановление, по-вашему, не переключение? А что оно такое тогда?

S>> Там нет восстаеовления ибо данные хранятся в куче.

N>Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.

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

N>Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.


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

N>>>С точки зрения логики управления выполнением — именно для тех случаев, когда шедулер узнал, что ответа для await сейчас нет и надо дать кому-то управление.

N>>>С точки зрения исходного кода — чтобы писать его максимально линейно.
S>> Линейно то и раньше писали, только внутри были всякие евенты и остановка потока.

N>Я говорю про варианты без такой остановки (если переключение, то оно минимально видно в ОС).


N>>>Переключений в нём, в идеале, столько же, сколько в аналогичном коде на коллбэках или промисах.

S>> Ну в итоге то колбеки запускаются из пула потоков.

N>Ну это уже дотнетовая специфика и не обязательно так везде. И это не обязательно удобно. Я во многих случаях хотел бы ограничить такое явно только тем же тредом, который вызвал исходную операцию.


S>>>>Ну и замена всяких Lock на ManualResetValueTaskSource

S>>>>http://rsdn.org/forum/dotnet/8030645.1
Автор: Serginio1
Дата: 16.06.21

S>>>> https://stackoverflow.com/questions/66387225/awaiting-a-single-net-event-with-a-valuetask

N>>>Я что-то не могу это раскурить. Какой смысл в его применении?

S>> Смысл в том, что замена lock на lockAsync. То есть нет никакого ожидания потока

N>Если оно сделано умно (с попытками захвата до переключения) — отлично.


S>>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.

N>>>Тоже не понимаю, при чём тут эта реплика.
S>> В том, что с использованием пула потоков сводит к минимуму переключение потоков

N>Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.

Вот ссылочка из предыдущего сообщения AsyncSemaphore

Если lock занят то возвращается Task и ожидается пока не вызовется SetResult
Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock
и солнце б утром не вставало, когда бы не было меня
Re[12]: Горутины и потоки
От: Cyberax Марс  
Дата: 02.07.21 23:05
Оценка: 12 (1)
Здравствуйте, Serginio1, Вы писали:

S>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?

Очередь потоков? Пул потоков, в смысле?

Из-за того, что системные потоки очень дорого запускать с нуля.
Sapienti sat!
Re[12]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 03.07.21 07:29
Оценка:
Здравствуйте, Serginio1, Вы писали:

N>>>>Эээ... после выполнения захвата лока (речь об этом, да?) как раз лучше не отдавать управление, а сделать максимум действий во время захвата. А до него — если лок занят — то раз ждём, надо оповестить шедулер о том, что есть отличный повод запустить другую задачу.

S>>> Я как раз про то, что если lock короткий, то не стоит передавать управление на другой поток.

N>>Так передавать когда? До него или после? В случае удачного захвата или неудачного?

S>При удачном захвате стоит дождаться освобождения, что бы другие потоки могли воспользоваться свободным монитором

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

Более того, в случае await-based построения работы вероятность насильственного переключения со стороны ОС выше, если нет возможности её явно проинструктировать "тут воздержись, а тут больше свободы" — если гружёный процесс держит всю активность в нескольких нитках по количеству хартов — с точки зрения ОС это одна неуправляемая прожора, и когда переключать на соседей — она просто не в курсе.

N>>Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация.

S> Я тебе привел пример аналога LockAsync. Поток ничего не ждет, а по событию запускается из пула потоков. Вот пример асинхронной очереди
S>AsyncProducerConsumerCollection
S>Просто привел пример с использованием ValueTask вместо Task

Так с точки зрения ОС что поможет тут осознать, когда можно переключать на другую нить вообще другого процесса, а когда лучше этого не делать, потому что захвачен важный лок (и даже в случае асинхронных локов это блокирует другие задачи)?
Я б ожидал каких-то явных системных вызовов, но тут про них ни слова.

N>>Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.

S> Угу который при переходе на новый поток сбрасывается.

Кто сбрасывается? Кэш процессора? Зачем? Даже с мерами против Meltdown/Spectre кэш сбрасывается при переключении между процессами, а не между нитками одного процесса. Хотя о чём и, главное, чем думают тут в MS, я не в курсе.

S>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?


Кто сказал, что "всё прекрасно"? Я не знаю, откуда и как вы делаете такие выводы.
И что это "все", что делают чтобы отказаться? В основном делают там, где просто неудобно/дорого по другим причинам возлагать это на ОС.

Да, есть цена на переключение, даже вход и выход из системного вызова чего-то стоит, и на Windows эта цена сильно выше, чем на Linux. Может, потому для Windows и стараются сократить эти переключения. В Linux я не вижу такого активного стремления, если где-то такое делают, то в рантаймах со сложной собственной логикой и полностью "управляемым" выполнением (Go, Erlang).

N>>Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.


S>Ну класс если используешь Value типы тоже будут компактно расположены, а не валуе типы что на стеке, что не на нем все равно в куче


То есть в дотнет ещё не завезли escape analysis? (сомневаюсь)

S>>>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.

N>>>>Тоже не понимаю, при чём тут эта реплика.
S>>> В том, что с использованием пула потоков сводит к минимуму переключение потоков

N>>Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.

S>Вот ссылочка из предыдущего сообщения AsyncSemaphore

S>Если lock занят то возвращается Task и ожидается пока не вызовется SetResult

S>Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock

Почему это "нужно заменять"? Обычный lock настолько неэффективен?
The God is real, unless declared integer.
Re[10]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 03.07.21 07:31
Оценка:
Здравствуйте, Cyberax, Вы писали:

S>>>В таком случае зеленые потоки будут просто не нужны. Ну т.е. в ядре ОС выполнять код соотв. стороннего планировщика.

N>>И кто пустит посторонний код в ядро?
C>Можно eBPF использовать

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

N>>Похожие разработки как раз были. Во FreeBSD игрались с подходом "scheduler activations". Это выглядело так: если нить блокируется в ядре, ядро запускает указанный процессом пользовательский код с указанием "а подумай, не переключиться ли на что-то ещё". Пользовательский код управляет тем, какие из видимых ниток должны исполняться.

N>>Разработка шла приблизительно в 1999-2002. После отказались и сделали тупо 1:1, все нити процесса известны ядру и оно их переключает. Вот не справились со сложностью.
C>Проблема в том, что переключения контекстов уж слишком дорогие.

Проблема нового или старого решения? По крайней мере горизонтальное переключение в пределах одного процесса вроде бы дешёвое.
The God is real, unless declared integer.
Re: Горутины и потоки
От: GarryIV  
Дата: 03.07.21 08:21
Оценка:
Здравствуйте, mrTwister, Вы писали:

T>А почему, собственно, потоки операционной системы не могут быть столь же эффективны, как и горутины? То есть почему разработчики OS тоже не могут сделать динамический стек и другие оптимизации?


Как ты это себе видишь на уровне ОС? Убрать относительно дорогие потоки не получится, хотя бы потому что уже 100500 программ их используют. Плюс у горутин (и других аналогов) есть свои ограничения и недостатки в дополнение к потокам они норм вместо них нет. И чем реализация на уровне ОС будет лучше библиотечной?
WBR, Igor Evgrafov
Re: Горутины и потоки
От: ononim  
Дата: 03.07.21 09:48
Оценка:
T>А почему, собственно, потоки операционной системы не могут быть столь же эффективны, как и горутины? То есть почему разработчики OS тоже не могут сделать динамический стек и другие оптимизации?
Что касается динамического стека тут уже написали. Что касается "других оптимизаций", то поток — это прежде всего контекст процессора. Поток операционной системы — это два контекста процессора — юзермодный и кернелмодный, чтоб их оба переключить требуется переход в кернелмод и обратно. "Другие оптимизации" aka green threads — это лишь юзермодный контекст, соответственно потоком операционной системы быть не могут. Но библиотеки, позволяющие реализовать такую функциональность для unmanaged кода существуют — начиная от реальных зеленых потоков (хотя я эту либу не юзал никогда) и заканчивая корутинами в плюсах. В принципе как часть АПИ тоже есть как минимум пример — т.н. fiber в винде но особой популярности они не получили.
Как много веселых ребят, и все делают велосипед...
Отредактировано 03.07.2021 9:55 ononim . Предыдущая версия . Еще …
Отредактировано 03.07.2021 9:49 ononim . Предыдущая версия .
Re[13]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 03.07.21 11:45
Оценка:
Здравствуйте, netch80, Вы писали:


N>>>Да, проблема есть (хотя про "основной источник проблем с производительностью" тут загнули, случай он разный бывает). Но вы никак от этого не избавитесь ни шедулингом, ни переходом на await, пока вам потребуется ровно та же синхронизация.

S>> Я тебе привел пример аналога LockAsync. Поток ничего не ждет, а по событию запускается из пула потоков. Вот пример асинхронной очереди
S>>AsyncProducerConsumerCollection
S>>Просто привел пример с использованием ValueTask вместо Task

N>Так с точки зрения ОС что поможет тут осознать, когда можно переключать на другую нить вообще другого процесса, а когда лучше этого не делать, потому что захвачен важный лок (и даже в случае асинхронных локов это блокирует другие задачи)?

N>Я б ожидал каких-то явных системных вызовов, но тут про них ни слова.
Ту тут главное tcs.TrySetResult(item);
И фцфше может продолжаться в этом же потоке в зависимости от условий создания TaskCompletionSource
http://rsdn.org/forum/dotnet/7639144.1
Автор: RushDevion
Дата: 20.01.20

var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);

https://docs.microsoft.com/ru-ru/dotnet/api/system.threading.tasks.taskcreationoptions?view=net-5.0#System_Threading_Tasks_TaskCreationOptions_RunContinuationsAsynchronously

N>>>Если так, то та же цена реально размазана по остальной работе: с данными в куче всегда дороже работать, чем с данными на стеке или тем более в регистрах: аллокация, GC где-то после, разыменование указателей (спрятанных в ссылках дотнетов), заметно худшее кэширование, потому что в стеке данные сидят плотно, и наверняка ещё и пара сотен байт вершины стека в кэшах процессора.

S>> Угу который при переходе на новый поток сбрасывается.

N>Кто сбрасывается? Кэш процессора? Зачем? Даже с мерами против Meltdown/Spectre кэш сбрасывается при переключении между процессами, а не между нитками одного процесса. Хотя о чём и, главное, чем думают тут в MS, я не в курсе.


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

S>>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?


N>Кто сказал, что "всё прекрасно"? Я не знаю, откуда и как вы делаете такие выводы.

N>И что это "все", что делают чтобы отказаться? В основном делают там, где просто неудобно/дорого по другим причинам возлагать это на ОС.
Асинхронность и производительность
Утверждают, что выхлоп есть

N>Да, есть цена на переключение, даже вход и выход из системного вызова чего-то стоит, и на Windows эта цена сильно выше, чем на Linux. Может, потому для Windows и стараются сократить эти переключения. В Linux я не вижу такого активного стремления, если где-то такое делают, то в рантаймах со сложной собственной логикой и полностью "управляемым" выполнением (Go, Erlang).


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

N>>>Не может быть квадратных кругов и круглых квадратов. Если с данными надо работать, то для этого нужен доступ к ним. Если нужен доступ, их надо прочитать из памяти и записать в память, с соответствующей ценой. Я бы предпочёл, чтобы компиляция хранила максимум в регистрах процессора, а что не помещается — максимум на стеке: не будет дурных потерь скорости. А если при этом само переключение формально дороже — тоже не страшно, записать компактно пару сотен байт и потом прочитать — эффективнее, чем размазывать их по десяткам кэш-строк.


S>>Ну класс если используешь Value типы тоже будут компактно расположены, а не валуе типы что на стеке, что не на нем все равно в куче


N>То есть в дотнет ещё не завезли escape analysis? (сомневаюсь)


S>>>>>>Конечно зависит от длительности задач, но если задачи непродолжительные, то пул потоков может и не переключаться а выполнять очередь заданий.

N>>>>>Тоже не понимаю, при чём тут эта реплика.
S>>>> В том, что с использованием пула потоков сводит к минимуму переключение потоков

N>>>Так не от желания конкретной задачи тут зависит, а от того, будет ли тот лок свободен.

S>>Вот ссылочка из предыдущего сообщения AsyncSemaphore

S>>Если lock занят то возвращается Task и ожидается пока не вызовется SetResult

S>>Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock

N>Почему это "нужно заменять"? Обычный lock настолько неэффективен?

Обычный lock может быть долгим и ожидать его могут тысячи потоков.
Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?
и солнце б утром не вставало, когда бы не было меня
Re[14]: Горутины и потоки
От: Sharov Россия  
Дата: 03.07.21 12:13
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?


Для типизации тасков/промисов .
Кодом людям нужно помогать!
Re[15]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 03.07.21 14:22
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Здравствуйте, Serginio1, Вы писали:


S>>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?


S>Для типизации тасков/промисов .


Тут речь про потоки и бесплатность их переключения. По сути раньше синхронный код и использованием ожиданием эвента при вызове асинхронной операции была нормальной практикой.
Но было сказано, что это отстой ибо есть большие затраты на переключение потока. Поэтому лучше использовать пул потока с минимумом переключений.
Но в этой ветке утверждают, что это все херня ибо все очень быстро и использование потоков и хэндлов не проблема и стоимость переключения минимальны.
и солнце б утром не вставало, когда бы не было меня
Re[16]: Горутины и потоки
От: Sharov Россия  
Дата: 04.07.21 00:13
Оценка:
Здравствуйте, Serginio1, Вы писали:
S>>Для типизации тасков/промисов .

S> Тут речь про потоки и бесплатность их переключения. По сути раньше синхронный код и использованием ожиданием эвента при вызове асинхронной операции была нормальной практикой.

S>Но было сказано, что это отстой ибо есть большие затраты на переключение потока.

Вероятно это связано с многоядерностью ... Хотя не факт -- ведь в чем разница для async\await от ядерности?
Т.е. мы четко типизируем "выполни это после этого (io операции какой-нибудь)" и нам ядерыные объекты синхронизации
тут ни к чему.

S>Поэтому лучше использовать пул потока с минимумом переключений.


Пул потоков это про создание потоков, а не про переключение.
Кодом людям нужно помогать!
Re[14]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 04.07.21 05:18
Оценка: 12 (1)
Здравствуйте, Serginio1, Вы писали:

N>>Так с точки зрения ОС что поможет тут осознать, когда можно переключать на другую нить вообще другого процесса, а когда лучше этого не делать, потому что захвачен важный лок (и даже в случае асинхронных локов это блокирует другие задачи)?

N>>Я б ожидал каких-то явных системных вызовов, но тут про них ни слова.
S>Ту тут главное tcs.TrySetResult(item);
S>И фцфше может продолжаться в этом же потоке в зависимости от условий создания TaskCompletionSource

Как по мне, это мало того, что имеет очень слабое отношение к теме дискуссии, так и вообще какое-то кошмарное безумие. На чём тут дедлочиться??
Ну, стал родитель в ожидание. Пошёл запускаться код из вложенной задачи. Ну поспал (Thread.Sleep асинхронный?) Поставил результат. Родитель проснулся. Что может пойти не так? Что за бардак там в дотнете, или C#, кто это строит?

Я вот хотел сделать (на Core 5.0) чтобы коллбэки на чтение сокета срабатывали всегда в нужной нитке. Не получается: мне их, фактически, принудительно загоняют в какой-то пул, который я не просил создавать, и я должен ещё думать о мьютексах, чтобы применить их данные. А тут, наоборот, какая-то самоблокировка на одной нитке непонятно на чём.

И при чём тут какой-то TrySetResult? Try на ожидание — я понимаю, а на SetResult — нет. Мы заданием результата для Task снижаем "напряжение" в системе, сокращая количество ожидаемых сущностей и сокращая contention за ресурсы (включая ожидания), тогда какой нафиг Try?

N>>Кто сбрасывается? Кэш процессора? Зачем? Даже с мерами против Meltdown/Spectre кэш сбрасывается при переключении между процессами, а не между нитками одного процесса. Хотя о чём и, главное, чем думают тут в MS, я не в курсе.


S>Ну кэш то первого уровня точто сбросится ибо слишком мал. И все зависит от используемых данных потока.


Он не _сбросится_. Он _вымоется_. Разница принципиальна. И почему ты думаешь, что они будут использовать настолько разные код и данные?

Я вон выше уже упоминал: открыл сокет, повесил коллбэки на приём датаграмм. Послал две датаграммы. Один коллбэк вызвался в нитке 6, другой — в нитке 9 (дотнет нумерует). Зачем?

S>>>У меня вопрос если все так прекрасно с переключением потоков, то зачем делают все, что бы отказаться от потоков и использовать очередь потоков?


N>>Кто сказал, что "всё прекрасно"? Я не знаю, откуда и как вы делаете такие выводы.

N>>И что это "все", что делают чтобы отказаться? В основном делают там, где просто неудобно/дорого по другим причинам возлагать это на ОС.
S>Асинхронность и производительность
S>Утверждают, что выхлоп есть

По сравнению с чем? Там сплошная Обстракция(tm) без конкретики.
Ну, повторюсь, на Windows треды дорогие (и создание, и переключение). Вероятно, по сравнению с этим, действительно, польза async-await очевидна.

N>>Да, есть цена на переключение, даже вход и выход из системного вызова чего-то стоит, и на Windows эта цена сильно выше, чем на Linux. Может, потому для Windows и стараются сократить эти переключения. В Linux я не вижу такого активного стремления, если где-то такое делают, то в рантаймах со сложной собственной логикой и полностью "управляемым" выполнением (Go, Erlang).


S> Ну сейчас практически все сервера в облаке на линуксе, но там же и asinc await используются на прополуюю ибо основная то работа это запрос к базе данных и прочие асинхронные операции


Возможно. А меряли результат по сравнению с другими подходами?

S>>>Если lock занят то возвращается Task и ожидается пока не вызовется SetResult

S>>>Никакой остановки потока нет. Да внутри конечно есть короткий lock который нужно заменять на SpinLock

N>>Почему это "нужно заменять"? Обычный lock настолько неэффективен?

S>Обычный lock может быть долгим и ожидать его могут тысячи потоков.

Эээ...
1. Если у кого-то на "обычном локе" тысячи ниток, то тут в первую очередь нужно менять весь дизайн (может быть, даже архитектуру, в обычном понимании).
Любые локи предназначены для малого количества конкурентов за их ресурс. Даже по количеству хартов — это уже много. До 4 — нормально. Если выше — надо думать разделять на разные локи. Если выше 8 — срочно думать.
2. Я уже описывал работу адаптивных мьютексов. Вначале, да, цикл сколько-то попыток захвата в стиле спинлока — чистым CAS (если нет видимой очереди). Если они не получились, тогда уже начинать более серьёзную машину — или через await, или через Lock() в ядре.
Но противопоставлять спинлоки мьютексам и говорить про "эффективность" spinlock по сравнению с мьютексом — это просто смешно.
Спинлок заведомо менее эффективен на длительных периодах и большом количестве конкурентов. Есть случаи, когда его использование неизбежно — когда шедулера более высокого порядка просто нет (в первую очередь это борьба в ядре за общие ресурсы между разными хартами). Но в остальных случаях его использовать — только ухудшать.

S>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?


1. "Во-первых, это красиво" (tm). Линейное написание кода в разы проще цепочек коллбэков, неважно, в явном виде или на промисах.
Если программист не справляется без await, он будет писать линейно, но будут сплошные блокировки на синхронных операциях. Await позволяет использовать широкие ресурсы кодеров с реальным улучшением характеристик именно за счёт этого — меньше блокировок.

2. При высокой цене переключения между нитками внутреннее переключение (шедулером на границе awaitʼа) будет в разы дешевле переключения через ОС.
The God is real, unless declared integer.
Re[16]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 04.07.21 05:18
Оценка: +1
Здравствуйте, Serginio1, Вы писали:

S>Но было сказано, что это отстой ибо есть большие затраты на переключение потока. Поэтому лучше использовать пул потока с минимумом переключений.

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

Если это "утверждают" про меня, я не говорю, что "херня", "стоимость переключения минимальна" и т.п. — я, наоборот, утверждаю, что для Windows она весьма высока (сам не мерял, данные "из интернетов"), и использование всех этих фокусов там может быть очень оправдано. Но — описываемые тобой цели типа "не переключать пока держится лок" автоматически этим не реализуются, и могут быть вместо этого только ухудшены, если нет явной подсказки ядру.
The God is real, unless declared integer.
Re[17]: Горутины и потоки
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 04.07.21 05:19
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Вероятно это связано с многоядерностью ... Хотя не факт -- ведь в чем разница для async\await от ядерности?


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

S>>Поэтому лучше использовать пул потока с минимумом переключений.


S>Пул потоков это про создание потоков, а не про переключение.


Про переключение тоже — их ведь больше одного
The God is real, unless declared integer.
Re[15]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 05.07.21 07:57
Оценка:
Здравствуйте, netch80, Вы писали:

N>Эээ...

N>1. Если у кого-то на "обычном локе" тысячи ниток, то тут в первую очередь нужно менять весь дизайн (может быть, даже архитектуру, в обычном понимании).
N>Любые локи предназначены для малого количества конкурентов за их ресурс. Даже по количеству хартов — это уже много. До 4 — нормально. Если выше — надо думать разделять на разные локи. Если выше 8 — срочно думать.
N>2. Я уже описывал работу адаптивных мьютексов. Вначале, да, цикл сколько-то попыток захвата в стиле спинлока — чистым CAS (если нет видимой очереди). Если они не получились, тогда уже начинать более серьёзную машину — или через await, или через Lock() в ядре.
N>Но противопоставлять спинлоки мьютексам и говорить про "эффективность" spinlock по сравнению с мьютексом — это просто смешно.
N>Спинлок заведомо менее эффективен на длительных периодах и большом количестве конкурентов. Есть случаи, когда его использование неизбежно — когда шедулера более высокого порядка просто нет (в первую очередь это борьба в ядре за общие ресурсы между разными хартами). Но в остальных случаях его использовать — только ухудшать.

S>>Ну вот я и хочу разобраться если переключение потоков так эффективно, то нахрена городить async await?


N>1. "Во-первых, это красиво" (tm). Линейное написание кода в разы проще цепочек коллбэков, неважно, в явном виде или на промисах.

N>Если программист не справляется без await, он будет писать линейно, но будут сплошные блокировки на синхронных операциях. Await позволяет использовать широкие ресурсы кодеров с реальным улучшением характеристик именно за счёт этого — меньше блокировок.
+
N>2. При высокой цене переключения между нитками внутреннее переключение (шедулером на границе awaitʼа) будет в разы дешевле переключения через ОС.
Вот спасибо! Все таки дешевле!
Ну давай возьмем типичный сервер. БОльшая часть задач сводится к вызову асинхронным методов а именно запрос к базе данных (не локальной), запрос к сайту, к файлу итд.
Выделять для каждого запроса отдельный поток не имеет смысла (тут и затраты на создание потока или держать пул огромного размера).
Как правило эти задачи небольшие.
Отдельный поток стоит выделять для длительных задач LongRunning

То есть смысла в выделении потоках на задачу нет. Есть смысл использовать пул потоков. Что было кстати в ранних версиях вэб сервисов ASP.Net.
Тогда тоже были и асинхронные делегаты (BeginInvoke() и EndInvoke()) и ThreadPool. Но в том же сервисе вызов вэб метода был синхронным, то есть привязан к одному потоку. То есть внутри то можно было использовать калбеки и прочее, но поток морозился на эвенте

То есть например когда мы в базе данных что то активно меняем, и запросов на чтение как раз и будут ждать пока не закончится запись.
Конечно можно по максимуму съузить блокировки, но не всегда это возможно.
и солнце б утром не вставало, когда бы не было меня
Re[15]: Горутины и потоки
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 05.07.21 08:08
Оценка: 6 (1)
Здравствуйте, netch80, Вы писали:


N>Я вот хотел сделать (на Core 5.0) чтобы коллбэки на чтение сокета срабатывали всегда в нужной нитке. Не получается: мне их, фактически, принудительно загоняют в какой-то пул, который я не просил создавать, и я должен ещё думать о мьютексах, чтобы применить их данные. А тут, наоборот, какая-то самоблокировка на одной нитке непонятно на чём.


Ну тебе в данной нитке нужно организовать очередь и пихать в неё свои калбеки. Поток опять же ставить на ожидантие и при постановке в очередь выставлять эвент в сигнальное состояние.
Либо использовать контекст синхронизации
https://docs.microsoft.com/ru-ru/dotnet/standard/parallel-programming/how-to-specify-a-task-scheduler-in-a-dataflow-block

Кстати про шедулер и сравнением с GO
https://habr.com/ru/post/336000/
и солнце б утром не вставало, когда бы не было меня
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.