→ C#

Оптимизируем свой собственный Inversion of Control Framework

Я люблю изобретать велосипеды для вещей, которые я очень часто использую. Свой собственный фреймворк Inversion Of Control – это как раз тот случай. Я уже как-то показывал очень простую реализацию Inversion Of Framework, которую я использую во всяческих своих приложениях Реализуем сами простой IoC контейнер (и он уже даже вырос до более интересного проекта OutcoldSolutions.Framework).

В общем, как-то я напоролся на вот это сравнение IoC Container Benchmark - Performance comparison, в котором сравнивалась производительность различных контейнеров. И тут я задался тем же вопросом о том, насколько производительный мой фреймворк. Вот результат (я уменьшил количество итераций, так что не стоит сравнивать с оригинальной статьей):

В этом списке "Outcold" – это то, что я использую сейчас (ранняя версия на github). "IoC E" – это мой вариант из предыдущей статьи, построенный на Expressions (смотри комментарий), "IoC A" – это первоначальный вариант из предыдущей статьи, построенный на обычном Activator.CreateInstance. "LightInject" – один из самых шустрых фреймворков, "Unity" – это то, что я раньше использовал везде. Самое смешное, что после той прошлой статьи – мне как сказали, что Activator медленный, и стоит использовать Expressions – я так и сделал и стал использовать реализацию "IoC E" везде (а как оказалось - это самый тормозной IoC фрейморк, который вы только можете представить). Все дело в том, что каждый раз, когда вызывался метод CreateInstance, я каждый раз компилировал при помощи Expressions код, который должен быть создавать объекты:

То есть, первая оптимизация должна была быть очень простая. Нужно было просто переписать Expressions таким образом, чтобы можно было бы их сохранять и использовать каждый раз создавая объекты одного и того же типа. Как вы видите в примере, в конструктор я передаю константы, поэтому данные Expression не получится сохранить для повторного использования, если значения для аргументов конструктора будут другие. То есть, нужно из этого создать Expression функцию, которая могла бы принимать параметры для конструктора. В результате я переписал Expression так:

Теперь я сохраняю скомпилированный Delegate, возвращаемый последней строкой. Проблема теперь в том, что единственный способ, который я знал, как вызвать этот делегат – это через DynamicInvoke (которая достаточно медленная):

В любом случае, разница с тем, как было без хранения Expression, значительная (но как мы видим, до сих пор хуже реализации с Activator):

Я не сдался, проанализировав код еще раз, запустив наш профилировщик от Visual Studio, я нашел слабое место – это DynamicInvoke. Мне нужно было найти способ, при помощи которого я мог бы конвертировать Delegate в метод. Мне пришла идея написания такого Expression:

Здесь я на входе всегда получаю массив параметров (object[]), затем я каждый элемент массива я последовательно сопоставляю с параметрами конструктора. Результат такой:

Теперь быстрее, чем Activator, но все же чуток медленнее, чем LightInject. Предполагаю, что он шустрее в силу того, что они использовали System.Reflection.Emit. Говорю в прошедшем времени, так как вижу, что последняя версия библиотеки LightInject тоже использует Expressions.

На этом я и остановился. Не самая быстрая версия у меня, но выше середины.

Проблемы предыдущей задачи

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

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

Но самая большая проблема этого кода (да и вообще всех статических конструкторов и инициализаций статических переменных), что никто не будет ожидать, что метод IsObsolete может бросить NullReferenceException. И правильно сказали в комментариях, если код их метода GetLegacyStatements выдаст исключение, то класс не будет проиницилизирован должным образом, и переменная ApplicationObsoleteStatements будет равен null. Как такого можно достичь? Кто-нибудь что-нибудь поменяет, этот метод не будет убивать AppDomain, да и использоваться этот код будет редко – вот и будет результат.

Открываем рубрику «простые задачи»

Я хотел опубликовать свой предыдущий пост совсем по-другому, но сделал этот черновик уже давно, на выходные съездил на Columbia River, и совершенно забыл обновить этот топик.

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

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

Предметная область такая: у нас есть какой-то парсер выражений, который мы обновляли с версии до версии. Соответственно, иногда у нас какие-то выражения становились obsolete. То есть скоро от них планируем избавиться, а пока выводим какие-нибудь оповещения о том, что такие выражения лучше не использовать. Реализацию проверки вы видите в приведенном мной куске кода.

Что ужасного в этом коде (скажем так, что самое ужасное в нем, самое неожиданное, не считая того, что такой код лучше не писать совсем)?

Использование оператора as (C#) и обработка Exceptions

Вчера первый раз в руках держал свою собственную копию бумажного варианта MSDN журнала. Новость, понятное дело, не эпическая, но хочу сказать, что именно бумажная копия журнала мотивирует на чтение MSDN статей, до этого ни разу я не читал столько статей из одного выпуска. В общем, на одной из первых страниц, в статье A Few of My Favorite Things... in the Entity Framework 4.2 DbContext я увидел следующий код:

var objectContext = (myDbContextInstance as IObjectContextAdapter).ObjectContext

Что плохого в этом коде? Я просто не понимаю смысла использования оператора as в этой строке. Почему бы просто не использовать обычное приведение типов:

var objectContext = ((IObjectContextAdapter)myDbContextInstance).ObjectContext

Чем этот вариант лучше предыдущего? А что если myDbContextInstance все-таки каким-то образом нельзя будет привести к типу интерфейса IObjectContextAdapter? Что будет в этом случае? В первом вариант мы получим всеми нами любимый NullReferenceException, так как оператор as вернет null в случае, если у него не удастся это приведение из типа DbContext в IObjectContextAdapter. Во втором случае мы честно получим тот самый InvalidCastException, который сразу же может дать нам понять, где именно, и в чем ошибка.

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

1  2  3  4  5  6  ...