Wednesday, December 31, 2008

Немножко об уличных фонарях

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

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

Полтава

Picture 098  Picture 117

 Picture 115

Киев

Picture 119 Picture 063

Днепропетровск

Picture 016

Коломыя

IMG_2706

Львов

IMG_2584

Санкт-Петербург

IMG_4988 IMG_5416

Москва

Picture 509 Picture 558

Иркутск

Picture 470

И наконец, Харьков

IMG_8279 IMG_2966 IMG_3217 

Picture 077 IMG_3210

IMG_3210_kvadrat_bw

Еще раз поздравляю всех с Новым годом и Рождеством! :)

Tuesday, December 30, 2008

Разминка мозга №1. Числа и Новый год

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

Задачка изначально была совсем не новогодняя, но я решил, что надо ее видоизменить в связи с праздниками, а заодно и запутать вам возможность поиска ответа в великом и ужасном ;)

Итак, представьте себе: Жил да был на свете Дед Мороз (он же – Санта Клаус). Да, это тот самый дедушка, который на Новый год приносят хорошим деткам конфеты и мандарины, а взрослым – iPod’ы и Wii. Что, вам не принесли? Ну, значит плохо себя вели ;)

Так вот, у Деда Мороза есть олени, много оленей (N). Чтобы не запутаться в них, он их пронумеровал положительными целыми числами, причем так, что нет двух оленей с одинаковыми номерами. Правда, номера могут начинаться не с 1 и иметь пропуски в числах. И вот стоят эти олени в длинном хлеву в стойлах, один за другим, без какой-либо последовательности (номера идут в произвольном порядке). Дед Мороз может ходить мимо этих стойл только в одном направлении, то есть заходит с одной стороны и идет до самого конца, потом обходим хлев снаружи и заходит снова со входа. И вот у нашего дедушки появился новый олень (Снегурочка подарила на день рождения, например). И захотел дедушка наконец-то навести порядок в своих оленях и присвоить новому оленю самый маленький номер из тех, что у него нет. Ну, чтобы заполнить пробелы. То есть ему нужно обойти своих подопечных (только в одну сторону, сколько угодно раз), определить минимальный положительный номер, которого нет у существующих оленей, и присвоить его новоприбывшему.

Однако, дедушка стар, и ваша задача – помочь ему найти это число за минимальное количество проходов по хлеву. На беду, у дедушки еще одна проблема – у него не очень хорошая память, он может запомнить лишь несколько чисел (для определенности положим не больше 5), а писать он не умеет. Зато дедушка умеет считать :) Складывать, вычитать, умножать и делить. Думаю, он умеет еще извлекать корни и решать интегральные уравнения третьего порядка, но к нашей задаче это отношения не имеет :)

Вот, собственно, и все. Прошу алгоритмы в комментарии, товарищи программисты и не только. Чур, код не писать, покажите, на что вы способны без компилятора ;)

PS. С наступающими вас! Пусть 2009 год даст новые идеи и мечты, а также энергию и устремление для их реализации! И, конечно, счастья, здоровья и любви вам и вашим близким!

Monday, December 22, 2008

Доклад на харьковской UNETA

Судя по всему, я буду одним из докладчиков на следующей харьковской UNETA, которая ориентировочно будет в начале-середине января. Тема у меня будет не совсем обычная, она будет связана с производительностью ORM. Точно будут L2S и EF, возможно, добавлю что-то еще, если хватит времени. Так как тема не связана с какой-то конкретной технологией, скорее, это разбор и сравнение, то я могу немного варьировать ее содержание. В связи с этим у меня возник вопрос, что бы вам хотелось услышать? В принципе, скелет доклада я уже продумал, но все равно есть место для различных деталей, тестов, которые я могу провести и т.д. Я постараюсь учесть все пожелания и осветить их в докладе по-максимуму.

В докладе я постараюсь рассказать о том, как работают рассматриваемые ORM внутри, что именно влияет на их производительность, проведу некоторые наиболее интересные и показательные тесты для их сравнения между собой и с чистым ADO.NET, и постараюсь рассмотреть генерируемые каждой ORM запросы.

Итак, у меня есть несколько вопросов к вам:

  1. Стоит ли включать в доклад NHibernate? Или достаточно будет L2S и EF?
  2. Какие тесты вы бы хотели увидеть?
  3. Какие сопутствующие темы вам были бы интересны? Что из неописанного выше можно осветить еще?

Ответы прошу оставлять в комментариях. Прошу поучаствовать в опросе не только харьковчан, которые планируют идти на UNETA, но и всех, кому это интересно. После доклада я сделаю отдельный пост, посвященный результатам, в котором выложу и презентацию, и тесты, и их результаты.

Спасибо за помощь :)

Saturday, December 13, 2008

Дао программиста

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

Технологии определяют “чем” мы работаем, т.е. это набор инструментов: языки программирования, базы данных, среды, тулзы, библиотеки классов, фреймворки, компоненты и т.д. То есть, если взять аналогию с токарем, это те инструменты и материальные приспособления, которые он использует для изготовления деталей.

Подходы же определяют “как” мы используем все эти инструменты и пишем код, т.е. в случае программирования это понимание и правильное использование ООП, SOLID, паттернов проектирования (GoF, Fowler), каких-то других Patterns & Best Practices, архитектурных принципов, рефакторинга, алгоритмов, структур данных и т.д. Сюда же можно отнести и знание и использование таких практик, как TDD, DDD и пр. В том числе это и тот опыт, который мы приобретаем в процессе работы. В случае с токарем это его умение вытачивать те или иные детали, которое он тоже совершенствует.

У каждого программиста эти 2 компонента развиты в разных соотношениях. Есть ребята гармонично развитые, которые, например, не только знают о том, что есть такая штука как ООП, но и правильно его используют. Есть отклонения в ту или иную сторону.

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

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

Неужели это конец развития? Нет, конечно, это только начало. Во-первых, нет предела совершенству, и знание и понимание тех же технологий можно и нужно увеличивать. А во-вторых, когда программист переходит на уровень архитекта, у него начинаются уже свои головные боли и дальнейшее развитие, но уже не как программиста. Есть application architect, solution architect, enterprise architect, etc. и у каждого свои навыки и знания. Про дао архитекта я пока написать не могу, извините, не дорос еще :) Но оно есть, я в этом уверен.

К чему я все это веду? К тому, чтобы все понимали, что в работе программиста важны и технологии, и подходы, что даже отличное знание технологий еще не делает программиста инженером. На одном крыле далеко не улетишь. Ну и чтобы каждый мог понять хотя бы приблизительно, на каком уровне он сейчас находится и увидел, что именно нужно развивать. Благо, книги есть не только по технологиям, но и по подходам. Изучайте ООП, паттерны, рефакторинг, всякие TDD/DDD/Agile/прочее, это чужой опыт, который может помочь вам стать настоящим профессионалом. Успехов!

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?