четверг, 22 января 2009 г.

I'm not standard. Programming on paper

Продолжение "I'm not standard". Тов.Чистяков, утверждает, что я читаю большие проекты с бумаги.

Я не читаю большие проекты с бумаги. Я пишу свои проекты с помощью программирования на бумаге. Проекты, как мне представляется, большие для одного человека. Например, ядро последней версии SObjectizer -- это более 40 тысяч строк C++ кода. Еще один проект, написанный мной в одиночку, ObjESSty, это еще 50 тысяч строк. Написанный на Ruby Mxx_ru -- 10 тысяч строк. И есть еще во много раз больше написанного мной закрытого кода. Во всех моих проектах время от времени возникают ошибки, и я не могу сказать, что пишу очень качественный код. Но лучшей характеристикой качества моей работы я считаю фразу, сказанную начальником нашей службы техподдержки (занимающейся эксплуатацией написанных мной систем): "Только твой код я не боюсь запускать сразу на боевых серверах". Для меня это является доказательством того, что каким бы странным или диким не казался кому-то мой подход к написанию кода, этот подход в моем случае дает хорошие результаты.

Почему я использую бумагу? Так сложилось исторически. В 1989-1990 годах, в последнем классе школы у нас была одна пара в неделю по предмету "Информатика". Дисплейный класс из десятка БК-1001 один на пять школ нашего района. Первая часть пары - теоретическая, и только вторая - непосредственно за компьютером. И хоть тогда я написал всего две или три какие-то программы, их исходный текст был подготовлен заранее в тетрадке, а на уроке просто введен и отлажен. Потом были первые курсы университета. С обычным для того времени дефицитом всего, в том числе и машинного времени. У нас, если мне память не изменяет, было что-то около трех пар в дисплейном зале на две недели. Да и в принципе, на первых трех-четырех курсах машинное время для себя приходилось вырывать, иногда с боем, иногда хитростью. Хотя и потом, уже на работе в КБСП, году эдак в 1996-м, когда в наш отдел пришел новенький Pentium-166MMX с 32Mb памяти, было составлено расписание для того, чтобы выделять каждому сотруднику несколько часов в неделю для работы на этом супербыстром монстре...

Ну да вернемся к 1990-му году. Машинного времени было настолько мало, что придя на него неподготовленным, можно было успеть сделать только заданные лабораторные работы. А ведь хотелось большего! Тогда я последовал совету своего преподавателя, Александра Васильевича Лубочкина, о том, что программу сначала нужно писать на бумаге. И это сработало!

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

Разговоры о том, что так никто не делает, я слышу уже очень давно. Сколько программирую, столько и слышу. Некоторые из тех, кто так говорил, уже и сами не программируют. А я пишу свой код на бумаге. И он работает.

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

Когда программа начинает создаваться на бумаге, можно начинать с чего угодно. Половина функции может состоять из вопросов, а вторая половина быть набором рисунков, например, иллюстрирующих расщепление узлов B+ дерева. Но затем все это начнет структурироваться, начнут вырисовываться группы основных и вспомогательных функций/методов/классов, начнут формироваться зависимости по данным. И все это будет перед глазами, на 5-10 листах формата A4, которые можно разложить на столе и обозревать результаты собственного творчества с высоты "птичьего полета". В результате все обязательно сложится в законченную картину, станет видно, какой параметр куда нужно будет передать и зачем, какие куски кода повторяются слишком часто и как это дублирование можно устранить. Но и это еще не все.
 
Когда программа написана сначала на бумаге, она может очень неформальные комментарии. Ими могут быть схемы/рисунки на полях, какие-то небрежно сделанные заметки или пояснения. Или же большие фрагменты кода могут не содержать комментариев вовсе. Зато при вводе текста в редакторе появляется возможность писать толковые комментарии, описывающие что, для чего и как. Ведь все это уже известно, остается только сформулировать и записать. Поэтому при вводе кода с бумаги не возникает сложностей с тем, чтобы написать перед новым классом развернутый Doxygen-комментарий с полным описанием назначения класса, особенностей его работы и, даже, примерами его использования. Что самым благоприятным образом сказывается на качестве генерируемой по исходным текстам документации.

Но и это еще не все :). Еще несколько положительных свойств программирования на бумаге будут затронуты при обсуждении других вопросов.

I'm not standard. Intro

Один из отцов-основателей RSDN, Владислав Чистяков (aka VladD2), недавно сделал забавное противопоставление себя со мной:

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


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

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

После такой преамбулы можно перейти к обсуждению приписанных мне... ну, скажем, заблуждений.

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

Уже опубликовано:
Programming on paper
IDE?
Is C++ the best language?
Programmer's productivity
Add-on

воскресенье, 18 января 2009 г.

Две статьи о многопоточности, которые следует прочитать

Первая статья Hans-J. Boehm "Threads Basics". Очень хороший рассказ о том, что же из себя представляет многопоточность в современных условиях (многоядерные системы, переупорядочивание инструкций как на уровне компилятора, так и на уровне процессора). В статье определяются понятия sequential consistency и data races. Буквально на пальцах объясняется назначение такой штуки, как fence (барьер памяти), и рассказывается, в каких случаях она должна использоваться. В общем, сильная статья, которая раскрывает глаза на то, какая же это сейчас сложная штука - многопоточность.

В дополнение к этой статье имеет смысл прочитать FAQ, который поддерживается тем же Hans-J. Boehm и Paul McKenney. Но этот FAQ посвящен только C/C++. А так же в статье есть ссылочка на C/C++ библиотеку atomic_ops, которую разрабатывает автор статьи.

Вторая статья Herb Sutter  "volatile vs. volatile" коротко и четко описывает, чем же различаются volatile в Java/.Net и C++, а так же о том, чем будут различаться atomic-типы и volatile в будущем стандарте C++0x.

Так же по ходу разбирательства с упомянутыми выше статьями мельком ознакомился с еще одной статьей Hans-J. Boehm "Reordering Constraints for Pthread-Style Locks". Статья сложная, с леммами, теоремами и доказательствами. Я ее читал позно вечером, поэтому смог осилить только введение и заключение (в котором показано, насколько могут снизить производительность неразумно используемые fences (барьеры памяти)) :(.

среда, 14 января 2009 г.

What is the '3' for?

Я уже очень давно говорю о том, что Boost, при всей своей полезности и прочих достоинствах, черезмерно усложнен. Разобраться в его внутренностях могут, разве что, сами разработчики Boost-а. И вот, выясняется, что и они не всегда это могут: мегавопрос по Boost-у на RSDN ;)

вторник, 13 января 2009 г.

ЧистА функциональный пакетный менеджер

Некоторое время назад увидел на www.linux.org.ru анонс NixOs – дистрибутива Linux, который использует новый менеджер пакетов – Nix. На Новогодних каникулах попробовал прочитать про него подробнее. Прочел две статьи: Imposing a Memory Management Discipline on Software Deployment (2004-го года) и Purely Functional System Configuration Management (2007-го года). Статьи, как мне показалось, бестолковые – читаешь их, читаешь, букв много, а сухого остатка -- мизер.

 

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

 

/nix/store

`- 068q49…-ssh-5.0.0/

`- 07837d…-ssh-5.1.0/

`- 8372dh…-svn-1.4.4/

`- 973bs9…-svn-1.4.5/

 

С помощью настройки переменных окружения можно легко создавать профили с разными версиями установленных пакетов. Например, один пользователь хочет иметь ssh-5.0.0 и svn-1.4.5, у него в настройках профиля будет что-то типа:

 

PATH=$(PATH):/nix/store/068q49…-ssh-5.0.0/bin

PATH=$(PATH):/nix/store/973bs9…-svn-1.4.5/bin

 

А другой пользователь хочет пользоваться ssh-5.1.0 и svn-1.4.4, для чего его профиль настраивается иначе:

 

PATH=$(PATH):/nix/store/07837d…-ssh-5.1.0/bin

PATH=$(PATH):/nix/store/8372dh…-svn-1.4.4/bin

 

Эта идея в NixOs доведена до того, что подобным образом происходит работа и с конфигурационными файлами. Т.е. том, что любое изменение должно приводить к созданию отдельного каталога с уникальным именем, в NixOs возведено в принцип.

 

А почему, собственно, я заинтересовался пакетным менеджером Nix?

 

Ну, во-первых, я еще не расстался с мыслью о том, что для распространения C++ библиотек нужно иметь что-то типа RubyGems. Обязательно кросс-платформенное. И ориентированное, в первую очередь, на распространение исходников. Чтобы не приходилось иметь дело с монстрами вроде Boost – по сути, это куча мелких, почти независимых, библиотечек, для использования которых нужно тянуть к себе tar.bz2 почти на тридцать(!) мегабайт. Самые последние собственные идеи на этот счет я чуть более года назад описывал в списке рассылки SObjectizer. А тут интересный подход – добавление криптографического хэша в имя каталога с пакетом. Может быть, когда-нибудь пригодится.

 

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

 

Ну и, в-третьих, читая упомянутые выше статьи об Nix и NixOs, я вспоминал название какой-то статейки, которую видел в специализированном АСУТП-шном журнале в 1998-м или 1999-м году. Там речь шла об объектно-ориентированном(!) контроллере. Модно тогда было ООП (объектно-ориентированное программирование), вот и упоминали его к месту и не к месту. Сейчас становится модно функциональное программирование, вот и притягивают его за уши куда ни попадя. Чисто функциональный пакетный менеджер – это вам не хухры-мухры, это по взрослому J.

вторник, 6 января 2009 г.

SObjectizer vs Kilim

Некто Sujit Pal в своем блоге привел результаты сравнения производительности нескольких агентных фреймворков для Java (сравнивались Kilim, Jetlang, Actor Guild, ActorFoundry, а так же Scala Actors из стандартной библиотеки языка Scala). Двумя наиболее быстрыми фреймворками оказались Kilim и Jetlang. Поскольку в блоге были опубликованы исходные тексты тестового приложения для Kilim, то я сравнил скорость работы SObjectizer 4.4.0-beta6 с Kilim 0.50.

 

На моей машине (Intel Core2Duo 2GHz, 2Gb RAM, WinXP) Kilim-приложение отработало за 12.870 секунды, а SObjectizer-приложение – за 11.828 (обе цифры являются усредненными по нескольким запускам результатами). В качестве JVM использовалась клиентская Java-машина из состава Java 1.6.0_07. SObjectizer-приложение компилировалось с помощью Visual C++ 2005. В процессе работы Kilim-приложение потребляло до 170Mb памяти, а SObjectizer-приложение – до 80Mb. Вот исходный текст SObjectizer-приложения.

 

Результаты тестов меня, честно говоря, несколько расстроили. Я рассчитывал на более серьезное преимущество SObjectizer за счет использования нативного кода. Но, видимо, в данном тесте, где создается большое количество мелких объектов-строк, сборщик мусора в Java делает выделение динамической памяти гораздо более быстрым, чем в C++.

 

Тем не менее, результат не самый плохой. Особенно с учетом того, как сообщения отсылаются в Kilim и в SObjectizer. В Kilim актеры должны владеть ссылками на конкретные объекты mailbox-ы, непосредственно в которые сообщения и помещаются. Тогда как в SObjectizer при отсылке сообщения есть только строковое имя агента-владельца сообщения, по которому SObjectizer сам находит получателя сообщения. Т.е. при отсылке сообщений в SObjectizer4 выполняется больше работы, чем в Kilim. Кроме того, в SObjectizer существуют понятия состояний и событий, которых нет в Kilim. А на работу с этими сущностями в SObjectizer так же тратится время и ресурсы.

 

Отдельно хотелось бы отметить один важный, на мой взгляд, момент. В упомянутых Java/Scala фреймворках процесс выборки сообщений агентами выглядит очень примитивным. Это обычный цикл, в начале которого из mailbox-а извлекается очередной объект-сообщение, после чего в виде if-ов, switch-ей или pattern matching-а (как в Scala) определяется, что же с этим сообщением нужно делать. Лично мне такой подход не нравится, я считаю его утомительным в написании, он череват ошибками и он сложен при сопровождении чужого кода. Я в очередной раз убедился, что лучше, когда фреймворк берет на себя задачу сопоставления очередного сообщения конкретному методу объекта.

 

Проведение данного сравнения так же привело меня к следующим выводам:

 

1. Ясно видно, что модель актеров сейчас стремительно набирает популярность. Об этом свидетельствует рост внимания к языку программирования Erlang, большое количество рассуждений на тему удобства модели актеров в блогах, растущее количество агентных фреймворков для Java. И это хорошо, т.к. формируется благоприятная для SObjectizer среда.

 

2. Если в области управляемых (managed) языков вроде Java/Scala/Python/Ruby наблюдается рост разнообразных (по мнению их авторов) агентных фреймворков, то для C++ такого роста не видно вообще. Что, на мой взгляд, благоприятно для SObjectizer – C++ умирать пока не собирается, и в C++ приложениях можно получить выгоды от использования модели актеров. Поэтому имеет смысл развивать SObjectizer именно как C++ инструмент.

 

3. В SObjectizer нужно вводить механизмы обмена сообщениями, которые не требуют поиска получателя сообщения по имени. Например, что-то типа mailbox-ов, как в Kilim, или каналов (channels, pipelines). Т.е. если двум агентам нужно обмениваться сообщениями друг с другом, они создают объект-канал, ссылка на который есть у обоих. И для отсылки сообщений они используют не send_msg, а метод записи сообщения в канал. Тем более, что каналы могут применяться так же для контроля за переполнением очереди сообщений агента.

воскресенье, 4 января 2009 г.

Очередные мысли о защите агентов от перегрузки

На днях через ленту новостей получил ссылку на интересный блог-пост. Суть в том, что Erlang-приложение, которое отлично работало на 2-х ядерной машине, при переносе на 8-ми ядерную машину начало падать из-за нехватки памяти. Причина оказалась в том, что один из медленных Erlang-процессов успевал разгребать свою очередь сообщений, когда его нагружали процессы с 2-х ядер. Но как только инициаторы сообщений получили в свое распоряжение 8-мь ядер, они стали генерировать так много работы для этого медленного процессора, что его очередь заполнялась быстрее, чем он успевал ее чистить. В результате очередь сообщений сжирала всю свободную память и все приложение падало.

 

Этот пример является хорошим показателем того, насколько важно в агентной системе следить за тем, чтобы каждый агент получал не больше сообщений, чем способен обрабатывать. Т.е. важность задачи overloading control. Которая в SObjectizer4 пока не имеет штатного решения. В поисках такого решения родилась очередная идея.

 

Пользуясь случаем, хочу еще раз выразить благодарность Дмитрию Вьюкову (aka remark), который первым обратил мое внимание на важность данной проблемы.

 

PS. Упомянутый мной блог-пост примечателен еще одним наблюдением. Люди написали на Erlang-е некую message switching platform (чтобы под этим не понималось), производительность которой на 2-х ядрах составляет 140 транзакций в секунду. Что они считают достаточной. При переходе на 8-мь ядер они получили прирост до 700 транзакций в секунду. Что они оценивают как хорошую масштабируемость. Сложно, конечно, оценивать их производительность, не зная, что именно они делают. Но, учитывая, что при 700 т/cек. узким местом были ни БД, ни другие компоненты системы, можно сделать вывод, что 140 т/сек. – максимум, который дает именно Erlang, далеко не самый быстрый язык программирования. Т.е. если бы они взяли вместо Erlang-а что-то более производительное (в особенности C++ и SObjectizer), то изначально могли иметь показатели в районе 500-600 т/сек. Но, видимо, это хороший пример, когда скорость работы программистов гораздо важнее скорости работы создаваемой им программы. И это компенсируется более производительным железом…