суббота, 21 февраля 2009 г.

Маразм крепчал: день трезвости...

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

Сегодня днем вышли с семьей погулять в лес. Погода замечательная: в последние дни часто шел снег, поэтому весь лес заснежен, он лежит даже на тоненьких веточках маленьких кустиков, не говоря уже о соснах и елочках. Снег свежий, белый-белый. Температура в районе -3..-2. Только вот солнышко пряталось за тучки, а то была бы просто сказка.

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

P.S. Никогда не доводилось пробовать шашлык зимой, в заснеженном лесу. Интересно, как это - легкий морозчик, свежий снежок, костер, горячий шашлык и холодная водочка? Здорово, наверное... :)

пятница, 20 февраля 2009 г.

Типа прочел "Actors and Continuous Functionals"

Вчера дочитал одну из основополагающих статей по модели актеров: "Actors and Continuous Functionals" by Carl Hewitt and Henry Baker...

Теперь меня мучают три вопроса:
Нафига мне это было нужно?
Что же было написано в статье?
Какой смысл в том, что написано в статье?

Как ни прискорбно признаться, но врожденная тупость и неспособность к восприятию чистых математических абстракций, не позволили мне понять почти 90% этой статьи. Был бы очень признателен, если бы кто-нибудь указал мне на что-нибудь, где на пальцах объясняется смысл всех этих законов и правил, а так же их роль в развитии информационных технологий.

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

Здесь есть толика моего труда :)

Слава Костин сделал доклад на iFin2009 об одном из наших проектов. Вот слайды из этого доклада.

Приятно осознавать, что в таком большом и серьезном проекте где-то есть и частичка моего труда. А именно, поддержка SMS-транспорта. Реализованная, как не сложно догадаться, с помощью SObjectizer.

четверг, 19 февраля 2009 г.

Многопоточное программирование превращается в кошмар?

В последнее время много читаю о проблемах, которые можно получить в многопоточных программах на современных многоядерных архитектурах (см. мегасообщения Димы Вьюкова на RSDN: "Многопоточность сегодня" и "3 базовых вещи относительно параллельных вычислений"). Страшно становится. Вот, кстати, одна из последних страшилок на эту тему от Герба Саттера - "Sharing Is the Root of All Contention" (статья интересная и, имхо, полезная - рекомендую).

Вот чего я не могу себе представить, так это как программисту учитывать все эти факторы в своей повседневной работе. Ведь ситуация такова, что нам приходится делать все больше и больше функциональности за меньшее время. В связи с чем постоянно идет переход к новым высокоуровневым языкам, библиотекам и технологиям. Взять тот же C++. Хочешь, чтобы программисту было удобно, чтобы он писал быстро и надежно -- используй STL. Вот тот же std::string -- удобно? Удобно! Но насколько эффективно? Особенно, если пишешь что-то вроде:

std::string make_some_params(
         const std::string & a,
         const std::string & b )
{
	return a + "::" + b;
}

А ведь пишешь так как раз для того, чтобы не возиться с ручным выделением памяти, ручным вызовом strcpy, strcat, чтобы не забыть освободить выделенную память. Ну да ладно std::string. Его еще хоть можно заменить на char*. А что делать с map или hash_map? И такие классы в больших прикладных программах всего лишь самый нижний слой.

Это я все к тому, что пока сложность разрабатываемой тобой системы позволяет задумываться над эффективностью операций с std::string/std::map, тогда еще можно как-то держать в уме вопросы размещения данных по разным линиям кэша. Но что делать, если сложность системы гораздо больше? Вот мне сейчас нужно сделать небольшое распределенное приложение, состоящее из двух процессов, связанных каналом связи. Приложение на SObjectizer, один из процессов при этом еще и работает с БД. Самой прикладной логики, которую мне предстоит написать с нуля, там очень немного. Зато сколько всего оказывается скрыто под капотом! Внутри SObjectizer, внутри ACE, внутри OTL, внутри ODBC... Какие там операции выполняются, насколько они дружелюбны и ориентированны на современные Core Duo, QuadCore и прочее? Тайна сия велика есть... Как и границы моих возможностей в случае необходимости увеличить производительность всего этого комбайна :(

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

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

вторник, 17 февраля 2009 г.

В поиске решения "Можно ли заставить компилятор проверять изменение принципов работы компонентов?"

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

Идею подсказал lomeo в одном из RSDN-овских флеймов. Суть в том, что если речь идет о синхронных вызовах, то можно потребовать, чтобы некий метод возвращал значение уникального типа. А этот тип нельзя было получить никак иначе, кроме как выполнив определенное действие.

Итак, вот суть задачи в переложении на синхронные вызовы. Есть некий интерфейс s_iface_t, с методом query_state. При обращении к этому методу у объекта типа mailslot_t должен быть вызван метод send_state_notification. Причем не у абы какого объекта mailslot_t, а только у того, который не меняется в течение всего времени жизни объекта, реализующего интерфейс s_iface_t.

Т.е. мы должны иметь что-то вроде (примеры написаны на C++ без учета проблем сборки мусора):

// Интерфейс почтового ящика.
class mailslot_t {
  public :
    virtual void send_state_notification() = 0;
};

class s_iface_t {
  public :
    virtual КАКОЙ-ТО-ТИП query_state() = 0;
};

class s_component_t : public s_iface_t {
  public :
    s_component_t( mailslot_t & m );

    virtual КАКОЙ-ТО-ТИП query_state();
};

И какой же тип должен возвращать s_iface_t::query_state()? Например, такой:

// Сигнал о том, что был задействован почтовый ящик,
// который не может измениться.
class nonchangeable_mailslot_used_signal_t {
  // Создавать экземпляр этого типа нужно очень хитро.
  // Поэтому его создание делегируется специальному
  // вспомогательному классу.
  friend class nonchangeable_mailslot_holder_t;

  // А конструктор поэтому закрыт.
  nonchangeable_mailslot_used_signal_t( mailslot_t & m ) {
    m.send_state_notification();
  }

public :
  // Зато деструктор открыт.
  ~nonchangeable_mailslot_used_signal_t() {}
};

Т.е. конструирование объекта nonchangeable_mailslot_used_signal_t невозможно без вызова send_state_notification. Т.о. мы гарантируем, что раз s_iface_t::query_state() вызывается, значит следует вызов send_state_notification у какого-то mailslot-а. Осталось только обеспечить, чтобы send_state_notification вызывался у того mailslot-а, который был нам передан в конструкторе. Это уже достигается реализацией класса nonchangeable_mailslot_holder_t:

class nonchangeable_mailslot_holder_t {
  // Поскольку это ссылка, то после инициализации она
  // измениться не может.
  mailslot_t & mailslot_;

protected :
  // Конструктор защищен, значит его может вызвать только
  // производный от nonchangeable_mailslot_holder_t класс.
  // А это значит, что производный класс должен получать
  // в конструкторе ссылку на mailslot.
  nonchangeable_mailslot_holder_t( mailslot_t & m )
    : mailslot_(m)
    {}

  // Этот метод так же защищен, значит его может вызвать
  // только производный класс.
  nonchangeable_mailslot_used_signal_t
  send_state_notify() {
    return nonchangeable_mailslot_used_signal_t( mailslot_ );
  }
};

Теперь все, что остается сделать - это унаследовать s_component_t от nonchangeable_mailslot_holder_t:

class s_component_t
  : public s_iface_t
  , protected nonchangeable_mailslot_holder_t {
public :
  s_component_t( mailslot_t & m )
    : nonchangeable_mailslot_holder_t(m)
    {}

  virtual nonchangeable_mailslot_used_signal_t
  query_state() {
    return send_state_notify();
  }
};

Решение далеко не идеальное и оставляет пользователю возможности для обхода контракта (например, можно сделать вспомогательный класс, унаследованный от nonchangeable_mailslot_holder_t и использовать его временные объекты в новых версиях s_component_t::query_state). Но все эти фокусы нужно делать осознано, а контракты в данной задаче были предназначены как раз для того, чтобы нельзя было их нарушить случайно (по незнанию или недосмотру).

понедельник, 16 февраля 2009 г.

О поддержке flow-based программирования средствами агентно-ориентированного фреймворка

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

Придется конкурировать с Google? ;)

Вчера обнаружил, что Google выпустил еще один OpenSource продукт, который является конкурентом одной из моих разработок. Похоже, назревает время серьезной конкуренции с Google :))) Ну да по порядку.

Вначале появился проект Google Protocol Buffers -- это инструмент для сериализации/десериализации данных, который на данный момент поддерживает языки C++, Java и Python. Принцип работы Protocol Buffers достаточно прост: на специальном языке программист описывает структуры своих данных, например:

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

после чего из этого описания генерируется вспомогательный код, который отвечает за сериализацию/десериализацию объектов этого типа в программе:

Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstream output("myfile", ios::out | ios::binary);
person.SerializeToOstream(&output);

fstream input("myfile", ios::in | ios::binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;

При этом поддерживается расширение сериализуемых типов в последствии (изменение типов полей, добавление полей).

Google не изобрел ничего нового. Подобные инструменты (и даже более мощные, на мой взгляд) уже давно существовали. Начать можно с ASN.1 -- большой стандарт, который определяет несколько способов упаковки данных (называемых encoding rules: двоичный BER (он же TLV -- Tag-Length-Value), двоичный PER (где данные могут упаковываться с плотностью до бита), XML-ный XER), а так же имеет специальное понятие extension points (т.е. мест в описании структур данных, в которых будущие версии могут содержать дополнения). С другой стороны, изучение ASN.1 не простая штука. Да и инструментарий для поддержки ASN.1, как правило, платный. И, в дополнение к этому, отображение ASN.1 на конкретный язык программирования не стандартизировано. Поэтому, выбор ASN.1-компилятора автоматически означает привязку разработчика к одному поставщику ASN.1-инструментария.

Другим конкурентом Protocol Buffers является Thrift, который начинался внутри проекта Facebook, а сейчас он включен в Apache Incubator. Небольшое сравнение между возможностями Thrift и Protocol Buffers можно увидеть здесь.

Так же можно вспомнить знаменитые проекты для C++ сериализации: Boost.Serialization и s11n.

Ну и мой проект ObjESSty предназначен для тех же целей, что и Protocol Buffers. ObjESSty, в отличии от Protocol Buffers, сейчас ориентирован исключительно на C++. Но зато для C++ ObjESSty имеет встроенную поддержку множественного наследования и стандартных STL-контейнеров (list, set, map, vector). А главной фишкой ObjESSty является поддержка атрибутов-указателей и наследования расширением (subclassing by extension).

Для меня стало сюрпризом то, какой восторг поднялся на программистских форумах по поводу Google Protocol Buffers. Уж не знаю, что здесь сыграло главную роль: бренд Google, заставляющий писать кипятком, или удачное сочетание в Protocol Buffers простоты и мощности инструмента.

Вторым конкурентом со стороны Google для меня оказался проект Software Construction Toolkit -- грубо говоря, очередной build tool для C++. Написанный на Python и построенный на основе SCons. Т.е. полку конкурентов моего Mxx_ru прибыло.

Не знаю, какой шум по поводу swtoolkit поднимется сейчас. Может быть, опять громкое имя Google сработает. А может быть ничего и не произойдет -- не смотря на длительное время существования того же SCons и ряда альтернативных build tool-ов (CMake, Bakefile, Boost Build, Rake, qmake, Ant) ни один из них пока не занял доминирующего положения и не вытеснил традиционный make (который под Unix-ом дополняется набором autotools) и проектные файлы конкретных IDE (то бишь, Visual C++ под Windows).

Уж не знаю, какие перспективы будут у Mxx_ru. С мыслью о том, что Mxx_ru станет сколько-нибудь заметным инструментом, я давно распрощался. Во-первых, это требует серьезного пиара и проталкивания Mxx_ru в различных англоязычных форумах и новостных группах. Во-вторых, Mxx_ru требует еще больших вложений -- нужны новые фичи, поддержка новых платформ, дополнительная документация и пр. На что требуются силы и время. А с этим не так просто. В первую очередь потому, что на данный момент Mxx_ru практически полностью удовлетворяет мои потребности. И поэтому Mxx_ru сейчас находится в той стадии, когда он дорабатывается только когда мне или кому-нибудь из пользователей Mxx_ru что-то понадобится и пожелание к Mxx_ru будет сформулировано и озвучено. Например, благодаря такому пожеланию в Mxx_ru недавно была добавлена поддержка Qt4. Может быть, какие-то новые изменения ждут Mxx_ru после появления нормальных стабильных версий Ruby 1.9 (даже в недавно вышедшем 1.9.1 я выловил баг, не позволяющий перевести под 1.9 один из моих Ruby-проектов). Есть надежда, что под Ruby 1.9 Mxx_ru будет работать быстрее. И что под Ruby 1.9 будет проще реализовать многопоточную сборку проекта (чтобы задействовать мощности современных многоядерных процессоров).

Пока же с удовольствием могу отметить, что документация по Mxx_ru, на мой взгляд, гораздо лучше таковой для swtoolkit. Даже странно -- вроде бы большая контора Google, могли бы найти ресурсы на выпуск хорошей документации для swtoolkit. Так что, есть смысл побороться с Google! ;)