Обновить
87
7
Александр@deniaa

Пользователь

Отправить сообщение

Ваня, привет. Случайно это получилось или ты специально акцентируешь внимание на этом, но реализация OTel в Дотнете (или, наверное, правильнее сказать реализация Activity) устроена примерно так же, как ты и описал. На твой вопрос есть несколько ответов.

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

Во-вторых, в LinkedList действительно придётся аллоцировать каждую ноду. А в случае ImmutableArrayDictionary мы не аллоцируем ноду на каждое добавление свойства: мы аллоцируем только массив (а хранятся в нём структуры). Строго говоря, однозначно утверждать "вот этот способ лучше" тут нельзя без разбора большого числа случаев и сценариев. Но я могу сказать, что осуществляя миграцию с вот этой реализации распределённой трассировки на встроенную в Dotnet реализацию OTel несколько реальных приложений не было замечено, что какая-то из них сильно превосходит другую, разница была в любую сторону в пределах погрешности.

В-третьих, интерфейс LinkedList очень удобен в той архитектуре Экспортёров, которая заложена в Дотнете. А Array-like интерфейс очень удобен в той архитектуре Экспортёров, которая заложена в представленном фреймворке Vostok. Я не буду вдаваться в детали, скажу лишь, что если оставить ImmutableArrayDictionary под капотом и сувать их в Dotnet-экспортёры, они будут делать лишнюю работу при некоторых цепочках кастомизации и переопределении заполнения свойств. И наоборот, если взять LinkedList и сувать его в Vostok-экспортёры, они будут делать лишнюю работу при некоторых цепочках кастомизации и переопределении заполнения свойств.

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

Не знаю, как случилась эта опечатка. Да, должно быть `needToStopEnumeration = false`. Спасибо, поправил в статье.

Очень философское наблюдение :)

Думаю, что-то в этом есть (в глобальном смысле).

Безусловно, все именно так. И весь этот блог пестрит примерами некрасивого кода, который эффективнее по бенчмаркам ;)

Реальность намного сложнее, чем синтетический пример. Для этого статья и начинается со слов, что мы лишь попытаемся «продемонстрировать».

На практике же действительно надо искать баланс стоимости поддержки кода (в том числе читабельность) и производительности, в зависимости от ситуации, с применением бенчмарка. А готовых рецептов тут не существует. Пытаться раздавать советов «как лучше» - плохое занятие.

промазал ответом на комментарий выше

Такой вывод, конечно же, имеет место быть.

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

Строго говоря, ни тот ни другой вывод не будут корректны на 100%. Можно так постараться с красивым кодом, что станет только хуже (чаще, неверное, так и происходит). И можно положиться на силу компилятора там, где на самом деле он тебе не поможет.

Всё так. В реальном мире это почти всегда не существенно. Task.Run - это так, для "формальной корректности и чистоты".

Вам только кажется :)

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

Несомненно, написать на C++ код, который выполняет длительную CPU-bound работу с ограниченным уровнем параллелизма, можно очень легко и просто. Я даже соглашусь, что это может быть проще, чем на C#.

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

Статья о C# и .Net. Она о примитивах дотнета и их особенностях. Она показывает, как те или иные синтаксические конструкции сказываются на работе приложения.

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

Замечание про Task.Run (или Task.Yield(), что я менее предпочитаю) валидное. Спасибо за дополнение.

Короткий ответ - да.

Ответ посложнее - ожидание Lazy.Value до сих пор осталось синхронным. Вот только мы теперь синхронно ждём не всей длительной CPU-bound работы по сериализации дерева, а только создания Task (что почти мгновенно). И только затем все асинхронно ждут await task, да, не занимая потоки тредпула бессмысленными ждунами. (И ExecutionAndPublication здесь всё ещё нужен, чтобы случайно не создать два Task, оба из которых будут брать единицу параллелизма из семафора и сериализовывать одно и то же дерево.)

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

Возможно, было бы чуть сложнее ухватиться взглядом за важные детали из-за другой визуализации.

В следующий раз обязательно попробую. В этих штуках с UI обязательно найдётся какая-нибудь удобная кнопка, о которой ты не знал :)

Спасибо за внимательность! Ссылку добавил.

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

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

При переносе статей мы (DevRel-ы администрируют и помогают с переносом на Habr, за что им спасибо) учитываем их топологическую сортировку, если они явно ссылаются друг на друга.

В данном случае во внутреннем блоге эта статья была выпущена раньше, чем статья про DOTNET_ThreadPool_UnfairSemaphoreSpinLimit. Из-за чего в оригинале такой ссылки не могло быть. А для переноса этой статьи потребовалось приложить больше усилий, чем для статьи про DOTNET_ThreadPool_UnfairSemaphoreSpinLimit (здесь было много внутрянки). Поэтому их порядок изменился.

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

Спасибо за более точное описание различия.

Спасибо за отличное дополнение!

Несомненно, если выбирать язык под задачу с требованием иметь фокус на производительность - выбирать C# не стоит.

Но это не значит, что на C# нельзя писать эффективные и высоконагруженные приложения - ещё как можно.

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

Например, мотивация для таких задач может быть сугубо экономическая. На определённом масштабе кластера даже из C#-приложений могут достигать сотен инстансов (а то и больше). И пара недель работы инженера над оптимизацией такого приложения на условные 10% потребления ресурсов могут сполна окупиться. При этом, рост до такого масштаба - это не повод переписывать приложение на условный C++.

 применять энергии исследований лучше на специальных областях

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

Производительность LINQ сильно привязана к generic-типу коллекции, типу самой коллекции, с которой происходит работа, и к собственно самой функции вызова. Ведь LINQ это не просто перечисление, это и какая-то полезная нагрузка.

Там, например, на .Net7 .Sum() на массивах чисел работает с помощью SIMD. А на каком-то сложном IEnumerable - просто складывает влоб. А в нашем случае сложение выбрано как относительно нейтральная и бесплатная полезная нагрузка. (Будь у нас какой-нибудь супер-умный компилятор, как у C++, то он бы тоже мог заметить возможность переписать код с использованием SIMD). Кстати, это и не только обсуждалось в предыдушей статье про reciprocal throughput (и отдельного внимания там заслуживают ветки в комментариях, например вот эта).

Поэтому рассматривать производительность LINQ в сравнении с перечислением foreach и циклом for в отрыве от функции и типа коллекции просто неправильно. Такое исследование, конечно, интересно, но скатится в перечисление огромного числа случаев. Больше пользы можно извлечь из чтения патчноутов: что конкретно в LINQ в новом .Net'е улучшили.

Рекомендую ознакомиться со всеми статьями целиком, но я приведу ссылки на абзацы именно про sealed-классы:

https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/#peanut-butter

https://devblogs.microsoft.com/dotnet/performance_improvements_in_net_7/#analyzers

Ну вот я и не считаю что эта статья должна быть в блоке про пул потоков.

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

В данном случае лично я ощущаю недогруженность.

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

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

Информация

В рейтинге
816-й
Работает в
Зарегистрирован
Активность