пятница, 13 мая 2011 г.

Groovy - обработка текстовых файлов



Недавно на работе в рамках написания функциональных тестов на шедулеры встретилась такая задача.Есть CSV-файл с (тестовой) информацией о заказах, в нем две колонки, в первой дата, во второй - число, представляющее quantity на эту дату. 

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

def path = "source.csv"
def pathNew = "target.csv"

def oldFile = new File(path)
def newFile = new File(pathNew)
newFile.withWriter { out ->
  oldFile.eachLine { line ->
    out << line.replaceAll(/,(\d)*/, {x, y -> ',' + (x[1..(x.size()-1)].toInteger() * 3).toString()} ) << '\n'
  }
}




суббота, 7 мая 2011 г.

addconf 2011 - долгое послевкусие

Типа вступление :)

Как многие знают, 29-30 апреля в Питере прошла конференция Application Developer Days 2011 (http://addconf.ru/add_2011). Впечатления у меня о ней остались сугубо положительные, но пока они еще немного сумбурные. Я сам выступал там с докладом по масштабированию систем управления поставками (http://addconf.ru/event.sdf/ru/add_2011/authors/MikhailAntonov/269), ходил на некоторые, наиболее мне интересные, доклады, и изрядно пообщался в кулуарах с интереснейшими людьми. Так же, очень хорошее впечатление оставила организация (и организаторы) конференции, да и Питер в целом. Но обо все по порядку. Погнали.

Мое выступление

Если вам интересны слайды, рекомендую подождать, пока Стас Фомин и Андрей Майоров выложат обработанное видео на офф. сайте конференции, там будут и слайды, и крупные планы, и выражение моего лица в ключевые моменты, и все такое.

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

Что было? Было внимание ко мне и тому, что я говорил, со стороны присутствующих в зале, не было (кажется :)) людей, сидящих в первом ряду и втыкающих  в нетбуки, айфоны и айпэды, было немало вроде бы искренне-заинтересованных вопросов после моего доклада. Было обсуждение на час-полтора в коридоре после выступления, на котором мы обсудили кучу интереснейших, но уже довольно узкоспециальных вещей (вроде оптимизации тяжелых баз данных на Oracle,  бизнеса управления поставками, data mining-a, статистического анализа, бизнес-логики на Groovy и почему это не совсем маразм, и еще кучу всего). В итоге собрали неплохую такую кучку в кулуарах, даже из JetBrains некоторые ребята подтянулись послушать и пообщаться :) Были люди, подходившие ко мне после доклада, хлопавшие по плечу и говорившие - "чувак, ты реально зажег". Были, наконец, какие-то упоминания в паре блогов и в твиттере.

Тем подозрительнее тот факт, что особенной критики в свой я (пока) не слышал. Может, просто не натыкался еще, или критики мой доклад проигнорировали, и писать отзыв им стало лениво (а жаль...). В любой случае, конструктивной критике буду рад.

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

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

А так - все было классно. Не считая некоторого волнения - получил искреннее удовольствие от выступления, от ответов на интересные, по существу, вопросы в конце и от общения с аудиторией. Надеюсь, она тоже свою порцию удовольствия получила :)

Запомнившиеся доклады

Первым докладом, на которые я пошел, был доклад Максима Мазина о  LOP - Language Oriented Programming и MPS (Meta Programming System) - продукте JetBrains, позволяющим, гм, визуально, конструировать DSL языки (и надстройки над существующими полноценными языками). Если мне не изменяет память, это был тот же самый доклад, который Максим рассказывал на JavaOne в Москве за две недели до этого, но, поскольку на JavaOne я его пропустил - в этот раз сходил на него и не пожалел.

Для меня это был один из самых интересных докладов, т.к. мы тоже занимаемся DSL (точнее, начинаем заниматься) в своем проекте, и тут любые знания и любой опыт кстати. Сам доклад был живой и интересный, но конечно, за отведенные 40 минут рассказать все было нереально. Именно потому после этого доклада большая толпа народа клубилась возле стойки с расшаренными мониторами в коридоре, выпытывая у Максима детали, наблюдая, как он вживую, при нас, реализует простейшую DSL-надстройку над Java (добавляет поддержку RW-lock на уровне синтаксиса), потом допиливает ее, добавляет туда поддержку Java-вских типов и прочее. Это общение о DSL-ах вообще и MPS в частности оставило очень хорошие воспоминания. Всегда приятно пообщаться со знатоками.

До этого я слышал про MPS, но разве что краем уха. Мне понравилась сама идея JetBrains - создать платформу / инструмент для облегчения разработки DSL-ей, и понравились те возможности, которые она дает. Стало даже немного обидно, что мы используем Eclipse и сильно на него завязаны :) Ну да ладно, JetBrains в любом случае молодцы, что смело вкладываются в продуктизацию таких черномагических (для многих) вещей, как метапрограммирование и DSL. Удачи вам в деле проталкивания этого на большой рынок!

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

После этого немного побродил по коридорам, поприставал к ребятам из JetBrains, связанным с Resharper-ом, и пошел на доклад Яши Сироткина - "программирования для самых маленьких". Что тут сказать - класс и уровень докладчика виден сразу, основатель российского JUG-a все ж таки. Доклад шел бодро и четко, внимание зало было все на Якове. Ну а фраза ближе к концу "АВТОБУС ! НЕ ! ПРИДЕТ !" была отретвитчена много раз и гуляла из уст в уста еще долго.
В общем, доклад скорее из серии отвлеченно-философских, при этом живой, интересный и полезным. Основная идея доклада - то, что в программировании важен не процесс как таковой (Agile ли у вас, или что угодно другое), а получение реального результата, быстро, и укладываясь в бюджет - для меня в общем-то была очевидной, но тем не менее, очень важной. Неожиданно понял, что я видел немало людей, сфокусированных на разнообразных процессах. Процессе багтрекинга, подготовки отчетов о проделанной работе, процессе Continuous Integration, процессе code review, семействе процессов Agile, и все такое - вместо того, чтобы быть сфокусированными на поставке работающего софта. Собственно, я и сам бывают, увлекаюсь процессами (т.к. верю, что правильный процесс в малых дозах способствуют достижению результата) - так что я решил для себя, что отныне буду осторожнее с процессами.

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

После обеда заглянул на доклад Жени Кирпичева, где он рассказывал про хитрый инструмент собственной заточки для анализа и визуализации логов, который помогает анализировать профили загрузки на кластерах (основное его применение, как я понял). Доклад был ничего так, много любопытных графиков, из которых Евгений извлекал скрытую информацию и разъяснял остальным. Я рассчитывал поймать его после доклада, т.к. знаю что он адепт функционального программирования, редактор журнала fprog.ru, хаскеллист, знаток хайлоада и вообще незаурядная личность, но он, к сожалению, быстро раскланялся и удалился. Ну ладно, как-нибудь еще пересечемся.

Доклад моего бывшего коллеги Андрея Реброва мне тоже понравился. Четко, сжато, быстро, обзорно по основным яваскрипт-фреймворкам для мобильной разработки. JQuery Mobile, Sencha Touch и иже с ними (и даже фреймворк с неприлично выглядящим для русского глаза названием XUI :)). Хороший такой доклад, и рассказан был живо и энергично. После него отметил для себя, что раз уж мы активно используем ExtJS, то и на Sencha Touch надо бы взглянуть попристальнее.

На рассказ Сергея Туленцова по MongoDB немного опоздал, и определенно часть недопонял / упустил, но то что я услышал мне понравилось. По сути, как я помню, они пишут приложения на Вконтакте, которое собирает от пользователей ответы на странные вопросы типа "Как вы думаете, способен ли ваш друг А выпрыгнуть из штанов ради внимания от девушки Б". Короче, приложение по своей полезности как раз под целевую аудиторию контакта. Но доклад был хороший, интересный. Я даже искренне принял докладчика за одного из коммитеров / разработчиков этой  MongoDB - что, определенно, для него большой комплимент.

Ну и последний доклад на котором я был в этот день был доклад от DZ о взаимоотношениях с заказчиками. О программировании там не было ровным счетом ничего, но всем понравилось - уверен, на 99% из-за личности / харизмы самого DZ.

Вечером я гулял по ночному Питеру далеко заполночь, и как результат, во второй день проспал и пропустил первый блок докладов.

Успел, хотя немного и не с начала, на круглый стол, организованный HeadHunter / IT-Dominanta / JetBrains, посвященный состоянию IT-индустрии в Питере. Понял вилки местных зарплат, основные схемы работы компаний, и почему JetBrains перевозит часть своих разработчиков из Питера в Мюнхен, в Германию.

После чего были опять кулуары, коридоры... Заглянул на доклад Андрея Карпова, посвященный статическому анализу кода на С / С++. Интересные примеры трудно находимых ошибок в популярных продуктах, таких, например, как Miranda. Доклад был хорош, и продукт видимо тоже - неясным лично для меня осталось только, как извлекать прибыть используя этот инструмент :) Точнее даже так - насколько часты и критичны те ошибки, которые не отлавливаются юнит-тестами, но которые отлавливаются статическим анализом, и как правильно установить процесс ревью этих ошибок, чтобы получать от этого бенефиты, не тратя слишком много времени на сам процесс (см. доклад Яши Сироткина о том, что процесс ради процесса - зло).

Дальше был отличный доклад Владислава "VladD2" Чистякова, тех. редактора журнала RSDN и по совместительству, главного нынешнего разработчика языка Nemerle. Доклад сам был хорош - единственное замечание по существу - ощущение такое, что аудитория не тянула уровень доклада, и докладчик это в общем понимал, из-за чего перескакивал со слайда на слайд, уходил в разъяснение каких-то деталей  и прочее. Но, повторюсь - лично мне было очень интересно.

Дальше был обед, и после него мой собственный доклад об управлении поставками, а после него часа на полтора обсуждения в кулуарах, из-за чего я, к сожалению, пропустил вторую часть доклада Влада о Nemerle, и успел только на окончание, в котором Денис Рысцов и Влад демонстрировали написанный на Nemerle.PEG парсер JSON-a. Было круто, но по моему, подавляющая часть аудитории не понимала до конца, что, и, главное, КАК там происходит :)

Ну и наверное, это все что мне запомнилось по докладам.

В кулуарах, люди, докладчики и организаторы

 Из докладчиков и просто людей, с кем пообщался - запомнились все тот же вездесущий Яков Сироткин, с которым я до этого оффлайн никогда не встречался..Запомнился Максим Мазин, с которым плодотворно поговорили про языки программирования, метапрограммирование, DSL-языки, грамматики, и в конце - про возможность проведения в Питере конференции по Groovy (тут спасибо Лере Андриановой, которой эта идея возможно, сразу показалось немножко бредовой, но она вроде бы восприняла ее позитивно :)). Еще запомнился Максим Цепков, которому была очень близка моя предметная область, и с кем мы ее обсуждали до и после моего доклад, VladD2, от которого я узнал много интересного о том, что происходит сейчас с Nemerle. Еще запомнились многие, но тут уже перечислять поименно не буду, извините.

Разве что, не могу не сказать про Стаса Фомина, Андрея Майорова и Владислава Орликова. Все они оба дня из кожи вон лезли, чтобы обеспечить нормальный ход конференции, успевая при этом ходить на доклады, постить воодушевляющие твиты, обсуждать технические тонкости в кулуарах. Респект. Стас даже дал мне на один вечер свой запасной ноутбук для дополнительной подготовки (спасибо!).

Организация
Ничего плохого не вспоминается, а значит, все было на самом деле хорошо. Особенно хорошо было то, что иногородние докладчики жили в той же гостинице "Карелия", где проходила конференция - т.е. не надо было вставать спозаранку и ехать до места проведения. Это может быть и мелочь, но очень важная. Про бутерброды, кофе, обеды ничего писать не буду, ибо всем это уже надоело. Скажу только - все было на уровне.


Единственное вот - может, не стоило начинать конференцию так рано, в 9-15? Все таки среди программистов много сов :)

Волшебный город на Неве
Ну и, несмотря плотное расписание конференции, я каждый вечер, включая дни приезда и отлета, гулял по ночному Питеру. Невский, Площать Восстания, Дворцовая, Аничков мост, канал Грибоедова, Фонтанка, Исаакиевский собор.. Питер есть питер. Коренные петербуржцы - цените то, что вы живете в одном из красивейших городов мира!

суббота, 23 апреля 2011 г.

Java и JNI - меняем java.library.path в runtime

В подмножестве экосистемы Java, относящейся в основном к JNI (без которого никуда не деться, если приходиться интегрироваться с каким-то legacy или просто редким и специфическим кодом, написанном на С или каком-то другом языке), есть такое понятие, как java.library.path. Вкратце, это в некотором роде аналог classpath, только не для Java классов и *.jar файлов, а для нативных библиотек - системное свойство, которое указывает JVM, где искать эти самые нативные библиотеки (.dll  в винде или .so под юниксами).

Свойство это устанавливается один раз, перед запуском JVM, через глобальные system properties, или как ключ -Dname=value для JVM, и после этого оно становится read-only. Точнее, менять-то его можно, но никакого эффекта на работу программы это не окажет, т.к. после того как вы обновите это свойство, JVM не перечитает его и не будет использовать новое значение.

Однако, возможность менять java.library.path на лету была бы очень кстати - тогда бы не пришлись много раз генерить, переписывать и перезаписывать скрипты для запуска JBoss-a, например, чтобы отразить в них все нужные пути ДО старта аппсервера.
И такая возможность, изменять эти пути, по которым JVM ищет нативные библиотеки, на самом деле есть. Подробно это объяснено тут:

http://blog.cedarsoft.com/2010/11/setting-java-library-path-programmatically/
и еще вот тут:

http://nicklothian.com/blog/2008/11/19/modify-javalibrarypath-at-runtime/

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

static {
  try { 
    System.loadLibrary("dp-integ");
  }
}

Этот вызов внутри выглядит так:

public static void loadLibrary(String libname) {
  Runtime.getRuntime().loadLibrary0(getCallerClass(), libname);
}
И далее

synchronized void loadLibrary0(Class fromClass, String libname) {
  // Проверяем, разрешено ли загружать данную конкретную библиотеку
  SecurityManager security = System.getSecurityManager();
  if (security != null) {
    security.checkLink(libname);
  }
  if (libname.indexOf((int)File.separatorChar) != -1) {
    throw new UnsatisfiedLinkError("Directory separator" +
      "should not appear in library name: " + libname);
  }
  ClassLoader.loadLibrary(fromClass, libname, false);
}

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

// The paths searched for libraries
static private String usr_paths[];
static private String sys_paths[];

Код метода ClassLoader.loadLibrary(fromClass, libname, false), довольно длинный, и загроможденный многочисленными проверками, в сокращенном виде выглядит это так.
// Invoked in the java.lang.Runtime class to implement load and loadLibrary.
static void loadLibrary(Class fromClass, String name, 
        boolean isAbsolute) {
    
  ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader();
  if (sys_paths == null) {
    // это то, что нам нужно
    usr_paths = initializePath("java.library.path");
    
    // а это для тех библиотек, которые загружаются из классов,
    // загруженных из boot classpath.
    sys_paths = initializePath("sun.boot.library.path");
  } 

  // Дальше попытка загрузить библиотеку, и дальше,
  //  если найти ее так и не удалось, то -  

  // Oops, it failed
  throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
}


Таким образом, теперь механизм загрузки нативной библиотеки стал более понятен.

Вы можете либо выставить в null свойство sys_paths у класслоадера, либо просто поменять его, добавив к нему нужный путь к вашим нативным библиотекам.

вторник, 19 апреля 2011 г.

ADD Conf 2011 - Масштабирование систем планирования и управления поставками

Как я писал в твиттере и как некоторые знают, я буду выступать на конференции ADD Conf 2011, Питер, 29-30 апреля сего года :).

Посоветовавшись с организаторами конференции, решил заранее выложить у себя презентацию для доклада (презентация будет меняться по мере того, как я буду получать фидбеки, делаясь лучше и лучше).

Соответственно, мне интересны любые фидбеки - по содержанию, оформлению, что лучше осветить поподробнее, что лучше вовсе убрать и т.д.

Ваше мнение важно для нас (С)

четверг, 7 апреля 2011 г.

Groovy вместе (и вместо) c Java в production. Part 1

Этот пост - некоторое мое размышление о текущем проекте, может кому-то будет просто интересно. А может, у кого-то есть здесь опыт именно в таких вещах, и он прокомментирует. А может, у кого-то просто очень богатый жизненный и профессиональный опыт, и он даст "мудрый совет"  (tm) . Итак.

В последнее время стараюсь сойти с прямых и строгих рельс стальных рельс Java на языки под платформу JVM. Приложение - платформа для систем управления поставками (логистика, розничная торговля и прочее), коотрая включает в себя разные компоненты для интеграции, расчетов прогнозов (статистические и эвристические методы) , планирования заказов, трекинга перевозок и еще много чего. Написана почти вся на Java (с примесью, как водится, SQL, HTML/CSS/Javascript, + в отдельных компонентах встречается флеш и питон). Пока что мы склоняемся к тому, чтобы окончательно выбрать Groovy. Почему же?



Какие были требования к новому дополнительному языку, и в каких местах мы бы хотели его использовать?


1) Тесты. Написание тестов, особенно таких, которые работают в сервлет-контейнере и базой данных, занимает много времени. По разным причинам. 


Первая, это то что сам язык Java весьма громоздкий, и даже отличная поддержка IDE мало помогает. Данные приходится либо загружать в базу из CSV-файлов, либо создавать руками на лету. И то и то требует много дополнительного scaffolding code, писать который неприятно, а на Java, с ее отсутствием поддержки для коллекция, замыканий, Path Expressions и прочих фич так и вовсе утомляет (когда появляется множество тест-классов каждый по 700-1000 строк кода).


Вторая причина довольно специфическая.  Написание контейнерных тестов на Java и их дописывание / отладка часто требует рестартов appserver-a  (у на это JBoss), который занимает 3-4 минуты. Собственно, любое изменение кода, кроме изменения "кода внутри методов" требует рестарта JBoss-a. Поменял название поля класса? Перезапусти JBoss. Поменял тип параметра метода с float на double? Перезапусти JBoss. И так все время.


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


3) Поддержка метапрограммирования - в понятном, не слишком зубодробительном виде, но достаточная, чтобы создавать DSL-языки для таких вещей, как моделирование иерархических структур данных в БД, построение экранов с отчетами, конфигурации портлетов - в общем того, что в мире Java-энтерпрайза традиционно программируется в XML-e.


Вообще,  "программирование в XML" - это совершенно отдельная вещь. Это именно то, что Джоэль Спольски называет leaky abstraction., только в чуть расширенном понимании. До тех пор, пока вы пишете много простых, тривиальных и однотипных кусков чего-то (чего-угодно), вы на коне, и ваша продуктивность приятно радует менеджеров. Это при условии, конечно, что автор соответствующего фреймворка (для парсинга этих XML-описания и построения по ним каких-то реальных вещей) правильно предвидел наиболее типичные случаи использования, и сделал для них нормальную поддержку.


Но как только вам надо сделать что-то такое, что автор соответствующего фреймворка не предусмотрел - ждите проблем - появления множества кода с тегами // HACK, // REVISE LATER, // FOLLOWUP, и долгих часов обсуждений, а можно ли это поддержать внутри фреймворка, а сколько на это понадобится времени, чтобы это реализовать, а сколько это будет стоить, а достаточно ли это нужная и важная фича, чтобы ее поддерживать на уровне фреймворка, и прочее, и прочее. А Тикет в джире все это время висит и висит c тегом "Being investigated".
Пока это все с причинами. Теперь, так какой у нас есть выбор?

1) Очевидно, хотелось бы что-то компилирующееся в Java-байткод. Лучше конечно, чтобы была полная двусторонняя интеграция с Java на уровне байткода JVM.
2) Предлагаюших писать на Lisp или Haskell в команде не нашлось.
3) Можно брать языки, портированные под JVM - JRuby, Jython. Jython мы используем кое-где, но только для скриптинга и работы с файлами в основном. Имело бы смысл, если бы кто-то из нас хорошо знал Ruby или Python. Таковые есть, но мало. Не айс.
4) Наконец, можно писать на языках, специально написанных под JVM, с синтаксисом, более похожим на Java, с явным упором на отличную интеграцию с ней, но перенявших фишки языков из предыдущих пунктов. Таких нашлось два - Groovy и Scala (есть еще Clojure, и недавно появившийся Morah, но это пока слишком экзотично).

Scala пока что отпала (к некоторому моему сожалению) по ряду причин - сложновата концептуально, заметный упор в функциональное программирование, выше порог вхождения, круче кривая изучения, меньше (вероятно) ореол использования. Плюсов у нее тоже немало, но в итоге они не перевесили.

Как итог, пока что медленно и осторожно пробуем интегрировать код на Groovy в намеченных областях, и кое-какие выводы уже можно сделать.


Плюсы:
 - Возможность перекомпилировать и перезагружать любые изменения в коде классов под JBoss, в отличие от Java hotswap, который умеет перезагружать "на лету" только изменения кода внутри методов класса. Мелочь, а дух захватывает.
 - Поддержка на уровне языка на списков, мэпов, регулярных выражений, замыканий, GPath-итерации по сложным структурым..метаобъектный протокол, билдеры иерархий, и еще много всего, что есть в Python-e и Ruby  и что делает код короче в разы и приятнее в чтении.


Минусы:
 - Динамическая (опциональная) типизация. Иногда удобно, но ненавижу эти no such property, no method found applicable for the following arguments:... и прочие обратные стороны медали под назвнанием "Rapid Software Development".
 - Редакторы кода и  поддержка IDE. Есть, и неплохая - но намного, НАМНОГО хуже чем для Java в современных IDE. Понятно, что язык динамический и многого не накрутишь..но все же (JetBrains - я в вас верю!!).


Часто упоминаемая на разных форумах и в блогах "низкая скорость выполнения" - не знаю, не заметил. Если вы часто вычисляете ряды фибоначчи (вместо того, чтобы раз посчитать и хранить), вычисляете фракталы Мандельбродта или перемножаете матрицы размером 10000 х 10000 - да, у вас вероятно будет много проблем. Но в типичном Enterprise Java приложении, я так думаю, не менее 60-80% кода можно переписать на Groovy без заметной потери производительности.


Да - мои мысли в этом посте немного связаны с этими.

воскресенье, 3 апреля 2011 г.

Мемоизация рекурсивных замыканий в Groovy - FIXED

Я и забыл написать, чем кончилась вся так история с мемоизацией рекурсивных замыканий - все там оказалось в итоге довольно просто, и бага, который я подозревал, там нет. После 3 минут общения с Jochen-ом (это техлид проекта Groovy), все как-то встало нормально на свои места.

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

def fact {it -> sleep(100)it ?  it call(it 1g1g}
fact fact.memoize()
println fact(70)
println fact(71)

Что же происходит в этом случае "за сценой"? Если скомлировать этот код, и декомпилировать получившийся байткод в Java-код, для простоты понимания,  то это будет выглядеть (схематично) примерно так:

class Closure1 extends MemoizedClosure {
   call (param{
       bla-bla;
       return param * this.call(param-1); // this тут значит Closure1
   }
 } 
}

Т.е. можно видеть, что вызов call() в теле замыкания всегда обращается к тому объекту замыкания, внутри которого (в лексическом контексте которого), он вызывается. Т.е. в нашем случае это всегда Closure1, а не тот враппер, умеющий делать мемоизацию, который возвращает вызов fact.memoize() (да, метод memoize() всегда возвращает новый объъект замыкания).

А как следует правильно записать это мемоизируемо-рекурсивное замыкание? А вот так:

def fact
fact {it -> sleep(100)it ?  it * fact(it 1g1g}
fact fact.memoize()
println fact(70)
println fact(71)

Обратите внимание на выделанное. Мы определяем переменную fact, и присваиваем ей ссылку на создаваемый объект замыкания, после чего создаем враппер для нашего замыкания и присваиваем ссылку на этот враппер переменной fact. И, поскольку внутри тела нашего  замыкания мы не вызываем его метод call() напрямую, а вызываем fact(), то у нас возникает возможность направить рекурсию по правильному пути - т.е. все рекурсивные вызовы теперь будет проходить через наш fact (т.е. через враппер с поддержкой мемоизации). 

В Java-коде это будет (схематично) выглядеть так:

class Closure1 extends MemoizedClosure {
  // эта ссылка будет указывать на fact, т.е. на closure wrapper,
  // который поддерживает мемоизацию.
  private Closure reference 

   call (param{
       bla-bla;
       return param reference.call(param-1);
   }
 } 
}
Желающие могут запустить исправленный код на http://groovyconsole.appspot.com/ и убедиться в его правильности :).

воскресенье, 13 марта 2011 г.

Groovy - глюк при мемоизации рекурсивных замыканий

Задумал тут написать статью о том, как в Groovy работает мемоизация замыканий.

Для тех кто не знает - это возможность кешировать результаты вычисления (работает, разумеется, только для "чистых" функций) какого-то значения в виде Map-a [набор аргументов функции] -> [значение], ассоциированного с функцией. Позволяет для сложных вычислений с реюзабельными результатми выигрывать во времени выполнения, проигрывая немного (или много, если диапазон результов большой) в памяти.

О ней, в частности, как об одной из интересных фич грядущего Groovy 1.8 писал в своем блоге один раз разработчиков Groovy Рошан Дорани (Roshan Dawrani).

И внезапно, экспериментируя, натолкнулся на следующий, не знаю пока, баг или нет, но нечто странное точно. Итак, пишем рекурсивную лямбда-функцию, считающую факториал, добавляем туда вызова sleep(time), чтобы разница в количестве вызовов была заметна и измерима, и пробуем мемоизировать все это дело. Тестировать online можно тут - http://groovyconsole.appspot.com/script/439003.

Пример 1:


def startTime = System.currentTimeMillis()
def fact = {it -> sleep(100); it ? it * call(it - 1g) : 1g}
println fact(50)
println fact(50)
println ((System.currentTimeMillis() - startTime) / 1000.


Никакой мемоизации здесь нет, и скрипт работает 10.391 секунд.
Теперь попробуем мемоизировать функцию. Для чего вызываем на объекте замыкания (да, я использую термин "замыкание" вместо "лямбда функция", пуристы - простите меня) memoize(), и присваиваем переменной fact ссылку на враппер, который возвращает метод memoise().
Пример 2:

ddef startTime = System.currentTimeMillis()
def fact = {it -> sleep(100); it ? it * call(it - 1g) : 1g}
fact = fact.memoize()
println fact(50)
println fact(50)
println ((System.currentTimeMillis() - startTime) / 1000.0)


В этот раз мы мемоизировали наше замыкание, и все работает за 5.175 секунд, т.е. второй раз вызов для того же аргумента отрабатывает почти мгновенно.
Но что если мы хотим вызвать для другого аргумента, и воспользоваться промежуточными результами, которые должны были бы, по идее, быть кешированы ранее?

Пример 3:

def startTime = System.currentTimeMillis()
def fact = {it -> sleep(100); it ? it * call(it - 1g) : 1g}
fact = fact.memoize()
println fact(50)
println fact(51)
println ((System.currentTimeMillis() - startTime) / 1000.0)

В приведенном случае я ожидал бы, что скрипт отработает за 5 с небольшим секунд, т.к. он начнет вычислять fact (51) = 51 * fact(50), то что выделено жирным уже должно быть вычислен о строчкой выше. Правда, логично и ожидаемо? Ан-нет, скрипт отработал за 10.519 секунд.

Т.е. мемоизация не работает для этого случая. Почему так происходит? Я смотрю в исходники сейчас, и похоже это потому, что метод memoize() создает враппер для исходного замыкания, и возвращает ссылку на него, а вызов call() изнутри самого замыкания не роутит вызов через этот враппер.

Написал вопрос в девелоперскую рассылку, жду.. Если подтвердят, что баг - может патч написать?