Thursday, November 20, 2008

IT-образование vs. профессиональный рынок

К написанию следующей заметки меня подтолкнула статья Виктора Ивановича Каука, к.т.н., директора Центра технологий дистанционного обучения Харьковского национального университета радиоэлектроники о проблемах подготовки кадров для IT-отрасли, ссылку на которую Паша Подлипенский разместил и снабдил комментариями в своем блоге. Меня эта тема также волнует уже очень давно, т.к. я только относительно недавно сам был студентом ХНУРЭ и видел все происходящее с нами и университетом своими глазами. Более того, я был активным участником данного действа, и мне бы хотелось высказать свои мысли по этому вопросу.

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

Итак, у нас есть три действующих лица: вуз, коммерческие IT-фирмы и студенты. Причем говорить я буду не обо всех студентах, а лишь о тех, о ком говорит Виктор Иванович – о тех 10-20%, которые находят себе работу по специальности, еще обучаясь в университете. Исходя из своего опыта (я учился на кафедре Программного обеспечения автоматизированных систем, на потоке численностью около 180 человек, «работал» в университете с первого курса и после третьего ушел работать в крупную IT-компанию города Харькова) я могу сказать, что я наблюдал 2 волны оттока студентов. Первая волна началась где-то с начала третьего курса и закончилась где-то на четвертом. Если не считать тех единиц, которые пошли работать на 1-2 курсах, здесь ушло порядка 15-20 человек, т.е. где-то 10% потока. Связана первая волна была с тем, что 1) толковые ребята поднабрались уже достаточно опыта в вузе и шабашках, чтобы попробовать свои силы, 2) у них появились запросы, требующие финансов, и 3) многие из них попали на практику в коммерческие фирмы, после которой они там и остались. Потом в течение полугода-года опять же устраивались на работу единицы, а где-то с середины 5-го курса, когда начался диплом, пошла вторая волна, которая захватила еще где-то человек 10-15. Общее количество работающих на потоке приблизилось к 20-25%. После окончания вуза работать по специальности (в той или иной мере) пошли еще наверно процентов 10. Остальные работают не по специальности. Но разговор сейчас не о тех, кто нашли работу после вуза, а о тех, кто, по сути, частично бросил обучение с различными целями.

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

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

  1. хочет найти применение тем знаниям и опыту, что у него уже есть (есть у человека стремление к самовыражению, есть)
  2. хочет быстрее получить более серьезные знания в реальной работе, т.к. он этого не может получить в вузе, где дают широкие, но неглубокие знания
  3. хочет получить опыт реальной работы, т.к. это ему пригодится после вуза
  4. хочет начать зарабатывать деньги, причем не такие и маленькие, т.к. запросы уже выросли, а стипендия – нет (или ее и не было), хочет стать финансово независимым
  5. и, да, как правильно отметил Виктор Иванович, иногда хочет просто казаться взрослым и шарящим, мол, меня вот взяли, я работаю, я умный

Чего хочет вуз и преподаватели:

  1. дать своим студентам максимально качественное образование
  2. трудоустроить студентов после окончания вуза (есть такое требование Минобразования, насколько я знаю)
  3. высокой посещаемости студентами занятий
  4. проведения совместных исследовательских работ, т.к. у преподавателей на это зачастую просто не остается времени, им нужны помощники и исполнители, не говоря уже о том, что часто они просто исследователи в душе
  5. привлечения самых толковых ребят в аспирантуру с прицелом на работу в вузе

Чего хотят коммерческие фирмы в условиях большого спроса на специалистов:

  1. получать после вуза хорошо подкованных специалистов в достаточных количествах, которых не нужно переучивать или учить с нуля
  2. лучше получать специалистов средних, но много, чем действительно классных, но мало, т.к. специфика аутсорсинг-проектов, как правило, не требует много асов, она требует одного-двух асов и кучу хороших исполнителей
  3. успеть забрать самых лучших на работу именно к себе, т.к. в противном случае они будут работать на других, а тебе останутся ребята послабее
  4. дать возможность работающим у них студентам не тратить много времени на учебу, т.к. это отвлекает их от реальной работы, которая приносит прибыль фирме
  5. не давать особой возможности студентам работать на полставки (есть фирмы-исключения, но их не так много), т.к. такую работу сложнее координировать, планировать и, в конечном счете, продать конечному клиенту

Как видите, как минимум цели вуза и коммерческих фирм достаточно серьезно расходятся, если не сказать больше. По сути, это в какой-то мере борьба за толковых, умных ребят, в которой, в конечном счете, все равно проигрывает университет, т.к. он не дает таким студентам ни возможности реализовать себя, ни возможности быстро получить необходимые знания, ни возможности заработать. Ситуация осложняется еще и тем, что IT-отрасль, наверно, одна из немногих, где диплом действительно почти ничего не значит. Это раньше было, что человек с высшим образованием всегда имел приоритет над тем, у кого оно неполное, не говоря уже про цвет корочки. А в IT важны реальные знания, умения и опыт, благо, их в нашей отрасли легче проверить, чем, допустим, в банковской сфере или менеджменте. В какой-то мере все это напоминает «утечку мозгов» за границу, которую остановить можно либо выставив железный занавес, что нарушает право свободного выбора человека, либо создав условия для того, чтобы эти «мозги» все-таки остались здесь, в родном вузе, по собственному желанию. А это уже ой как непросто.

К сожалению, состояние нашего государства сейчас таково, что ему плевать на все эти проблемы. Какие там стипендии, гранты, какие там исследования. Я считаю, что IT-отрасль в Украине существует во многом не благодаря, а вопреки. В то же время, я уверен, что если бы в университете была интересная исследовательская работа, подкрепленная именными стипендиями, грантами и неким подобием зарплат, многие из тех, кто ушел работать, подумали бы 30 раз, прежде чем сделать это. Не знаю, кто как, а я уже насмотрелся на эти однообразные аутсорсовые приложения, в которых можно найти в лучшем случае технологический интерес, но уж никак не исследовательский (что бы там кто ни говорил, а настоящего R&D в аутсорсовых проектах, как кот наплакал). Если бы мне сейчас предложили программировать роботов, системы с элементами ИИ или спутники, я бы пошел, не раздумывая. А если бы это предложили в университете, это был бы вообще предел мечтаний. По крайней мере, часть людей, которым это действительно интересно, остались бы и могли бы заниматься тем, что им нравится, и не факт, что с меньшими зарплатами и перспективами.

Решение мне видится достаточно простым в формулировке, но в то же время ужасно сложным в реализации. Мне очень нравится фраза о том, что «настоящий учитель – это тот, кто умеет сначала разбудить любопытство, а потом его удовлетворить». Так и здесь, преподаватели должны суметь разбудить студентов, причем не только тех, кого называют толковыми, а вообще большинство из них. Разбудить их интересными задачами, совместными исследованиями, перспективами. Как это сделать?

  1. С ранних курсов стараться занимать студентов на различных внутренних проектах вуза, как учебных, так и практических, в рамках изучаемых ими дисциплин. При этом не нужны эти искусственные курсовые и лабы. IT – это новый век, здесь нужны новые подходы. В разных вузах используются экспериментальные подходы по обучению студентов программированию, когда пишутся проекты целыми группами студентов, максимально приближенно к реальности.
  2. Давать студентам по-возможности полные и последние знания, чтобы им было интересно ходить на занятия. Чтобы они знали, что они могут и там могут научиться чему-то полезному, а не только «на улице». Постоянно совершенствовать свои знания и умения, чтобы быть «в струе».
  3. Стимулировать внеклассные, факультативные занятия на различную тематику.
  4. Проводить в рамках вуза различные конференции, форумы, приглашать признанных специалистов, чтобы заинтересовать, зажечь людей.
  5. Проводить олимпиады по программированию, конкурсы научных работ, отправлять ребят с их работами в другие города, за границу. Пусть посмотрят мир.
  6. Стимулировать исследовательскую работу. Искать реальные, интересные проекты, желательно оплачиваемые, и решать их силами студентов и кафедры. Создавать исследовательские центры внутри вуза, получать гранты и т.д.
  7. Отправлять талантливых ребят (а не тех, кто заплатил, как у нас часто случается) учится по обмену за границу, выполнять там какую-то исследовательскую работу. В общем, активизировать связи с другими вузами и двигаться в этом направлении.
  8. Поощрять ребят, добивающихся успехов, стимулируя и подстегивая тем самым остальных.
  9. Сделать будущую возможную работу в аспирантуре максимально привлекательной для студентов, причем не только в финансовом плане.
  10. И так далее, думаю, основная идея понятна, а дальше можно развивать фантазию.

Да, эти меры не дадут 100%-го возврата людей в вуз, но они, по крайней мере, оставят тех, кто бы действительно хотел бы заниматься наукой и исследованиями, в вузе, а также, возможно, повысили бы те 20-30% работающих в результате по специальности ребят еще раза в полтора-два. Разве овчинка не стоит выделки? И, что удивительно, это выгодно не только вузам, но также и коммерческим фирмам, не говоря уже о студентах.

Sunday, November 16, 2008

Полтава

Думаю, почти каждый человек любит свой родной город/поселок/деревню. При этом это не обязательно место, где вы родились. Часто это место, где вы выросли, закончили школу, провели свои самые беззаботные и лучшие годы жизни. И не важно, насколько красиво это место, кто там живет, маленький ли это городишко или огромный мегаполис, это та самая малая Родина, место, куда хочется возвращаться даже после того, как ты уезжаешь в другой город, страну или даже улетаешь на другую планету Солнечной системы. Однако, не все могут сказать, что родились в самом красивом месте на нашей планете. И я тоже не могу :) Однако, я точно знаю, что мое детство прошло в одном из самых красивых городов Украины – Полтаве.

Полтава – один из древнейших городов Руси. Первое письменное упоминание о нем можно найти в Ипатьевской летописи от 1174 года. Считается, что это официальная дата основания города. Таким образом, в следующем году Полтаве официально исполнится 835 лет. Кроме того, в следующем, 2009 году, Полтава будет отмечать 300-летие Полтавской битвы.

Да, известна Полтава прежде всего тем, что в 1709 году около города произошла битва между русским войском Петра I и шведским войском Карла XII. Эта битва вошла в историю под названием Полтавской и стала переломным моментом в ходе Северной войны. Надо отметить, что до прихода Петра I войска шведов осаждали город в течение 3 месяцев и не смогли взять его. 4-тысячный гарнизон Полтавы под командованием полковника А.С. Келина отразил 20 штурмов 37-тысячной армии Карла XII. В осаде шведы потеряли 6 тыс. человек и израсходовали почти весь пушечный боезаряд, что, конечно же, помогло подоспевшему 42-тысячному войску Петра I победить в битве. Сама битва произошла 27 июня у деревни Яковцы, где сейчас располагается музей Поле Полтавской битвы, а также памятные знаки на местах, где были редуты русского войска. Еще в этой битве интересно то, что она началась в 2 часа ночи атакой шведов на эти самые редуты. Кому интересен ход сражения, подробнее о нем можно почитать на википедии или здесь. Еще один интересный источник: «Полтава: рассказ о гибели одной армии» Петера Энглунда, шведского историка. Результатом битвы стала потеря шведами более 11 тыс. солдат убитыми и 16 тыс. солдат пленными, русские же потеряли 1345 человек убитыми и 3290 ранеными. В результате Полтавской битвы одна из самых сильных европейских армий того времени (если не самая сильная, судя по успехам шведов в Европе) перестала существовать, а Карл XII был ранен и был вынужден бежать с Мазепой на территорию Османской империи.

Кроме этого, Полтава известна еще своим культурным и научным наследием. Иван Котляревский, известный украинский писатель и драматург, был первым классиком новой украинской литературы. Именно он был первым автором, начавшим писать на украинском языке, близком к современному, в основу которого был положен диалект Полтавской губернии. Также стоит отметить писателей Панаса Мирного, В.Г. Короленко, А.В. Луначарского, поэтессу Марусю Чурай, одного из основоположников теории космонавтики Юрия Кондратюка, известного математика XIX века М.В. Остроградского, а также украинского политического и военного деятеля Симона Петлюру. В разное время в городе жили и другие известные личности.

Однако хватит истории. Что меня радует в Полтаве сегодняшней, так это ее чистота, древность и красота. Когда я был маленький, я этого всего не замечал. Наверно, точно также себя чувствует житель Санкт-Петербурга, Рима, Праги или любого другого туристического города, когда вдруг оказывается, что все эти домики и памятники, к которым он привык с самого детства, оказываются памятниками мирового наследия. Конечно, до перечисленных городов Полтаве очень далеко, но все же по меркам Восточной Украины город очень и очень интересный. Здесь есть много музеев (самые интересные – Поле Полтавской битвы, Краеведческий, Котляревского, П.Мирного и Кондратюка), целая толпа памятников (Славы, Неизвестному солдату, Шевченко, Пушкину, Гоголю, Котляревскому, М.Чурай, козакам, несколько памятников Петру I, русским от шведов, шведам от русских, русским от русских, конечно же, Ленину, а также юмористические полтавской галушке и свинье, кормилице украинского народа :)), много храмов, Крестовоздвиженский монастырь, куча парков (вообще, Полтаву можно назвать городом-садом, у нас очень много деревьев), архитектурных сооружений XIX-начала XX века, а также один из символов города – Белая Альтанка (или Ротонда Дружбы народов), которая была сооружена к 200-летию со дня Полтавской битвы. В общем, посмотреть есть на что, так что немудрено, что Полтава в наши дни становится одним из туристических центров Украины. Лишь бы не растерять потенциал.

Впрочем, в ближайший год этого сделать Полтаве не удастся точно. Как я уже упоминал, в июне 2009 года город будет отмечать 300-летие Полтавской битвы. Так что, если наши гениальные политики ничего не испортят, то на это событие будет стоить съездить. В хедлайнеры обещают шведского короля, а если все будет хорошо, то делегация из России тоже будет. Мы так точно поедем посмотреть на это столпотворение :)

Несколько полезных ссылок о городе:

История города Полтава Полтава Историческая

Если кто еще не был в Полтаве, можете глянуть мои фотографии:

С чего начинается Родина Прогулка по Полтаве Ночная Полтава

Может, они вас натолкнут на мысль там побывать. Ну, и несколько наиболее удачных фотографий:

Thursday, November 13, 2008

Сравнение производительности .NET ORM: Часть 2. Генерация SQL-запросов

И снова здравствуйте. В первой части серии я постарался рассмотреть производительность L2S, EF и AR/NH по сравнению с классическим ADO (тестовое приложение можно найти здесь). NET и друг с другом. Для этого были написаны специальные тесты, но замеры мы производили на достаточно высоком уровне, не выделяя в результатах базу данных. Как я и обещал, во второй части я бы хотел опуститься ниже, в базу, и сравнить запросы, генерируемые нашими подопытными. Должен сказать, что я был несколько удивлен полученными результатами, но обо всем по порядку.

Способ сравнения

Способ сравнения я выбрал до предела простой. У нас уже есть написанный код для получения продуктов по заказчику. Это не ахти какой сложный запрос, конечно, но тем не менее что-то мы из него получим. При этом у нас есть два варианта получения данных: одним запросом с джойнами (тест CustomerProducts) или при помощи простых операций вроде получения сначала заказчика, потом его заказов, потом деталей заказа и уж в самом конце – продуктов, то есть при помощи пачки запросов (тест CustomerProductsComplex).

Значит, заряжаем SQL Server Profiler, запускаем наши тесты, вытаскиваем пойманные SQL-запросы, садим их в Management Studio, включаем “Include Actual Execution Plan”. Я сделал 4 теста. В первом сравниваются запросы из теста CustomerProducts для всех четырех способов доступа к данным. В последующих трех я сравнивал попарно запросы для теста CustomerProducts и для теста CustomerProductsComplex для каждого из ORM отдельно (для классического ADO.NET я не делал этот тест).

Какова цель этих тестов? Цель первого теста – сравнить качество генерируемого SQL-кода по сравнению с конкурентами и самым простым SQL-запросом, написанным вручную и получающим эти же данные. Цель остальных тестов – выяснить, какой способ получения данных, через один или несколько запросов, выгоднее и насколько.

Итак, поехали.

Сюрпризы

Уже на проведении первого же теста (того, где должен быть один результирующий запрос) меня ждало несколько интересных открытий:

  1. L2S зачем-то сгенерировал два запроса вместо одного. Первый запрос у них вытаскивает заказчика, а второй уже выполняет реальную работу. Я проверил свой код – вроде бы там простой LINQ-запрос, который должен превратиться в один SQL-запрос. Более того, такой же LINQ-запрос в EF действительно превращается в один SQL-запрос. Что ж, сами напросились, L2S будет тестироваться с двумя запросами, тем более что первый запрос по сравнению со вторым выполняется почти моментально, поэтому какая разница.
  2. AR/NH пошел еще дальше, чем L2S. Я ожидал, что написанный мною HQL-запрос выльется в один SQL-запрос. Не срослось. NH сгенерировал и выполнил 23 запроса. Но, присмотревшись к природе этих запросов, я увидел, что это запросы к таблице Supplier, которая в тесте никак не участвует, но зато есть в моем маппинге. Я понял, что, по всей видимости, не отключил lazy loading, поэтому просто в очередной раз отметил свое незнание AR/NH, исправил свойство Supplier класса Product на SupplierID, а атрибут [BelongsTo] поменял на [Property], после чего просто перезапустил тесты и получил корректные SQL-запросы. Замечание: Да, вы правильно подумали, результаты общих тестов для AR из предыдущей части тоже учитывают эти дополнительные запросы, но исходя из того, что я увидел в результатах выполнения тестов, мы не получили здесь особого прироста производительности.
  3. Я наконец-то убедился, что L2S и EF писали разные люди :) Даже несмотря на то, что я специально сделал LINQ-запросы в L2S и EF идентичными, генерируемые SQL-запросы отличаются. Более того, они отличаются не только текстом, но и производительностью, что вы увидите в результатах.

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

Результаты тестов несколько необычны. Для простоты я запустил все запросы каждого теста в одном наборе и посмотрел на Query cost каждого запроса, который считается относительно всего набора.

В первом тесте я получил следующие результаты:

Классический ADO.NET – 14%
LINQ 2 SQL – 2 + 48 = 50%
Entity Framework – 22 %
Active Record (NHibernate) – 14%

Вот и еще один сюрприз: запрос L2S оказался существенно медленнее запроса EF, не говоря уже про победителей – NH и наш контрольный запрос. Зато NH превзошел все ожидания. HQL очень чисто трансформировался в SQL, его план выполнения в точности совпал с планом выполнения контрольного запроса. План выполнения EF тоже неплох, а вот L2S намудрил. Да, здесь есть важный момент: это всего лишь ОДИН тест, на основании которого не стоит судить об ORM в целом. Вполне возможно, есть запросы, где намудрит EF или даже NH. В любом случае, среднее время выполнения этих запросов намного меньше результирующего времени, учитывающего дополнительные расходы на построение запроса и материализацию результата (в среднем где-то 50-150 мс согласно профайлеру). И тут уже L2S несомненно опережает EF и AR/NH с большим отрывом.

Во втором тесте я сравнил сложный запрос с джойнами и набор простых запросов для L2S:

Сложный запрос с джойнами – 25%
Набор простых запросов – 75%

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

Третий тест был посвящен EF:

Сложный запрос с джойнами – 18%
Набор простых запросов – 72%

Как видите, результаты похожи до безобразия.

И, наконец, в четвертом тесте я сравнил запросы для NH:

Сложный запрос с джойнами – 6%
Набор простых запросов – 94%

Вот уж где для увеличения производительности стоит отказаться от удобных переборов коллекций в пользу HQL, так это здесь :)

Выводы

Какие выводы мы можем сделать по результатам этих тестов?

  1. Прежде всего, если у вас есть возможность выполнить один сложный запрос – выполняйте, не бегайте по коллекциям и не инициализируйте их отдельно, это немного сэкономит вам драгоценное время. Особенно это касается ситуаций, где bottleneck находится в базе данных, а не внутри ORM.
  2. Похоже, оптимизация запросов в L2S сделана немного хуже, чем в EF. Я слышал, что в EF team привлекли очень толковых специалистов по оптимизации запросов, так что, возможно, это результат их работы.
  3. Как показывает практика, генерируемые запросы достаточно неплохи по сравнению с контрольным, так что основная проблема в производительности ORM находится все-таки за пределами базы данных, а именно внутри ORM, в алгоритмах генерации SQL-запроса, работы с контекстом и материализации данных.
  4. Я протестировал LINQ-запросы для L2S и EF. Было бы еще интересно посмотреть, как с заданием справится eSQL (Entity SQL) для EF. Если у кого-то есть опыт – поделитесь.
  5. Аналогично я хотел бы узнать у специалистов насчет HQL в NH. Насколько я понял, в NH есть еще и альтернативный способ создавать сложные запросы через джойны – при помощи критериев. Какие у них плюсы и минусы?

Бонус

Ну, и напоследок бонус тем, кто дочитал до этого места :) Собственно, сгенерированные сложные запросы с джойнами для NH, EF и L2S. Не пугайтесь их, они не такие страшные, как кажутся на первый взгляд :)

NHibernate:

exec sp_executesql N'select product2_.ProductID as ProductID4_, product2_.ProductName as ProductN2_4_, product2_.UnitPrice as UnitPrice4_, product2_.SupplierId as SupplierId4_ from [Order Details] orderdetai0_ inner join Orders order1_ 
on orderdetai0_.OrderID=order1_.OrderID inner join Products product2_ on orderdetai0_.ProductID=product2_.ProductID where (product2_.ProductID=product2_.ProductID )AND(order1_.OrderID=order1_.OrderID )AND(order1_.CustomerID=@p0 
)',N'@p0 nvarchar(5)',@p0=N'BERGS'

Entity Framework:

exec sp_executesql N'SELECT 
[Project3].[CustomerID] AS [CustomerID], 
[Project3].[C1] AS [C1], 
[Project3].[C3] AS [C2], 
[Project3].[OrderID] AS [OrderID], 
[Project3].[C2] AS [C3], 
[Project3].[ProductID] AS [ProductID], 
[Project3].[ProductName] AS [ProductName], 
[Project3].[QuantityPerUnit] AS [QuantityPerUnit], 
[Project3].[UnitPrice] AS [UnitPrice], 
[Project3].[UnitsInStock] AS [UnitsInStock], 
[Project3].[UnitsOnOrder] AS [UnitsOnOrder], 
[Project3].[ReorderLevel] AS [ReorderLevel], 
[Project3].[Discontinued] AS [Discontinued], 
[Project3].[CategoryID] AS [CategoryID], 
[Project3].[SupplierID] AS [SupplierID]
FROM ( SELECT 
    [Extent1].[CustomerID] AS [CustomerID], 
    1 AS [C1], 
    [Project2].[OrderID] AS [OrderID], 
    [Project2].[ProductID] AS [ProductID], 
    [Project2].[ProductName] AS [ProductName], 
    [Project2].[SupplierID] AS [SupplierID], 
    [Project2].[CategoryID] AS [CategoryID], 
    [Project2].[QuantityPerUnit] AS [QuantityPerUnit], 
    [Project2].[UnitPrice] AS [UnitPrice], 
    [Project2].[UnitsInStock] AS [UnitsInStock], 
    [Project2].[UnitsOnOrder] AS [UnitsOnOrder], 
    [Project2].[ReorderLevel] AS [ReorderLevel], 
    [Project2].[Discontinued] AS [Discontinued], 
    [Project2].[C1] AS [C2], 
    [Project2].[C2] AS [C3]
    FROM  [dbo].[Customers] AS [Extent1]
    LEFT OUTER JOIN  (SELECT 
        [Extent2].[OrderID] AS [OrderID], 
        [Extent2].[CustomerID] AS [CustomerID], 
        [Project1].[ProductID] AS [ProductID], 
        [Project1].[ProductName] AS [ProductName], 
        [Project1].[SupplierID] AS [SupplierID], 
        [Project1].[CategoryID] AS [CategoryID], 
        [Project1].[QuantityPerUnit] AS [QuantityPerUnit], 
        [Project1].[UnitPrice] AS [UnitPrice], 
        [Project1].[UnitsInStock] AS [UnitsInStock], 
        [Project1].[UnitsOnOrder] AS [UnitsOnOrder], 
        [Project1].[ReorderLevel] AS [ReorderLevel], 
        [Project1].[Discontinued] AS [Discontinued], 
        [Project1].[C1] AS [C1], 
        1 AS [C2]
        FROM  [dbo].[Orders] AS [Extent2]
        LEFT OUTER JOIN  (SELECT 
            [Extent3].[OrderID] AS [OrderID], 
            [Extent4].[ProductID] AS [ProductID], 
            [Extent4].[ProductName] AS [ProductName], 
            [Extent4].[SupplierID] AS [SupplierID], 
            [Extent4].[CategoryID] AS [CategoryID], 
            [Extent4].[QuantityPerUnit] AS [QuantityPerUnit], 
            [Extent4].[UnitPrice] AS [UnitPrice], 
            [Extent4].[UnitsInStock] AS [UnitsInStock], 
            [Extent4].[UnitsOnOrder] AS [UnitsOnOrder], 
            [Extent4].[ReorderLevel] AS [ReorderLevel], 
            [Extent4].[Discontinued] AS [Discontinued], 
            1 AS [C1]
            FROM  [dbo].[Order Details] AS [Extent3]
            LEFT OUTER JOIN [dbo].[Products] AS [Extent4] ON [Extent3].[ProductID] = [Extent4].[ProductID] ) AS [Project1] ON [Extent2].[OrderID] = [Project1].[OrderID] ) AS [Project2] ON [Extent1].[CustomerID] = 
[Project2].[CustomerID]
    WHERE [Extent1].[CustomerID] = @p__linq__1
)  AS [Project3]
ORDER BY [Project3].[CustomerID] ASC, [Project3].[C3] ASC, [Project3].[OrderID] ASC, [Project3].[C2] ASC',N'@p__linq__1 nvarchar(5)',@p__linq__1=N'BERGS'

LINQ 2 SQL:

exec sp_executesql N'SELECT [t0].[CustomerID]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[CustomerID] = @p0',N'@p0 nvarchar(5)',@p0=N'BERGS'

exec sp_executesql N'SELECT [t2].[ProductID], [t2].[ProductName], [t2].[SupplierID], [t2].[CategoryID], [t2].[QuantityPerUnit], [t2].[UnitPrice], [t2].[UnitsInStock], [t2].[UnitsOnOrder], [t2].[ReorderLevel], [t2].[Discontinued], (
    SELECT COUNT(*)
    FROM [dbo].[Order Details] AS [t3]
    INNER JOIN [dbo].[Products] AS [t4] ON [t4].[ProductID] = [t3].[ProductID]
    WHERE [t3].[OrderID] = [t0].[OrderID]
    ) AS [value]
FROM [dbo].[Orders] AS [t0]
LEFT OUTER JOIN ([dbo].[Order Details] AS [t1]
    INNER JOIN [dbo].[Products] AS [t2] ON [t2].[ProductID] = [t1].[ProductID]) ON [t1].[OrderID] = [t0].[OrderID]
WHERE [t0].[CustomerID] = @x1
ORDER BY [t0].[OrderID], [t1].[ProductID]',N'@x1 nchar(5)',@x1=N'BERGS'

Monday, November 10, 2008

Сравнение производительности .NET ORM: Часть 1. Выборки данных

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

  1. получение адекватных результатов сравнения и попытка их объяснить
  2. обсуждения различных вариантов увеличения производительности вышеуказанных способов доступа к базе данных
  3. получение понимания, какие варианты доступа к базе данных являются наиболее приемлемыми в той или иной ситуации с точки зрения производительности

Сразу скажу, что, несмотря на то, что я уже почти год использую 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 раза То же самое + кеширование
Микс: получение заказов + продуктов заказчика Синтетический тест, эмулирующий разные запросы Посмотреть, как влияют результаты выполнения одного запроса на следующий
Тест более серьезной нагрузкой Все предыдущие запросы последовательно за один присест Попробовать нагрузить базу большим количеством последовательных запросов, и посмотреть, что получится

Код я здесь приводить не буду, а лучше дам вам ссылочку, где можно скачать приложение, чтобы глянуть запросы и код и надавать автору по голове за него посоветовать мне, что и как можно улучшить :) Для запуска приложения вам понадобится база Northwind, VS2008 SP1 (там есть EF и L2S), а также установленный Active Record (скачать можно здесь). Не забудьте сконфигурировать строки подключения к базе и параметры Active Record в классе ActiveRecordProvider (да, знаю, что за такое нужно отбивать руки, но для текущей задачи такой реализации с головой).

Пара слов перед тем, как показать результаты. Тестировал я достаточно просто, вручную запуская один тест за другим, но в то же время я старался учесть кеширование запросов в SQL Server'е. Не знаю, насколько мне это удалось, но я старался запускать каждый тест отдельно вразнобой, и с некоторыми временными интервалами. К сожалению, тесты с базой данных – это такая вещь, которую бывает трудно воспроизвести, результаты варьируются, но, надеюсь, показанные числа дадут хотя бы какое-то общее представление о порядках. В любом случае, вы можете скачать исходники, установить 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, не стоит заморачиваться.

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

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

Полезные ссылки:

Тестовый проект для сравнения производительности L2S, EF и AR/NH между собой и с классическим ADO.NET

Exploring the Performance of the ADO.NET Entity Framework - Part 1
Exploring the Performance of the ADO.NET Entity Framework - Part 2
ADO.NET Entity Framework Performance Comparison
Entity Framework Instantiation Times with a 120-Table Database
Linq to SQL vs NHibernate Part 1: What do they have in common?

Saturday, November 1, 2008

Будущее LINQ to SQL и Entity Framework

В одном из своих постов я имел неосторожность высказать мнение, что Entity Framework будет активно развиваться, а L2S (LINQ to SQL) – вряд ли, и что акцент разработки будет переведен на Entity Framework. К сожалению, мои опасения, похоже, становятся реальностью: Tim Mallalieu, Product Manager LINQ to SQL and LINQ to Entities сначала написал в блоге ADO.NET, что, цитирую:

«We’re making significant investments in the Entity Framework such that as of .NET 4.0 the Entity Framework will be our recommended data access solution for LINQ to relational scenarios. We are listening to customers regarding LINQ to SQL and will continue to evolve the product based on feedback we receive from the community as well»

А потом, в ответ на кучу комментариев о том, что «WTF? LINQ to SQL is dead?» он сделал дополнительный пост с разъяснениями, в которых повторил свои предыдущие слова, но постарался смягчить выражения:

«As mentioned, we have been working on this decision for the last few months. When we made the decision, we felt that it was important to immediately to let the community know. We knew that being open about this would result in a lot of feedback from the community, but it was important to be transparent about what we are doing as early as possible. We want to get this information out to developers so that you know where we’re headed and can factor that in when you’re deciding how to build future applications on .NET»

Так что, да, L2S будет жить и даже немного развиваться, но не с такой скоростью и не с такими инвестициями, как Entity Framework. Похоже, Microsoft сделала ставку на более тяжеловесный, но функциональный ORM. В свете последнего опроса на сайте того же Скотта Хенселмана, где оказалось, что L2S пользуется в 3 раза больше людей, чем EF, и по популярности он сравнялся с каличными датасетами, это решение выглядит немножко странным. С другой стороны, не стоит забывать, что L2S с нами уже почти год, на EF зарелизился только месяц назад. Я же считаю, что Microsoft нужно бы продолжать развивать L2S с прицелом на легковесность и производительность. Entity Framework – это, конечно, здорово, но все дело в том, что есть целый класс веб-приложений, в которых производительность превыше всего. Это, в первую очередь, сайты для массового обслуживания пользователей: электронные магазины, социальные сети, форумы и др. А здесь Entity Framework, увы, совсем не рулит. Кроме того, L2S можно рассматривать как реальную альтернативу датасетам, которые до сих пор используют многие. На деле же получается, что своим решением и маркетинговой политикой Microsoft может просто оставить L2S без существенной поддержки, а нас с вами – без мощного инструмента для быстрой работы с базой. Неужели придется возвращаться к DataReader'ам? :)

PS. Присмотритесь внимательнее к чарту с результатами опроса: ASP.NET MVC, который еще в бете, УЖЕ пользуется внушительное количество людей. Конечно, выборка не репрезентативна, так как Скотт работает в MVC тиме, и на его блог люди приходят целенаправленно, чтобы что-то узнать об этой технологии, но все же уже неплохо :)

Upd. Nov 9. Джули Лерман (Julie Lerman) дала дополнительные комментарии по этой теме здесь. В частности, она рассказала, откуда у Microsoft появилось 2 похожих ORM. Меня вот тоже волновал всегда этот вопрос :)

"LINQ to SQL grew out of the C# team. At the same time, the Data team was working on the EDM and Entity Framework. When both technologies were presented, it was a bit of a shock for each team to see that they had to products that, on the surface, were similar."

И еще пара слов о том, почему все-таки выжил Entity Framework:

"Eventually, LINQ to SQL became part of ADO.NET (it is a Data Access feature after all) and was handed over to the ADO.NET team. It wasn't the happiest of occasions for it's creators since the writing seemed already to be on the wall."

Видите, как все просто :)