Pull to refresh
6
Хохлов Игорь@pladar

Fullstack

6
Subscribers
Send message
Если использовать словарь, то мы не LINQ сравниваем с его отсуствием и не компилированный делегат с динамической рефлексией, а оптимально написанный метод и не оптимально написанный метод. Такое сравнение глупо, потому что результат предопределен. Доступ к элементу через хештейбл словаря намного быстрей, чем доступ через итератор по порядку, особенно при больших последовательностях. Вы сейчас просто начали переписывать код и сообщать, что он работает быстрей.
Он и будет.
Вопрос был в другом — LINQ если писать его безопасно, не рискуя наткнуться на гиперитерации или извлечение всей таблицы в память, зашумляет результат так, что разницы просто не заметно.
Нет, это не то же самое. Я логику понял. Доступ к членам заменяется на доступ к словарю по ключам. Тут словарь надо ставить конкурентный, но это мелочь.

Приведенная реализация не позволит добиться тех же результатов, что и рассматриваемая в статье — конечный тип не будет типом Contract. Из очевидного — его нельзя будет передать в типобезопасные методы. Так же нельзя будет сохранить его в БД с помощью DB контекста как другую валидную энтити. Ближайшее, что можно сделать — сунуть его в автомаппер, с надеждой что тот перемаппит этот динамический объект в нужную нам энтити, но по сути автомаппер сделает то же, что предлагаю делать я. Соответственно, замерять производительность нет смысла.

А проблема Expression'ов еще и в усложненном дебаге. Статья рассматривает конкретный аспект и не навязывает использование рефлексии. Но если её использовать. Было бы неплохо идти до конца и компилировать результат.
Первое — сравнение строк в верхнем регистре — это по сути и есть эквалс, но! Вам следует на будущее учитывать, что EF не понимает Equals, и извлечение из бд нужно делать по оператору ==. Если будете ставить Equals, EF не сможет транслировать код в SQL. Посмотрите по истории коммитов, там сперва был Мок DB Context'a EF Core. То, что Вы видите — суть артефакт от него. Как и ToArray(). В исходной реализации тут вызывался ToArrayAsync().

Кроме того, ToArray() — это важный вызов. Фактически, в этот момент вызывается LINQ-овский Deffered Execution. Если его не делать, LINQ возвратит IEnumerable, который в себе сохранил ноды выражений, встроенные в одно дерево и если вынешний код этого не учитывает, а работает с коллекцией как с коллекцией в памяти, можно словить лишнее итерирование, и оно вообще производительность убьет. Поэтому ToList() и ToArray() вызываются не редко в конце цепочки, это факт.

В остальном, не было цели оптимизировать код, была цель сравнить влияние двух подходов к отражению на фоне иной логики. После обнаружения бага не весь код был переписан с выпиливанием артефактов, за это прошу меня извинить.
Ну почему? Я переписал её, восстановив часть исходного смысла. Так выводы сделать можно. Я думаю. Во-первых, LINQ зашумляет до полной незаметности влияние рефлексии, а LINQ там относительно не много. Во-вторых, даже не считая IO, на фоне остальной работы рефлексия вредит не так сильно, и в-третьих, пример оптимизации все равно наглядно демонстрируется и реализует свою задачу. Понимаю, что целостное восприятие три раза переписанного не самая простая задача, но такова наша жизнь — диалектическое воспритие меняющейся системы в нескольких её состояниях. #мыжпрограммисты.
Тут выигрыш за счет того, что Вы оптимизируете доступ к элементам, а не гидрацию, что было примером статьи. Фактически, LINQ сильно зашумляет результат. За ним иной разницы мало заметно. Выше с пользователем Dmitry Tikhonov 0x1000000 мы установили реальную причину таких результатов.
Да, я уже заапдейтил результаты ретеста в статье. Спасибо большое.
Суть результата все равно осталась прежней. Вся ересь, которая реально используется в работе нормального приложения в значительной мере подавляет эффект от разницы между динамическим сеттером и скомпилированным.
Результаты и правда другие. Судя по всему, когда я проверял работу без Moq, я упустил какой-то другой фактор и это исправление не сказалось.

Статью придется переписать еще раз. Большое спасибо за помощь.
Нет, мне извлекает схемы маппинга EF Core из DbContext'а.
Я про оптимизацию при работе с БД ни слова не говорил, я отвечаю просто на изначальный вопрос о сравнении по ключам.
Да, без LINQ работать будет быстрей гораздо + меньше мусора.
Но это внесет шум в замеры. Там так вызодит, что на фоне LINQ вообще трудно заметить что-то остальное.
А Вы правы. Сейчас откалибруюсь.
Это не должно быть проблемой — я проверял с возвратом инициализированной коллекции напрямую без Мокью. Не было разницы.

Кроме того, я не совсем понял ход Ваших мыслей. Почему возвращается null?

Впрочем, я проверю.
И действительно. Никакого. Артефакт изначальной реализации, когда я хотел итерировать по свойствам, а из словаря извлекать по ключу. Но потом забыл поменять. )
Я проверял — разницы нет. По сути, там и с обычным словарем будет поведение потокобезопасное, потому что в него ничего не пишется.
Надо разбирать тогда пошагово. Возможно та разница, которая видна у Вас, в моем случае просто зашумляется остальной работой.
Можете загрузить мои исходники и проверить самостоятельно. Найдете ошибку — все вместе будем знать.
Могу только предположить, что PropertyInfo.SetMethod.Invoke работает медленней, чем SetValue или кешируется иначе самой CLR.
А в целом, я тоже считаю свой результат странным. Но для меня было важно именно SetValue проверить против скомпилированного делегата с обращением напрямую к сеттеру.
Первый пункт — мы выносим сопоставление типов по строкам из конечной логики в логику инициализации, сопоставляя при чтении из бд и маппинге строковые значения объектам PropertyInfo в DTO. Этих объектов в общем случае больше, чем реальных пропертей, потому что на одно свойство может быть N маппингов. Тут мы делаем много лишней работы, тем более что строку к PropertyInfo просто так, без кода резолва SqlDataReader не приведет. Это новая логика, новая работа, новый мусор. Я согласен, что это имеет смысл, если за один парсинг перебирается условно много писем, а самих маппингов в бд условно мало на каждое свойство. Но что будет работать быстрей — надо устанавливать эмпирически.

Второй пункт — жертва паттерну, а с Вашей точкой зрения я согласен. Там сначала и был словарь, который я убрал в конечной реализации и заменил на корреляционные пары. Dictionary<string, string> в шаблонном методе делает достаточно трудным восприятие контекста, в котором надо переопределять метод в наследнике. Если бы Вы не обратили внимание на утечку производительности тут, двое других читателей указали бы на некрасиво реализованный паттерн. Мне показалось второе более постыдным. Только поэтому я сделал так. Я считаю, что по Вашей рекомендации и надо было бы делать в энтерпрайзе, так как доступ по хэшу в словаре действительно быстрый, он был бы даже быстрее, чем поиск по хэшу типа через LINQ, как Вы предлагали изначально, так как не было бы итерирования (а с ним и мусора).
Тога ревью пройдено. Мердж, коллега. )
Спасибо. Рад, что в конечном счете труд не пропал зря.
Строковые сравнения не являются причиной утечки производительности в данном случае. И их нельзя заменить на сравнение хэшей типов, так как в условии примера был доступ к данным из БД. Корреляция в данном случае устанавливается по строковым ключам.

В конечном делегате вызова рефлексии и нет.

Автомаппер делает именно так, но он не смаппит Вам словарь в объект без настройки под конкретный DestinationType методом — трансформеров. Не помню, как они у него называются. резолверы вроде.

Information

Rating
Does not participate
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity