Сравнение производительности .NET ORM: EF, linq2sql, NH, ADO.NET

 

Наткнулся на полезную и интересную статью по сравнению производительности от Александра Кондуфорова.
Сама статья по ссылке: http://merle-amber.blogspot.ru/2008/11/net-orm-1.html

Здесь привожу фрагменты в качестве памятки:

===========================================================================

Сегодня я хотел бы начать серию заметок о сравнении производительности Entity Framework, LINQ to SQL, Active Record (NHibernate) и классического ADO.NET.

Сразу скажу, что, несмотря на то, что я уже почти год использую Entity Framework, я не буду стараться его выгораживать. Откровенно говоря, сейчас мы в нашем проекте подошли к той границе, за которой bottleneck начинается уже в самом Entity Framework, а я пока и близко не в восторге от производительности нашей системы. Поэтому я постараюсь быть максимально объективным в данном вопросе, а если все же где-нибудь буду говорить чушь, вы меня поправите.

Изначально я хотел выяснить производительность EF и L2S, а также сравнить их с классическим ADO.NET, т.к. последний на данный момент является наиболее быстрым способом доступа к базе данных. Естественно, любая ORM будет медленнее. Потом меня эта идея настолько увлекла, что я для полноты картины добавил еще и Active Record, который, как известно, базируется на NHibernate. Еще один важный момент: я вижу NHibernate впервые в жизни, поэтому: а) не судите строго тот код, который я с горем пополам в нем написал, б) если у вас есть советы и комментарии, как по коду, так и по улучшению производительности – буду счастлив услышать :)

Все, довольно вступлений. Итак, для начала я покажу результаты моих тестов read-запросов. В следующих заметках серии я постараюсь уделить время генерируемым запросам, а также CUD-операциям. Пока же ограничимся выборками.

Использовал я всеми любимую базу Northwind, в частности таблицы Customers, Orders, Order Details и Products. Для каждого из способов доступа к данным я сделал 8 простых тестов:

Тест Описание Цель
Инициализация Создание контекста, инициализация, чтение строки подключения Получить время инициализации, возможно, оптимизировать его
Получение всех заказов Простая выборка всех строк из таблицы Orders Самый простой запрос, в то же время можно сравнить время материализации, т.к. там больше 800 записей
Многократное получение всех заказов Выборка всех строк из таблицы Orders 3 раза Сравнить степень кеширования запросов и их результатов
Получение всех продуктов одного заказчика Один сложный запрос через 3 таблицы Сравнить эффективность генерации SQL-скриптов, материализация минимальна
Многократное получение всех продуктов одного заказчика Тот же запрос 3 раза То же самое + кеширование
Получение всех продуктов одного заказчика (сложная версия) Пошаговое получение того же результата, что и в предыдущем случае. Берем коллекцию, проходимся foreach'ем, берем связанные записи и т.д. Сравнить данный способ обращения к данным с предыдущим
Многократное получение всех продуктов одного заказчика (сложная версия) То же самое 3 раза То же самое + кеширование
Микс: получение заказов + продуктов заказчика Синтетический тест, эмулирующий разные запросы Посмотреть, как влияют результаты выполнения одного запроса на следующий
Тест более серьезной нагрузкой Все предыдущие запросы последовательно за один присест Попробовать нагрузить базу большим количеством последовательных запросов, и посмотреть, что получится

 

К сожалению, тесты с базой данных – это такая вещь, которую бывает трудно воспроизвести, результаты варьируются, но, надеюсь, показанные числа дадут хотя бы какое-то общее представление о порядках. В любом случае, вы можете скачать исходники, установить L2S, EF, AR (если еще не установлены) и поиграться сами. Машинка у меня дома относительно небыстрая (Athlon 64 3200+ с 3 гигами мозгов), поэтому делайте скидку и на это.

Анализ результатов

Итак, результаты в миллисекундах:

Test Classic L2S EF NH/AR
Initialization 15 78 101 410
Orders 125 172 1078 1062
OrdersMultiple 187 218 1093 1187
CustomerProducts 109 234 1328 828
CustomerProductsMultiple 125 281 1344 1156
CustomerProductsComplex 140 464 1453 937
CustomerProductsComplexMultiple 203 511 1515 1109
Mixed 140 296 1265 1187
All 187 515 1656 1546

Что сразу бросается в глаза, так это тотальное отставание EF и AR от L2S (в 3-6 раза), а также то, насколько быстро L2S работает с базой – в среднем всего лишь где-то в 2 раза медленнее. При ближайшем рассмотрении можно сделать вывод, что AR работает быстрее, чем EF, по крайней мере, в запросах, возвращающих небольшое количество данных. У меня есть подозрение, что AR медленнее материализует объекты, чем EF (запрос по Orders – самый простой, но в то же время самый большой по количеству данных), но это нужно взять выборку побольше, чтобы проверить. При этом инициализация контекста у EF происходить почти так же быстро, как и у L2S, а AR тут немного отстает, хотя это и не страшно. Что еще важно: все ORM отлично справляются с кешированием повторных запросов: 3 запроса выполняются ненамного дольше, чем один. Связано это, прежде всего, с тем, что они кешируют не только материализованные объекты, чем существенно экономят время, но и, в случае EF – деревья промежуточных запросов (CQT, Canonical Query Tree). Так что одинаковые и даже похожие запросы выполняются намного быстрее. Также показателен последний тест: он дает возможность увидеть, что каждый запускаемый поодиночке тест все-таки несет определенный накладные расходы, потому что один общий тест лишь очень ненамного превышает самый медленный из своих компонентов. А это говорит о том, что НЕ НУЖНО создавать контексты на каждый запрос (это касается, в первую очередь L2S и EF), как бы вас этому ни учили в умных статьях. Если хотите добиться хорошей производительности – старайтесь использовать контексты как можно дольше, но в разумных пределах. Мы вот у себя нашли этот самый разумный предел – контекст живет в пределах обработки одной страницы, то есть контекст создается на каждый HttpRequest и умирает вместе с ним. В сложных случаях многостраничных диалогов мы продлеваем время жизни контекста, перемещая его временно в сессию.

 

Теперь пара слов о том, почему же мы видим такие результаты. Ну, во-первых, L2S у нас завязан лишь на один SQL Server и, если я не ошибаюсь, не строит никаких промежуточных CQT, чтобы общаться с тысячей различных провайдеров, как это делает EF. Во-вторых, EF – это не просто ORM, способная работать с разными базами данных и на лету подменять их без изменения кода. EF также обладает различными продвинутыми фичами маппинга, которых как минимум нет в L2S (не буду говорить про NH, ибо не знаю всех его возможностей). В-третьих, судя по проанализированным мною сгенерированным SQL-запросам, здесь особой разницы между ORM нет (подробнее об этом мы поговорим в следующей заметке), поэтому сюда особо рыть не стоит. И, в-четвертых, EF еще только-только зарелизился, думаю, мы еще увидим более быстро работающие версии.

Советы по использованию подопытных

Итак, определенные советы по использованию этих ORM (в широком смысле, L2S – это тоже ORM) можно дать уже сейчас. Если вам нужно написать простенькое приложение – используйте L2S – это САМЫЙ быстрый способ. Накидали табличек в базу, сгенерировали модель 2-мя кликами мыши – и вуаля, можно работать. Для более серьезных приложений уже нужно смотреть внимательнее. С точки зрения скорости написания кода равных L2S и EF нет. При всем уважении, AR/NH пока медленнее за счет того, что нет тула по созданию и поддержке модели. Если вам нужны продвинутые возможности маппинга, вы делаете разработку в рамках DDD или нужна поддержка различных баз данных – смотрите в сторону AR/NH и EF. Тут уже что больше по душе. Если же у вас на первом месте производительность и вы работаете лишь с MS SQL Server – тогда вам, наоборот, нужно смотреть либо в сторону L2S, либо вообще в сторону классического ADO.NET. Однако, в случае с классическим ADO.NET время разработки, а также последующей поддержки Data Access Layer увеличивается не просто в разы, а на порядки. Да, и пусть вас не смущают результаты запросов секунда и выше – как можно заметить, реально занимают время первые запросы, а остальные идут очень быстро и по накатанной. Более того, могу сказать по опыту, что где-то для 50-60% приложений производительности EF и AR/NH должно хватить с головой. Так что, если у вас не rocket science, не стоит заморачиваться.

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

 

Ваш комментарий:

Сколько будет 6 + 6 ?