Как стать автором
Обновить
24
0
Николай @NikolayPyanikov

Программист

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

Реализация Owned выглядит примерно так:

class Owned : IOwned
{
    private List<IDisposable> _disposables = List<IDisposable>();
    
    public void Add(IDisposable disposable)
    {
        if (!(disposable is Pure.DI.IOwned))
        {
            _disposables.Add(disposable);
        }
    }
}

Его производительность почти полностью зависит от производительности List<T>. Я замерял, но под рукой цифр нет - довольно быстрый))

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

  • так как дорогих ресурсов не должно быть много

  • если накапливать много, то могу быть проблемы с памятью

Вы всегда можете заменить его на свой. Для этого зарегистрируйте свою реализацию накопителя, например MyOwned:

Accumulate<IDisposable, MyOwned>(
  Lifetime.Transient,
  Lifetime.PerResolve,
  Lifetime.PerBlock)

И используйте его. При этом если Owned из API Pure.DI не будет использован (не будет внедрен где-то), то и накапливать он ничего не будет и даже не будет создан. А будет работать только ваш.

В этом случае

void Setup() => DI.Setup(nameof(Composition))
  .Root<FormMain>("FormMain")
  .Bind<IClockViewModel>().As(Singleton).To<ClockViewModel>()
  .Bind<IClockViewModel>().As(Singleton).To<ClockViewModel>();

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

Composition.cs(23,51): Clock.ViewModels.IClockViewModel has been overridden.

т.е. забыли Bind для IClockViewModel

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

Например, закомментируйте эту строку. Ошибка компиляции будет такой:

FormMain.cs(11, 39): Unable to resolve "Clock.ViewModels.IClockViewModel" in FormMain(Clock.ViewModels.IClockViewModel clockViewModel<--Clock.ViewModels.IClockViewModel)).

Если в IDE перейти по ошибке то он укажет на параметр IClockViewModel clockViewModel в конструкторе класса FormMain. Т.е. программа конечно не скомпилируется и бинарника, который можно запустить у вас не будет.

Реализовать не сложно, пара функций это сбилдить dll из исходника и этот dll загрузить

Нужны и все зависимости

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

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

Не много не в тему, но интресно узнать, а какая у вас мотивация заниматься open source проектом подобным этому?

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

Глядя на функциональность Incremental Generators, выглядит так что было бы вполне легко реализовать что-то типа greasemonkey для браузеров.

Такой подход будет сложно реализовать, так как анализируется не только исходный код, но и все бинарные зависимости. Плюс слишком много событий когда нужно будет перестраивать код –будет не удобно. Я не уверен, что до конца понял вашу мысль про greasemonkey.

Хотел одно время написать, но с приходом этой движухи с ИИ не понятно, нужно ли вообще суетиться.

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

Спасибо за интерес))

Да, это генератор кода на основе .NET Incremental Generators и идея Pure.DI похожа на Jab, но много различий в деталях.

  • Jab – это все вокруг IServiceProvider, Pure.DI не привязан ни к чему

  • Jab – использует атрибуты для метаданных, в Pure.DI использует и атрибуты, но самое главное основной API Pure.DI похож на обычные DI контейнеры

  • В Jab на сколько я понял каждую зависимость нужно регистрировать атрибутом, что не очень удобно, в Pure.DI нужно определить только корни или привязки абстракций и все работает само

  • В Jab при построении композиции объектов везде используются вызовы GetService<T>(), это не хорошо по производительности

  • В связи с подходом из предыдущего пункта нет эффективного способа реализовать такие времена жизни PerResolve или PerBlock, которые очень удобны

  • В Jab нет эффективных фабрик как такое

  • Те фабрики что есть - не очень эффективны, например вот такие фокусы сделать невозможно

  • В Jab плохая работа с обобщёнными типами

  • Он не потокобезопасен, на сколько я понял

  • Нет множества полезны фич такие как аргументы композиции/корней

  • Нет возможности сделать лямбды с параметрами как такое

  • Нет настроек генерации кода как такие подсказки

  • Я не понял как там делать перехват как это или декоратор как этот

  • Я не понял, как там внедрять реализации по ключу - может быть не доделано еще

  • Там не поддерживаются BCL (Lazy, IList ...) и другие типы из коробки, я уже и не говорю про создание Span на стеке))

  • Нет возможности из коробки внедрять Func<> что бы создавать столько инстансов сколько нужно

и много другого

Jab - это генератор DI "на минималках", но многим его возможностей вполне достаточно, мне нет))

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

??? Создать словарь на основе чего? На основе записей которых ещё нет? Или как-то предугадать то что будет?

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

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

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

На мой взгляд основная проблема большого количества данных это не оптимальный способ передачи и хранения этой инфы. Сейчас логи это обычные строки. Причём с очень большим дублированием. Есть очевидный и очень простой способ значительно повысить производительность работы с логам и снизить затраты на хранение - заменить неизменяемые части шаблонами, а изменяемые хранить не в виде строк, а в виде бинарных данных, параметров этого шаблона. Например лог вида "Service Abc is started." сейчас занимает 23 байта при передаче в utf8 и в 2 раза больше в памяти. Если использовать шаблон "Service _ is started." То при хранении и передаче это будет какой то ID шаблона например 2 байта + параметр: строка Abc - 3 байта в utf8. Всего 5 байт. Шаблон может включать сам текст и метаданные для того что бы склеить их в привычные строки только перед их просмотром. Профит - меньше памяти, меньше трафика, быстрее поиск.

Кажется технологии работы с логам застряли где-то в 90х прошлого столетия.

Я не хочу с вами спорить и что-то доказывать. Просто читайте документацию, можно начать с этой.

Сейчас разбираю как работает https://github.com/VictoriaMetrics/VictoriaMetrics, и мне кажется, что тут все очень зависит от разработчика. Думаю, код на .NET/Java/Kotlin можно было бы сделать и проще и не менее эффективным. Про надежность и тесты точно не хуже ))

Ссылка - атомарна, так как влазит в регистр CPU. Если в первом if код поймет что значение уже есть в переменной то поток просто пойдет дальше. Если там будет пусто или не успели его сбросить из регистров CPU, то возмется lock, при этом значения регистров запишется в память и _random будет содержать актуальное значение перед вложенным if. Внутри lock и if будет создан новый экземпляр и он гарантированно и атомарно попадет из регистра CPU в память, так как после lock все регистры CPU сбросятся в память. Реордерить внутри lock тут нечего.

Если бы внутри lock была, например, дополнительная логика по инициализации переменной _radom, тогда ее нужно было бы делать над временной переменной, например _randomTmp, потом ставит барьер, а потом записывать в _random = _randomTmp. Так как, во первых, в _radom могла попасть "недоинициализированная" переменная, во вторых, иструкция _random = _randomTmp на ia64 могла быть сделана ранее окончания "инициализации". Этот сценарий учтен.

Для valued типов ситуация иная. Там используется флаг, например как `_isRandomCreated` и барьер после `_random = new ()` и перед тем как сделать `_isRandomCreated = true;`

Тут нет смысла использовать Volatile.Read, так как кэш CPU не будет использоваться для чтения поля _random после lock - чтение будет из памяти. _Volatile.Write_ так же нет смысла тут использовать, так как нет инструкций для перестановки внтури lock, а после lock все содержимое кэша CPU будет перенесено в память.

На x86 и x64 volatile - избыточно. В общем случае, volatile тут необходим из за риска реордеринга инструкций на Itanium 64. С Full .NET 2.0 данный сценарий учтен и проблем нет, но этого нет в спецификации ECMA, которая позволяет реордеринг, если нет проблем с одним потоком. В нашем слуае для Random мы не используем дополнительное поле типа _isInitalized для того что бы определить выполнена ли инициализация и реордеринг не ломает ни чего, но для структур оно используется. Генератор кода не работает с Full .NET 1.0 и 1.1, т.е. даже если сейчас нет volatile - проблем нет, но на будущее я это исправил в соотвествии ECMA.

В случае с Random объект является инициализированым после вызова конструктора. И другой поток получит доступ к объекту готовому к использованию:

if (object.ReferenceEquals(_random, null))
{
  lock (_disposableSingletons)
  {
    if (object.ReferenceEquals(_random, null))
    {
      _random = new Random();
    }
  }
}      

return (State)random.Next(2);

Если бы после вызова конструктора выполнялась "доинициализация", например какие то свойства определялись, тогда генератор создает временную переменную, которая решает проблему объекта, который "не готов".

В случае временной переменной оптимизатор просто сразу заменит её на целевую и "смотри пункт 1".

Есть доказательства этому? Мне кажется вы сочиняете.

Вот эта мысль давно не покидает голову :) Учитывая масштаб использования DI, пора бы обеспечить поддержку на уровне платформы и даже языка. При такой поддержке, можно обеспечить максимальный уровень оптимизации и контроля.

А пока языки "тырят" друг у друга синтаксический сахар, имеем что имеем ((

Если ничего ощутимо не поменяется, а выигранные наносекунды на общей картине просто не будут даже заметными, стоит ли игра свеч?

Не все проекты использую MS DI, да их большинство, но не все. Много людей например пишут игры на Unity. Другой сценарий - когда модули могут реализовывать свой функционал используя internal типы, а наружу выставляют несколько public интерфейсов, несколько public DTO и "точку" входа в виде какого-нибудь статик метода. Далее эти интерфейсы уже регистрируется в качестве зависимостей в каких-то DI. Так вот эти модули могут использовать Pure.DI. Какие-либо зависимости не добавятся и все будет работать максимально эффективно и по CPU и по памяти. Я, используя Pure.DI сам, в том числе и в высоконагруженных сервисах и этот подход прекрасно себя показывает. Причем я не заморачиваюсь: что мне создавать через оператор new, а что через DI – все делаю через DI.

Спасибо за интерес!

Спасибо за отзыв и интересные мысли!

1) По производительности «подходов» ускорение, фактически, на порядок и на мой взгляд это не мало. И больше сделать не получится по сравнению с обычным созданием объектов. Можно спокойно сказать, что сделано максимум возможного. И там где вы не использовали раньше DI теперь его можно, и даже нужно, использовать. Если в какой-то момент вы отказываетесь от использования DI для создания объектов, то преимущества от применения сильно DI уменьшаются. «Кастомная sinleton-фабрика» как раз это случай. Pure.DI позволяет избежать таких компромиссов и использовать DI абсолютно везде, даже в самом горячем «коде» и при переходе «на стек».

Pure.DI можно использовать совместно с IServiceCollection. Есть и альтернативы, которые полностью заточены на MS DI, например stronginject. Опять же IServiceCollection это решение изначально близкое к ASP.NET. Есть огромное количество ниш, где IServiceCollection не используется. Я не в коем случае не предлагаю переписывать решения, которые используют IServiceCollection или другие IoC контейнеры, я предлагаю подход, который может быть где-то полезен. Например, Pure.DI может быть полезен при создании игр, библиотек и кода критичного к потреблению ресурсов или когда планируется иметь очень большие и сложные графы зависимостей и требуется особая аккуратность при добавлении новых.

2) По поводу «валится в рантайме сразу». Любой сбой может иметь неприятные последствия. Неудачно разрешение зависимостей во время выполнения может происходить в НЕ частых, но важных, сценариях. Но последствия будут не менее неприятными.

Обычно много пишется модульных тестов, которые тестируют класс в изоляции. Интеграционные тесты пишутся на базовые сценарии (пирамида тестирования), они могу не выполнить нужные проверки.

3) Про жечь мосты полностью с вами согласен. Но не кажется ли вам что повсеместное использование контейнеров для внедрения сжигает мосты по отношению к чистому внедрению зависимостей? История обычно развивается по спирали. Было чистый DI, стали использовать контейнеры, можно вернуться к чистому DI и не потерять удобные вещи из контейнеров. Надеюсь, на каком-то этапе ответственность за внедрение зависимостей на себя возьмет язык и виртуальная машина.

Мне нравится Java и Kotlin, но я хочу, чтобы и в .NET было все ок в этом плане. Почему у JVM есть свой Dagger/Yatagan, а .NET подобных не должно быть? :)

 

Информация

В рейтинге
Не участвует
Откуда
Санкт-Петербург, Санкт-Петербург и область, Россия
Дата рождения
Зарегистрирован
Активность