среда, 21 октября 2020 г.

[prog.c++] json_dto-0.2.11 released

json_dto is a thin wrapper around RapidJSON library we wrote several years ago. It's surprising for us that this small bicycle is used by someone outside our team. Sometimes users show us use-cases we just weren't thinking about. And we add new features to json_dto to fulfill expectations.

The last updates to json_dto add a possibility to customize formatting of (de)serialized values by specifying custom Reader_Writers. For example let's imagine that we have a struct with a `std::map<std::uint32_t, int>` field:

struct example_data {
   std::map<std::uint32_tint> m_weights;
   ...
};

It's impossible to write a simple (de)serialization code for example_data in the form:

struct example_data {
   std::map<std::uint32_tint> m_weights;
   ...

   template<typename Io> void json_io(Io & io) {
      io & json_dto::mandatory("weights", m_weights)
         ...
         ;
   }
};

because RapidJSON expects string values as keys, but json_dto (de)serializes `std::uint32_t` as integers. And the naive implementation of `json_io` shown above leads to run-time error during serialization.

Now json_dto allows to use custom formatters for fields to be (de)serialized:

struct simple_reader_writer {
   // Those methods will be used for (de)serializing of map's keys.
   void read(
      json_dto::mutable_map_key_t<std::uint32_t> & key,
      const rapidjson::Value & from) {
      ... // Deserializing key from a string.
   }
   void write(
      const json_dto::mutable_map_key_t<std::uint32_t> & key,
      rapidjson::Value & to,
      rapidjson::MemoryPoolAllocator<> & allocator) {
      ... // Serializing key as a string.
   }

   // Those methods will be used for (de)serializing of map's values.
   void read(
      int & v,
      const rapidjson::Value & from) {
      json_dto::read_json_value(v, from); // Just reuse json_dto.
   }
   void write(
      const int & v,
      rapidjson::Value & to,
      rapidjson::MemoryPoolAllocator<> & allocator) {
      json_dto::write_json_value(v, to, allocator); // Just reuse json_dto.
   }
};

struct example_data {
   std::map<std::uint32_tint> m_weights;
   ...

   template<typename Io> void json_io(Io & io) {
      io & json_dto::mandatory(
              // Explicit specification of custom Reader_Writer for that field.
              // The usage of apply_to_content_t tells that custom formatter
              // should be applied to every item of the container.
              json_dto::apply_to_content_t<simple_reader_writer>{},
              "weights", m_weights)
         ...
         ;
   }
};

So now keys of `example_data::m_weights` will be (de)serialized as strings.

But custom Reader_Writers allow to go further. Let's imaging that `example_data` contains yet another `std::map<uint32_t, int>`:

struct example_data {
   std::map<std::uint32_tint> m_weights;
   std::map<std::uint32_tint> m_colors;
   ...
};

where keys of `example_data::m_colors` should be represented in the form `#xxxxxx`, where `xxxxxx` is hexadecimal representation.

We can create another custom Reader_Writer:

struct color_hex_reader_writer {
   // Those methods will be used for (de)serializing of map's keys.
   void read(
      json_dto::mutable_map_key_t<std::uint32_t> & key,
      const rapidjson::Value & from) {
      ... // Deserializing key from a string.
   }
   void write(
      const json_dto::mutable_map_key_t<std::uint32_t> & key,
      rapidjson::Value & to,
      rapidjson::MemoryPoolAllocator<> & allocator) {
      ... // Serializing key as a string.
   }

   // Those methods will be used for (de)serializing of map's values.
   void read(
      int & v,
      const rapidjson::Value & from) {
      json_dto::read_json_value(v, from); // Just reuse json_dto.
   }
   void write(
      const int & v,
      rapidjson::Value & to,
      rapidjson::MemoryPoolAllocator<> & allocator) {
      json_dto::write_json_value(v, to, allocator); // Just reuse json_dto.
   }
};

and use it for (de)serialization of `example_data::m_colors`:

struct example_data {
   std::map<std::uint32_tint> m_weights;
   std::map<std::uint32_tint> m_colors;
   ...

   template<typename Io> void json_io(Io & io) {
      io & json_dto::mandatory(
              json_dto::apply_to_content_t<simple_reader_writer>{},
              "weights", m_weights)
         & json_dto::mandatory(
              json_dto::apply_to_content_t<color_hex_reader_writer>{},
              "colors", m_colors)
         ...
         ;
   }
};

The full working example can be seen here.

PS. I don't like to answer questions like "Is json_dto better than nlohmann::json, cereal or any other similar library?" We started to use RapidJSON several years ago and we didn't know about many of the JSON-libraries well known today. At some time we decided to simplify our marriage with RapidJSON and wrote json_dto. Since then it's easier for us to continue to use json_dto than to switch to any other library. So if you are happy with nlohmann::json there is no need to see for something else. But if you have to stay out of nlohmann::json for any reason there is json_dto ;)

PPS. We love reinventing bikes and we know how to do it: RESTinio and SObjectizer are also out of our garage.

понедельник, 12 октября 2020 г.

[prog.c++] The follow-up for "What's new in rotor v0.09" article

There is a new article "What's new in rotor v0.09" that tells several things about yet another C++ actor framework named "rotor". As for me it's a good example of how different implementations of Actor Model could be. And it's a good reason to write some words about SObjectizer's features to show how the same things were addressed a long time ago.

Before I start, it's necessary to note that some of the decisions described below are rather philosophical than technical ones. It also means that some decisions taken from philosophical standpoints had a significant impact on technical aspects of SObjectizer's internals and API, and on applications built on top of the SObjectizer.

четверг, 8 октября 2020 г.

[prog.c++] Наш RESTinio упомянули в докладе про I/O Objects из Networking TS на CppCon2020

При просмотре доклада "The Networking TS from Scratch: I/O Objects" от Robert Leahy уронил челюсть на пол при появлении вот этого слайда:

Отрадно. Не зря, выходит.

Пользуюясь случаем хочу напомнить, что если вам не хватает чего-то в RESTinio или же вам что-то не нравится в RESTinio, то об этом можно рассказать нам. Возможно, мы сможем вам помочь.

понедельник, 5 октября 2020 г.

[prog.c++] Смешанные впечатления от трюка с dont_deduce

Несколько дней назад на Reddit-е появилась ссылка на статью dont_deduce<T>. Если кто не читал, но итересуется различными плохоосвещенными закутками языка C++, то рекомендую. Станет понятно, зачем в C++20 появился шаблон std::type_identity.

На меня знакомство с трюком dont_deduce/type_identity произвело двойственное впечатление.

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

Но, с другой стороны, C++ вполне обоснованно критикуют за то, что в C++ часто и бесконтрольно происходит автоматическая конвертация типов. Начиная от (может быть) безобидных преобразований из int в float/double и заканчивая неявным вызовом конструкторов классов с единственным параметром (как, например, конструирование std::string из строкового литерала). А применение трюка dont_deduce/type_identity, как по мне, есть не что иное, как целенаправленное закладывание в код этих самых неявных преобразований типов. О которых другой программист, использующий ваши API, скорее всего, даже не будет знать.

Так что я бы лично предпочел бы получить от компилятора ошибку о том, что он не может вывести шаблон функции т.к. один параметр имеет тип Vec3<float>, а второй -- int. Чтобы поправить код и явным образом вписать константу 1f вместо 1.

Возможно, трюк dont_deduce/type_identity может применяться в коде шаблонных функций/классов. В котором приходится хардкодить константы (типа явно описываемых в коде единичек или ноликов). Но в современном C++ есть же всякие decltype, чтобы легко определить какой тип должны иметь захардкоженные константы, так что серьезной проблемы из-за неприменения dont_deduce/type_identity я не вижу.

Итого: если кто-то не в курсе что такое dont_deduce/type_identity, то ознакомиться с вышеозначенной статьей полезно. Но вот наскольо оправданно будет применение этого трюка на практике... Это большой вопрос.

суббота, 3 октября 2020 г.

[life] Осенью в Гомеле зацвел каштан

Нынешний 2020 год продолжает удивлять и подкидывать вещи, которые раньше видеть не доводилось.

Вот, например, вчера по дороге с работы увидел цветущий каштан. В октябре. В Гомеле.

Просто афигеть.

20201002-IMG_20201002_150108

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

А тут второе цветение дерева, с которого еще не осыпались созревшие каштаны.

Просто афигеть.

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

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

среда, 30 сентября 2020 г.

[prog.opensource] c-smile собирает средства на перевод sciter в OpenSource

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

Так вот, уже много лет c-smile делает в одиночку проект sciter. Это встраиваемый HTML/CSS движок, который может использоваться для разработки легковесных GUI-интерфейсов на базе Web-технологий.

Оказывается, в середине сентября c-smile открыл на Kikstarter-е компанию по сбору средств для перевода sciter в категорию OpenSource: Open Source Sciter Engine.

Хоть сам я sciter-ом никогда не пользовался, но, как по мне, начинание хорошее. Посему делюсь информацией. Может кто захочет поддержать деятельность c-smile своим трудовым рублем.

суббота, 26 сентября 2020 г.

[prog.thoughts.c++] Очередная рефлексия о том, куда движется C++ и по пути ли мне с ним...

Так уж получается, что в последний месяц работать получается немного, зато много думается о разном и разнообразом. В том числе о том, остается ли для C++ место под Солнцем в современном мире. И, если остается, то какое и где (гусарам молчать!).

Данный пост появился под влиянием нескольких факторов.

суббота, 19 сентября 2020 г.

[life] Мои краткие впечатления об интервью Юрия Дудя авторов канала NEXTA

Посмотрел сие творение. Просто чтобы не случилось "Пастернака не читал, но осуждаю".

Общее впечатление: один "малолетний дебил" (с) берет интервью у пары других "малолетних дебилов". При этом с дебильностью Дудя посоперничать может разве что упоротость Степана Путило.

На моменте, когда Дудь говорит "Выяснилось, что люди, судя по всему, антисемиты... Я чего-то не ожидал, что я могу такое в центре Европы услышать. Тем более в П-О-О-О-Л-Ь-Ш-Е!!!" пришлось даже сделать паузу. Антисемитизм у поляков? Как такое может быть вообще? Ведь никогда такого не было, а вот опять... ДБ (лавров.jpg).

ИМХО, лучшей реакцией властей РБ на этот фильм был бы его показ без купюр по белорусскому ТВ. В прайм-тайм. С утренним повтором на следующий день.

пятница, 18 сентября 2020 г.

[life] Месяц вне радаров и это явно не последний месяц

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

Сейчас я еще более уверен в том, что события развивались бы по известному уже сценарию даже если бы Лукашенко на выборах набрал бы всего лишь 51% голосов. Цифры не важны, важен сам факт того, что победу одержал АГЛ. Просто официально зафиксированные 80% сделали задачу протестных кукловодов немного проще.

Сейчас я еще более уверен в том, что происходящие события -- это прямая аналогия не с украинским майданом 2014-го, а с позднеперестроечным СССР и раздербаниванием СССР посредством удачного использования сложившихся тогда условий. При этом неполживые dev.by/onliner.by/tut.by выглядят прямо как "Огонёк" Коротича в 1980-х.

Целью же всей движухи является установление такой власти, при которой нужные люди смогут отнять и переделить имеющиеся в РБ ресурсы.

Это и является основной причиной происходящего. Плюс сюда же и геополитические интересы отдельных игроков, включая желание поднасрать на границах РФ.

А результаты выборов -- это лишь повод. Тщательно подготовленный надо сказать. Но лишь грамотно использованный повод.

Общество, действительно, расколото. Самым ярким признаком служит активное использование БЧБ-флагов протестующими.

Столь активное использование БЧБ-символики я лично рационально объяснить не могу.

Один из худших вариантов, почему такое происходит, -- это смена поколений. Т.е. постепенно те, кто вырос на советской историографии уходят, на смену приходит поколение, для которого БНР -- это, оказывается, полноценное государство; Польша -- лепши сябр, который всячески заботился о белорусском и украинском населении своих всходних кресов, отрывая последнее у поляков и отдавая белоруссам/украинцам; коллаборация с фашистами в ВОВ -- борьба на независимость; деятельность Рады БНР в изгнании значит больше, чем все развитие ССБР/БССР в рамках Советского Союза; принятие БЧБ-символики Верховным советом БССР перед распадом СССР -- более демократичное действо, чем возврат к красно-зеленому флагу посредством всенародного референдума.

Ну что ж, если это так, то значит историческая реальность становится вот такой.

Как по мне, в стране уже идет гражданская война, пока что в холодной фазе. Из-за чего в соцсети типа Facabook или LinkedIn заходить просто страшно. Во многом благодаря работе алгоритмов этих соцсетей (а так же Google на Android-е). Из-за чего от змагарских постов, лайков и репостов невозможно спрятаться.

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

Но даже если все будет проходить по мирному сценарию и активные протесты таки стихнут спустя 2-3 месяца, сформировавшийся раскол никуда не денется. Люди перестанут шляться по городам с БЧБ-флагами, но настроения останутся на десятилетия и будут проявляться в разговорах на кухнях, как это было во времена СССР. Только сейчас к этому добавятся и соцсети с мессенджерами. Так что питательный протестный бульон никуда не денется, только бурлить он будет уже на медленном огне. До следующей точки бифуркации.

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

В соцсетях, по-прежнему, стараюсь не появляться без надобности. Связаться со мной можно через личные сообщения в FB/VK/LinkedIn можно, но ленту новостей я там не читаю. Если кому-то нужно привлечь мое внимание к чему-то, то делайте это через сообщения.

Ну пока на этом все. Пожалуйста, сохраняйте благоразумие и берегите себя.

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

четверг, 17 сентября 2020 г.

[prog.c++] Эволюция развития новой фичи в json_dto: от простого к сложному, а затем к менее сложному

В этом посте, по горячим следам, хотелось бы рассказать о том, как появилась новая фича в нашем небольшом проектике json_dto, который очень сильно упрощает работу с JSON в C++ посредством RapidJSON. Надеюсь, что этот рассказ будет еще одним подтверждением известного афоризма о том, что простота не предшествует сложности, а следует за ней.

Пару слов о json_dto для тех, кто не в курсе что это

Библиотека json_dto была создана нами года 4.5 назад для упрощения работы с JSON посредством RapidJSON. Если делать (де)сериализацию собственных структур с использованием API RapidJSON, то получаются большие портянки кода. Что не есть хорошо: требует много времени и отнимает много сил и внимания. Мы же взяли за основу идею из Boost.Serialization и сделали небольшую обертку над RapidJSON, которая позволяет описывать (де)сериализацию практически в декларативном виде. Например:

вторник, 8 сентября 2020 г.

[software.lavrov.jpg] Что я имею сказать по поводу призывов использовать Boost Software License

Данный пост навеян вот этим обсуждением на Reddit-е: "Why You Should Use the Boost Software License", которое сосредоточено вокруг вокруг одноименного поста в блоге Peter Dimov. Суть в том, что BSL не требует перечислять использованную библиотеку под лицензией BSL при распространении конечного продукта, сделанного на базе этой библиотеки.

Т.е. если есть продукт P, в котором используются библиотека X под лицензией MIT и библиотека Y под лицензией BSL, то про библиотеку X где-то в продукте P упоминать нужно обязательно, тогда как про Y можно и не упоминать.

Поэтому Peter Dimov (и не только он, но и скажем, Vinnie Falco, автор Boost.Beast-а) призывают разработчиков использовать для своих открытых проектов именно BSL.

На эту тему имею сказать две вещи.

Первое: любой труд должен вознаграждаться. Разработка OpenSource -- это труд. Только, в отличии от разработки закрытого коммерческого ПО, труд этот чаще всего неблагодарный и оплачиваемый несравнимо хуже (если вообще оплачиваемый). Соответственно, если автор OpenSource проекта хочет, чтобы те, кто используют результаты его труда, вознаграждали автора хотя бы посредством упоминания, то это, как по мне, совсем небольшая плата. И нежелание вознаграждать автора OpenSource разработки даже таким недорогим способом... Это, как минимум, жлобство.

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


Соответственно, вывод простой: если я делаю OpenSource проект сам и за свои, то авторов призывов использовать именно Boost Software License можно разве что отправить в пешее эротическое. Ну а если разработка ведется за деньги заказчика, то и лицензия будет такой, какую захочет заказчик.

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

воскресенье, 30 августа 2020 г.

[life.photo] Вероятно, это будет мое лучшее фото в 2020-ом году

20200829-IMG_20200829_175546_2

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

понедельник, 24 августа 2020 г.

[work.management] Интересное видео про менеджмент и организацию эффективного производства (и не только производства)

Давно не постил в блог видео, но вот это заслуживает того, чтобы попасть в склерозник.

Как говорится, "эх, если бы я об этом знал раньше..." Теперь-то знаю. Но ошибки прошлого не исправить :(

А тем, кто еще не знает, но что-то начинает подозревать, имеет смысл посмотреть.

PS. Особенно меня порадовали моменты про мотивацию (денежная мотивация работает, но недолго) и KPI (тут же вспомнилось, что о подобном и сам писал когда-то).

четверг, 20 августа 2020 г.

[prog.c++] Незапланированный, но показательный релиз RESTinio

Сегодня была зафиксирована очередная версия RESTinio: 0.6.10. В ней только одно нововведение, но зато оно показательно.

Пару дней назад пользователь открыл issue на GitHub, в котором попросил возможность сделать так, чтобы экземпляр asio::ssl::context можно было разделять между RESTinio-сервером и другими частями приложения. Что изначально в RESTinio предусмотренно не было.

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

Мораль сей басни: если вы что-то хотите видеть в RESTinio, то дайте нам об этом знать. Попробуем решить вопрос.

Да, и это касается не только RESTinio ;)

понедельник, 17 августа 2020 г.

[life] В связи с кризисом в нашей стране исчезну с радаров

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

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

Попробую пояснить почему так думаю.

четверг, 13 августа 2020 г.

[prog.c++] My view of the near (and not so) future of the RESTinio project

Now, after the official release of RESTinio-0.6.9, it's a good time to share some thoughts about the near (and not only near) future of the RESTinio project.

It is worth noting that the text below is only my point of view. This point of view can change over time.

This text is split into two parts. The first part tells about rather practical things that I want to see in the upcoming releases of RESTinio. The second part is more philosophical and intended for the discussion about the role of RESTinio in the modern C++ landscape.

Part One. What I want to have in RESTinio 0.6 and what could be seen in 0.7?

суббота, 1 августа 2020 г.

[prog.thoughts] Какой способ информирования об ошибках мне бы хотелось иметь для написания надежного кода?

Много лет занимаюсь разработкой софта, который должен работать в режиме 24/7 и, зачастую, под приличной нагрузкой. Это не mission-critical софт, скорее business-critical. Т.е. если будет глючить и падать, то никто не умрет. Так что писать "пуленепробиваемый" и "не убиваемый" кода пока не приходилось. Тем не менее, нестабильная работа написанного нами софта -- это авралы, стрессы, неприятности с клиентами. Понятное дело, что никому такое не нужно.

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

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

Сперва постараюсь рассказать о том, что лично я бы хотел видеть в языке программирования. А потом скажу пару слов о том, почему C++ в этом плане не очень хорош (впрочем, как и другие мейнстримовые языки).

пятница, 31 июля 2020 г.

[life.cinema] Очередной кинообзор (2020/07)

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

Фильмы

Офицер и шпион (J'accuse, 2019). Мне понравилось, посмотрел с удовольствием.

Грейхаунд (Grayhound, 2020). Добротно рассказанная история, за которой интересно следить. И хорошо показанные персонажи, которым сопереживаешь. Слегка разочаровала, разве что, графика. Слишком уж нарисованными выглядели некоторые сцены.

Форпост (The Outpost, 2020). На мой взгляд весьма достойный военный фильм.

Гори, гори ясно (Brightburn, 2019). В принципе, идея мне понравилась. Вот что было бы, если бы Супермен оказался бы плохишом. Но подвело то, что в фильме всего одна звездная актриса, Элизабет Бэнкс, однако, главным-то героем должна была бы быть не она. Так что оттягивание внимания от пацана с суперспособностями на единственную звезду сделало фильм менее интересным, чем он мог бы быть.

Темное наследие (Inheritance, 2020). Ну так себе. По ходу просмотра очень быстро начинают возникать вопросы "А почему так-то?", а затем и желание воскликнуть "Не верю". Еще больше этих вопросов и реплик "Не верю", остается после просмотра. Так что по итогу впечатление остается "ну так себе".

Всегда верен (Semper Fi, 2019). Купился на хороший трейлер и ожидал напряженный и динамичный фильм про побег из тюрьмы. Но оказалось, что того самого побега в фильме всего 1/5 часть времени. В лучшем случае. Все остальное что-то вроде мелодрамы. В общем, вполне можно и не смотреть.

Призраки войны (Ghosts of War, 2020). Начинался как заштатный фильм ужасов на тему призраков в антураже Второй Мировой. Ближе к финалу авторы замутили твист. Который, как мне показалось, на пользу совсем не пошел. И уж лучше бы фильм оставался заштатным фильмом ужасов на тему призраков в антураже Второй Мировой.

Мой создатель (Archive, 2020). Ну очень нудное кино. Ну очень нудное. Ну очень. Насколько нудное и усыпляющее, что даже финальный твист вызывает скорее раздражение, нежели удивление.

Бессмертная гвардия (The Old Guard, 2020). Редкая бредятина по отдаленным мотивам "Горца". С кучей соплей и чрезмерной толерастией, из-за чего сопли временами оказываются даже не розовыми, а голубыми.

Сериалы

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

Голова (The Head), первый сезон. Посмотреть можно. Но развязка оказалась несколько предсказуемой.

Настоящий детектив (True Detective), третий сезон. В принципе, как детектив он мне понравился гораздо больше, чем второй сезон. Тут, действительно, была линия расследования загадочного преступления. Но очень не понравились две вещи. Во-первых, скакание из одного исторического отрезка в другой. Понятно, что это нужно было для большего драматизма и придания глубины, чтобы зрители лучше осознали, что все это тянулось не один десяток лет. Но меня лично такие перескакивания отвлекали. Во-вторых, слишком уж много внимания было уделено личной жизни одного из детективов. Как мне показалось, если бы выбросить лишние семейные сопли, то можно было бы сделать на пару серий меньше, но зато оставшиеся были бы интереснее, а повествование динамичнее и напряженнее.

суббота, 25 июля 2020 г.

[prog.c++] Пример использования шаблонов для создания C-шных коллбэков из методов C++-ных классов

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

Итак, есть известная чисто-С-шная библиотека http_parser, которую много кто использует (в том числе и мы в RESTinio). Для работы с ней нужно создать у себя в программе экземпляр типа http_parser и экземпляр типа http_parser_settings. Где http_parser_settings должен содержать набор указателей на С-шные коллбэки, которые будут вызываться во время парсинга HTTP-сообщений.

Возникла задача сделать так, чтобы коллбэками для http_parser выступали нестатические методы C++ ного класса.

Решалась эта задача в два этапа.

воскресенье, 19 июля 2020 г.

[prog.c++] Продолжение истории про parent/child и удаление child-а из метода самого child-а

Около месяца назад в блоге была заметка, в которой показывалась схема с классами parent и child. Класс parent владел экземпляром child, а child в своем методе мог вызвать у parent-а метод replace_child, во время которого текущий child уничтожался. Т.е. получалось, что объект, метод которого сейчас работает, уничтожается прямо внутри этого работающего сейчас метода.

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

Все-таки вот такое:

void child::some_method()
{
   ... // Some actions.

   delete this// The last action of the method.
}

можно контролировать лишь в самых тривиальных случаях. А по мере того, как some_method усложняется и/или погружается куда-то ниже в стек вызовов, вероятность возникновения use after free стремительно приближается к единице. И можно быть уверенным, что в один прекрасный момент use after free таки произойдет.

Под катом небольшой рассказ о схеме, которая была применена для того, чтобы сохранить режим взаимодействия parent и child, но при этом защититься от use after free.

среда, 1 июля 2020 г.

[prog.flame] Может уже и пришло время закапывать C++, но пока он позволяет в коде выписывать ABNF грамматики...

Меня, честно говоря, за последние пару недель основательно подзадолбали наезды на C++. Мол, страх и ужас, давным-давно свернул не туда, понадобавляли в новые стандарты всякой херни, комитет работает как бешеный принтер, комитет нихера не делает и пр., и пр.

Иногда складывается впечатление, что эти стенания повсюду. Особенно на профильных ресурсах, типа Reddit, LOR, RSDN (может и на Хабре, но до Хабра сейчас просто уже руки не доходят).

Финальным гвоздем стали очередные вбросы в Telegram канале C++ Russia от известного своим праздным звездежом хаскелиста Александра Гранина, у которого, походу, второй натурой стал троллинг C++а и C++ников. С традиционным уже участием на C++ Russia с докладом про функциональщину.

В общем, послушать активно испражняющихся в Интернетах специалистов по всем вопросам на свете и может показаться, что хуже C++ только мучительная смерть от бубонной чумы. Да и то не факт.

В тоже самое время этот самый современный C++, в который, как искренне верят неасиляторы, напихали всякой ненужной чуйни, лично мне позволяет в декларативном виде записывать в коде ABNF грамматики из RFC по HTTP/1.1.

Причем это даже не самый еще современный C++, а всего лишь C++14.

И что-то мне подсказывает, что сделать подобное на каких-то "типа альтернативах" C++ будет не так уж и просто. Особенно с учетом накладных расходов в run-time. Если вообще возможно.

Ну а вот и сам упомянутый фрагмент. Который отвечает за парсинг содержимого HTTP-заголовка Transfer-Encoding согласно спецификации.

[life.cinema] Очередной кинообзор (2020/06)

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

Фильмы

В погоне за ветром (Ride Like a Girl, 2019). Отлично снятый байопик. Добрый, жизнеутверждающий. Мне понравился.

Идеальный дворец Фердинанда Шеваля (L'incroyable histoire du facteur Cheval, 2018). Странный фильм про странного человека. Снят хорошо. Но смотреть тяжело. История рассказана трогательная, хотя и очень печальная.

Афера в Майами (Wasp Network, 2019). Просто удивительно, как из такой интересной и многослойной истории получилось такое унылое и затянутое непонятно что. Хорошо хоть, что снято красиво.

Отверженные (Les misérables, 2019). У фильма высокие оценки, но мне было смотреть не интересно. Ни сюжет не зацепил, ни актеры. А операторская работа так вообще раздражала. Сложилось ощущение, что фильм ценен разве что за остросоциальную тему, но вовсе не как художественное произведение.

Сериалы

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

Обвиняемый (Presunto culpable, 2018). Начал смотреть из-за исключительно красивого видеоряда в трейлере. Ну и досматривал тоже исключительно из-за работы оператора. Сам по себе сериал туфта, все события из 13 серий можно было бы запросто уместить в 3-4 и получилось бы динамичнее и напряженнее. Но вот работа оператора -- это что-то. В некоторых местах картинку хотелось останавливать и печатать, чтобы на стену повесить. Компоновка кадров, работа со светом и даже разумное использование светосильной оптики вызывает мое почтение.

Ночной администратор (The Night Manager, 2015). Актеры хорошие, на них приятно смотреть. А вот сам фильм не зацепил.

среда, 24 июня 2020 г.

[prog.c++] Максимальное значение времени, которое можно передать в std::condition_variable::wait_for

Метод std::condition_variable::wait_for в качестве одного из параметров получает время ожидания срабатывания condition_variable. На cppreference.com этот параметр называется rel_time. И про его значение сказано следующее:

an object of type std::chrono::duration representing the maximum time to spend waiting. Note that rel_time must be small enough not to overflow when added to std::chrono::steady_clock::now().

Ok. Значит значение rel_time не должно быть таким, чтобы вызвать переполнение, когда его суммируют с std::chrono::steady_clock::now(). Но каким же оно тогда может быть?

В принципе, можно предположить, что максимально допустимое значение для rel_time может быть получено таким нехитрым способом:

std::chrono::steady_clock::time_point::max() - std::chrono::steady_clock::now()

Т.е. оно не должно быть больше разницы между самой большой меткой времени, которую можно выразить в std::chrono::steady_clock, и текущей меткой времени.

Вроде бы так. Но не все так просто, ведь мы же в мире C++.

Вот программка, которую я написал для того, чтобы проверить, какое значение rel_time можно подсунуть в wait_for и получить при этом ожидаемое поведение wait_for. Ее суть в том, что она пытается брать сперва большое значение rel_time, а затем постепенно уменьшает это значение. Если же значение rel_time слишком большое, то wait_for может завершать свою работу сразу же, вообще без ожидания.

Так вот под Windows и VC++19 (и под FreeBSD и clang) получается вполне себе ожидаемый результат:

wait_time: 9222236437359ms, days: 1
wait for: 1001ms

А вот под Linux-ом и GCC/clang меня ждет неожиданное открытие:

wait_time: 9223171751593ms, days: 1
wait for: 0ms
wait_time: 9136771751593ms, days: 1001
wait for: 0ms
wait_time: 9050371751593ms, days: 2001
wait for: 0ms
wait_time: 8963971751593ms, days: 3001
wait for: 0ms
wait_time: 8877571751593ms, days: 4001
wait for: 0ms
wait_time: 8791171751593ms, days: 5001
wait for: 0ms
... skipped
wait_time: 7840771751592ms, days: 16001
wait for: 0ms
wait_time: 7754371751592ms, days: 17001
wait for: 0ms
wait_time: 7667971751592ms, days: 18001
wait for: 0ms
wait_time: 7581571751592ms, days: 19001
wait for: 1000ms

Т.е. мало того, что нельзя просто взять разность между (time_point::max() - time_point::now()) и для безопасности отнять оттуда какую-то разумную дельту (вроде 24 часов). Эту самую дельту нужно сделать весьма большой.

Почему так -- я хз. Вероятно какие-то фокусы трансформации вызова condition_variable::wait_for в POSIX-овый вызов. Но вот на практике довелось в это стукнуться.

PS. Зачем может потребоваться задавать большие значения для rel_time? Бывают случаи, когда либо пользователь задает время ожидания какого-то события, либо же нужно ждать "когда рак на горе свиснет". И если мы хотим время ожидания представить в программе всего одним значением std::chrono::duration, которое можно будет просто без дополнительных проверок отдать в condition_variable::wait_for (или не делать отдельно вызовы condition_variable::wait и condition_variable::wait_for), то нужно понимать, какая именно величина будет означать "когда рак на горе свиснет".

понедельник, 22 июня 2020 г.

[prog.c++] Релиз SObjectizer-5.7.1 и so5extra-1.4.1

Нашлось время и ресурсы запилить новые версии SObjectizer и so5extra.

В SObjectizer-5.7.1 реализовано несколько полезных нововведений и исправлено несколько косяков, которые пока еще не проявлялись.

Одно из самых важных изменений в SObjectizer-5.7.1 -- это возможность назначать лимиты для сообщений по умолчанию:

class demo final : public so_5::agent_t
{
public:
   demo(context_t ctx)
      : so_5::agent_t{ctx
         + limit_then_drop<msg_A>(100u)
         + limit_then_abort<msg_B>(10u)
         // That limit will be used for all other messages.
         + limit_then_drop<any_unspecified_message>(50u)
         }
   {}

   void so_define_agent() override
   {
      // Explicitly defined limit will be used.
      so_subscribe_self().event([](mhood_t<msg_A> cmd) {...});

      // A new limit object will be created for msg_C here.
      so_subscribe_self().event([](mhood_t<msg_C> cmd) {...});
   }
};

Так что теперь, если агенту нужно обрабатывать 100500 разных сообщений с одинаковыми лимитами, то можно сделать всего одно описание этого общего лимита.

Второе полезное нововведение в SO-5.7.1 -- это шаблонная функция make_agent_ref, которая позволяет гарантировать нужное время жизни указателя на агента, если этот указатель приходится отдавать в какой-нибудь коллбэк:

class io_performer final : public so_5::agent_t
{
   asio::io::tcp::socket connection_;
   std::array<std::byte, 4096> input_buffer_;

   void handle_incoming_data(std::size_t bytes_transferred) {...}

   void on_some_event(mhood_t<some_msg> cmd)
   {
      // It's time to read some data.
      connection_.async_read_some(asio::buffer(input_buffer_),
         [self=so_5::make_agent_ref(this)]
         (const asio::error_code & ec, std::size_t bytes_transferred)
         {
            if(!ec)
               self->handle_incoming_data(bytes_transferred);
         });
   }
   ...
};

Полный перечень нововведений и изменений в SO-5.7.1 можно найти здесь.

В so5extra-1.4.1 был добавлен новый тип диспетчера: asio_one_thread.

Этот диспетчер использует отдельную рабочую нить, на которой запускается io_context::run и на которой обслуживаются как I/O-операции, так и события привязанных к этому диспетчеру агентов.

По своей логике работы asio_one_thread очень похож на asio_thread_pool диспетчер, но имеет более простую реализацию. И он более удобен в использовании, когда все I/O-операции нужно "прибить" к одному контексту.

На самом деле asio_one_thread-диспетчер в so5extra появился еще месяц назад, но публичных анонсов so5extra-1.4.1 пока не было, т.к. asio_one_thread обкатывался в новой проекте. Вроде бы работает как и задумывалось, так что можно и анонсировать его наличие.


Хочу поблагодарить всех, кто выбрал SObjectizer/so5extra. Ваш интерес к этим проектам является самым важным стимулом для дальнейшего развития SObjectizer-а. А ваша обратная связь позволяет добавлять в SObjectizer/so5extra функциоальность, до который мы сами бы и не дошли. Что делает SObjectizer/so5extra полезными еще большему кругу разработчиков.

суббота, 20 июня 2020 г.

[prog.thoughts] Применимость вложенных классов в C++ и серьезность проблемы с forward declaration для них

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

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

Итак, речь пойдет о применимости вложенных классов в C++. И, немного, о том, насколько в реальности серьезна проблема невозможности forward declaration для вложенных классов. Все нижеизложенное является моим имхо, сформулированным после небольшого обдумывания данной темы. Наверняка я в чем-то не прав, поэтому все написанное ниже стоит воспринимать просто как мнение одного конкретного старого C++ника, а не истину в последней инстанции. Если же я забыл описать какой-то важный и интересный сценарий использования вложенных классов, то об этом можно (и даже нужно) написать в комментариях.

понедельник, 15 июня 2020 г.

[prog.flame] Тут опять поднимается волна хайпа вокруг Rust-а, а мне интересно, насколько просто будет на Rust-е...

...делать какие-то вещи из привычной мне окружающей реальности.

Данный пост навеян двумя факторами:

  • после поверхностного знакомства с Rust-ом при написании кода на C++ я зачастую задумываюсь о том, а как такие же вещи можно было бы сделать на Rust-е и насколько бы сложно было бы объяснить Rust-овому компилятору, что я сам понимаю, что делаю;
  • прозвучавшей на днях в этих наших Интернетиках статьей в которой кто-то выступил от имени всего Microsoft-а: "Microsoft: Rust Is the Industry’s ‘Best Chance’ at Safe Systems Programming".

Так вот, в свете очередного обещания неизбежного пришествия Rust-а в индустрию с неизбежной заменой C++, мне захотелось узнать, а насколько идиоматично будет выглядеть на Rust-е решение, которое пару недель назад в текущем проекте довелось сделать на C++.

Вкратце суть такова: есть класс-родитель, который владеет неким объектом child. При этом у класса-родителя есть метод replace_child:

class child;
using child_unique_ptr = std::unique_ptr<child>;

class parent
{
   child_unique_ptr m_child;

public:
   void replace_child(child_unique_ptr new_child)
   {
      m_child = new_child;
   }
   ...
};

Класс child -- это интерфейс, который имеет несколько реализаций. Что-то вроде:

class child
{
   parent & m_parent;

public:
   ... // some pure virtual methods.
};

class first_stage : public child {...};
class second_stage : public child {...};
class third_stage : public child {...};
...

А фокус в том, что объекты-дети заменяют у родителя сами себя. Т.е. есть код вида:

class first_stage : public child
{
   ...
   void on_some_event() override
   {
      ...
      m_parent.replace_child(std::make_unique<second_stage>(...));
   }
};

Обратить внимание нужно на то, что только parent владеет указателем на child. И вызов replace_child синхронный. Поэтому, когда child вызывает replace_child у своего parent-а, то parent внутри replace_child-а уничтожает того child-а, который и сделал вызов replace_child.

Это ведет к тому, что значение this после возврата из replace_child будет невалидным. И, если в first_stage::on_some_event после возврата из replace_child будут происходить какие-то обращения к this (например, вызовы каких-то нестатических методов и/или чтение/изменение полей объекта), то возникнет UB.

В общем-то, примененное мной решение явно опасное и неоднозначное. Да и вообще программист из меня так себе, поэтому пишу код как умею. Отсюда и такие спорные решения.

Однако, если уж мне не удалось найти более безопасного решения в C++, то программируй я на Rust-е, то и на Rust-е бы я пришел бы к чему-либо подобному. И вот тут как раз и возникает вопрос, который мне любопытен:

А можно было бы в Rust-е применить такой же прием, не прибегая к unsafe?

Мои поверхностные знания Rust-е не позволяют дать точный ответ. Есть смутные подозрения, что без unsafe не обойтись.

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

четверг, 11 июня 2020 г.

[prog.thoughts] Пару слов благодарности в адрес статической типизации

У нас тут подходит к концу четвертая неделя внезапного спурта по реализации неожиданно свалившегося на нас проекта. Написано уже порядка 12KLOC на C++17. И уже недели две с половиной лично я временами ловлю себя на том, что сегодня уже мало чего помню о том, что именно, почему и как было сделано три дня назад.

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

Тут еще нужно сказать, что C++ в принципе не тот язык, на котором можно писать быстро и качественно. Вне зависимости от цены.

Но у C++ есть очень важное полезное качество -- это статическая типизация. Которая в условиях, когда нужно сделать много и быстро, зачастую просто напросто спасает.

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

Но это достаточно тривиальные примеры. Давеча довелось столкнуться с несколько другой ситуацией. В первой реализации одного механизма, в котором ограничивалось количество байт для чтения/записи, ограничение (квота) задавалась просто типом std::size_t. Но затем первая реализация начала эволюционировать в сторону усложнения и возникла потребность интерпретировать нулевое значение квоты специальным образом. При этом пропала возможность использовать в коде простое сравнение quote < bytes_to_read.

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

В случае статически-типизированного языка это делается вообще элементарно. Для хранения квоты начинает использоваться не std::size_t, а свой собственный тип. Соответственно, компилятор просто-напросто заставит тебя поправить все места использования квот в коде.

А в C++, при желании, можно было бы для своего типа квоты перегрузить операторы сравнения и равенства. И тогда бы код вообще не пришлось бы править.

Так что статическая типизация в условиях гонки за сроками очень сильно повышает коэффициент спокойного сна.


Еще, если не сильно увлекаться auto, аннотации типов в коде очень облегчают чтение как своего старого, так и чужого кода. Вообще удивительно, но многословность C++ и все вот эти длинные template, typename, nodiscard, namespace, unique_ptr и пр. при чтении кода изрядно замыленным глазом начинают работать в плюс языку. А вот то, что записывается какими-то короткими сочетаниями букв, бывает, что и проскакивает незамеченными.

Доходит до того, что в условиях гонки за сроками и постепенно накапливающейся усталости написать что-то вроде:

std::size_t m_bytes_written{ 0u };

оказывается выгоднее, чем более компактный аналог:

std::size_t m_bytes_written{};

Ну и еще раз хочется сказать спасибо людям, благодаря которым появился C++11 и развился до C++17 (и продолжает развиваться дальше). Потому что та же самая возможность инициализировать члены класса/структуры прямо по месту их описания, а не в отдельно написанного конструкторе (как это было в C++98), так же очень и очень сильно упрощает жизнь и делает код надежнее.


Отдельно хочется сказать про тесты. Про те тесты, которые может и должен писать разработчик.

Тесты -- это очень важно и очень полезно. Хорошие тесты в дополнение к статической типизации поднимают коэффициент спокойного сна еще выше.

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

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

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

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

Но тесты обходятся недешево.

Поэтому если что-то можно закрыть системой типов или каким-то подобием контрактов в коде, то нужно именно это и делать. Сокращая объем того, что должно проверяться тестами. Потому, что "проверить, как поведет себя код, если туда придет такое-то значение" бывает гораздо дороже, чем "сделать так, чтобы такое-то значение вообще нельзя было передать, а то и получить".

воскресенье, 31 мая 2020 г.

[life.cinema] Очередной кинообзор (2020/04-2020/05)

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

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

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

2020/04

Тайлер Рейк: Операция по спасению (Extraction, 2020). Очень бодренько, много экшена, интересно следить за происходящим. А с учетом, что это творение Netflix-а, так прямо удивительно хорошо.

Расправь крылья (Donne moi des ailes, 2019). На удивление приятный фильм для семейного просмотра. Местами с очень красивыми съемками.

Охота (The Hunt, 2020). Мне зашло хорошо. Интересная смесь, которая так и норовит скатиться в абсолютный треш, но не скатывается и балансирует на тонкой грани между "вау" и "фу" :)

Морские паразиты (Sea Fever, 2019). Общее впечатление: собрались неплохие, в общем-то, в прошлом актеры, чтобы сделать недорогой околофантастический околотриллер. Но чего-то сильно не хватило. Возможно, бюджета. Возможно, хорошей идеи для фильма.

Тот, кто меня слышит (The Parts You Lose, 2018). Решился посмотреть потому, что в описании к фильму было указано и "боевик", и "триллер". На самом деле там не оказалось ни того, ни другого. А была довольно скучная и затянутая околокриминальная драма.

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

2020/05

Приговор (Une intime conviction, 2018). Как по мне, так отличное европейское кино. Понравилось.

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

Не в себе (Unsane, 2018). Очень даже неплохо. Визуальная составляющая, правда, меня регулярно раздражала. Но в целом очень даже неплохо.

Магия зверя (Seules les bêtes, 2019). Первая треть фильма вызывала уныние и желание завершить просмотр. Но потом разные линии повествования стали закручиваться в общую сюжетную канву так, что в итоге мне даже понравилось.

Аванпост (2019). Был очень приятно удивлен. Несмотря на мои собственные претензии к развязке фильма по сравнению с бондарчуковскими "Притяжением" и "Вторжением" это смотрится как вполне себе нормальная фантастика для детей и подростков.

Последнее слово (The Last Word, 2017). Редко смотрю мелодрамы и не являюсь их любителем, но вот этот фильм почему-то зашел. Хотя он какой-то незамысловатый и вполне себе предсказуемый, без каких-то интересных сюжетных ходов. Тем не менее посмотрел с удовольствием.

Просто помиловать (Just Mercy, 2019). Добротно сделанный фильм. Но очень уж на специфическую для США тему. Поэтому на меня сильного впечатления не произвел.

Помнить (Remember, 2015). Не смотря на хороших актеров и важную тему, меня не впечатлило. Сюжет попахивает плагиатом с "Помни" (Memento, 2000г.) (https://www.kinopoisk.ru/film/335/), а развязка хоть и неожиданная, но воспринимается как слишком фантастическая и притянутая за уши.

суббота, 30 мая 2020 г.

[prog.c++] Насколько дорого дергать раз в секунду некий метод у целой кучи объектов?

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

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

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

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

Для оценки была сделана небольшая программа, в которой создается несколько рабочих нитей, на каждой из которых создается множество объектов handler-ов, а затем с темпом ~1s у этих handler-ов вызывается метод on_next_turn.

Реализация on_next_turn у handler-ов не совсем пустая. А что-то типа вот такого:

void
actual_handler_one_t::on_next_turn( trigger_t & trigger )
{
   const auto now = std::chrono::steady_clock::now();
   if( now > m_border )
   {
      trigger.triggered();
      m_border = now + m_step;
   }
}

Т.е. в каждом вызове on_next_turn вызывается еще и метод now из steady_clock.

Так же в этом тесте я постарался сделать так, чтобы компилятор не повыбрасывал код, который компилятору мог бы показаться неимеющим наблюдаемых эффектов. Поэтому интерфейсы и реализации объектов handler-ов были разнесены по разным файлам, сделана пара разных реализаций handler-ов (в каждой из которых дергается steady_clock::now()), введен абстрактный тип trigger_t, у которого handler-ы время от времени вызывают triggered. Что позволяет думать, что проведенный замер таки показывает именно то, что мне нужно.

А показывает он вот что: на i7-6600U с Kubuntu 18.04 и GCC-8.4 при запуске с тремя рабочими нитями и 50K объектами handler-ами на каждой из рабочих нитей, среднее время цикла вызова on_next_turn для всех 50K handler-ов составляет 7-8ms. Худшие из увиденных результатов -- 10ms.

При этом 50K объектов -- это где-то раза в 3 больше, чем нужно на данный момент.

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

воскресенье, 24 мая 2020 г.

[prog.c++] Особенность привязки агентов к диспетчеру, о которой полезно знать в SObjectizer

Мы позиционируем SObjectizer как инструмент для упрощения разработки сложных многопоточных приложений. Упрощение, в основном, за счет того, что SObjectizer-овские агенты, как и акторы в "классической" Модели Акторов, обладают собственным приватным состоянием. И никто, кроме самого актора, не может поменять это состояние извне.

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

Этой штукой являются SObjectizer-овские диспетчеры. ИМХО, диспетчеры в SObjectizer -- это одна из отличительных черт нашего фреймворка, можно сказать, один из краеугольных камней в его основе.

Программист создавая своих агентов должен указать SObjectizer-у где агент должен работать. Для этого программист создает нужный ему экземпляр диспетчера и "привязывает" агента к этому диспетчеру. После чего агент будет работать на том (и только том) рабочем контексте, который ему выдаст диспетчер.

Диспетчеры могут быть как штатными из SObjectizer-а или so_5_extra, так и написанными самим программистом для своих специфических задач (см. пример). Суть не в этом, а в том, что если диспетчер выдал агенту какой-то рабочий контекст, то агент будет работать именно на этом контексте.

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

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

Можно представить себе агента work_manager, который накапливает сообщения от других подсистем приложения на выполнение какой-то операции (скажем, транзакции в БД, обращения к подключенным к компьютеру устройствам, выполнение запросов к внешнему сервису и т.д.). Агент work_manager складывает запросы в очередь, контролирует поступление дубликатов, упорядочивает запросы согласно каким-то правилам и отправляет очередной запрос из очереди в обработку, когда для этого предоставляется возможность.

Вполне логично сделать так, чтобы work_manager владел очередью запросов, т.е. эта очередь была бы частью его приватного состояния.

Однако, со временем мы можем прийти к тому, что work_manager будет обрастать все большей и большей функциональностью. Так, мы можем решить, что время нахождения заявки в очереди нужно ограничивать. И у work_manager-а может появится периодическое сообщение, при получении которого work_manager начнет выбрасывать те заявки, которые ждут слишком долго. Потом окажется, что отправитель заявки может захотеть проверять статус заявки и work_manager-у придется начать обрабатывать новое сообщение get_operation_status. Затем может оказаться, что отправитель запроса может захотеть отменить заявку. И work_manager-у придется начать обрабатывать еще одно новое сообщение revoke_operation. А потом мы можем захотеть, чтобы work_manager начал собирать статистику о том, сколько заявки ждут в очереди. И т.д., и т.п.

По мере усложнения агента work_manager мы можем обнаружить, что объем и сложность кода work_manager постоянно растет и мы ничего не можем с этим поделать, т.к. только агенту принадлежит очередь заявок. И со временем нам придется столкнуться с тем, что work_manager вырос в объеме до 3-4-5K строк кода, и вынужден обрабатывать несколько десятков разнообразных сообщений.

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

И вот добавление поддержки get_operation_status в более-менее сложный work_manager, а потом изъятие get_operation_status из work_manager, -- это трата времени, да еще и чревато возникновением каких-то ошибок.

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

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

Каждый из этих агентов работал бы "независимо" от другого. И мы могли бы добавлять новых агентов, а так же изымать старых когда нам этого захочется, без существенной переделки всех остальных агентов. Поскольку они вообще могут не знать друг от друге. Связывали бы этих агентов только общие разделяемые данные.

Но ведь разделяемые данные -- это плохо?

Да, плохо. Особенно когда сущности, которым требуются общие данные, работают на разных рабочих контекстах.

А вот в SObjectizer-е мы можем директивно заставить агентов работать на общем контексте.

Так, мы запросто можем создать one_thread диспетчер и привязать к нему как work_manager, так и operation_status_monitor, так и любого другого агента, которому потребуется доступ к очереди заявок.

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


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

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

Но, повторюсь, нужно такое изредка.

пятница, 22 мая 2020 г.

[work.sandness] Отказался от предложения Яндекс.Практикум поучаствовать в разработке нового обучающего курса по C++

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

Но, главное, старого пса новым трюкам не обучишь. За многие годы программирования на C++ я привык к этому языку, набил шишки, научился не наступать на разбросанные там повсюду грабли. Уже даже свои мысли и идеи чуть ли не автоматически облекаешь в C++ные конструкции.

Но при этом всем я не знаю толком C++.

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

Вот такой вот парадокс: пишу на C++, как мне кажется, без проблем, но при этом экспертом по C++ не являюсь (от слова совсем). Особенно плохо знаю и умею применять действительно современный C++, т.к. не часто пока представлялась возможность работать именно на C++17.

Да и, по большому счету, накопилась изрядная усталость от C++. Если бы не тот большой объем наработок, которые наша маленькая компания успела сделать за последние годы именно на C++, которые, к моему удивлению и радости, постепенно получают признание, то я бы лично рискнул бы сменить C++ на что-то другое. На тот же Rust, скорее всего. Или же на C#.

Но столько сил, времени и средств было вложено в наши плюсовые разработки, что выгоднее оставаться в C++. Так что это больше "выбор по неволе", а не веление сердца.

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

Но вот обучать кого-то C++... Это уже нет. Староват-с

среда, 20 мая 2020 г.

[prog.c++] Собственные ощущения от выбора C++17 в 2018, 2019 и 2020 годах

Интересно, как меняется собственное ощущение от применимости C++17 с течением времени.

Два года назад, в 2018-ом, когда мы начинали делать демо-проект Shrimp, то выбор в пользу C++17 был достаточно рискованным. Типа, ну это же изначально эксперимент, не для продакшена, да и C++17 в условиях, максимально приближенных к боевым, надо осваивать.

Год назад, в 2019-ом, когда принималось решение делать SObjectizer-5.6 уже исключительно с расчетом на C++17, было ощущение, что это не самое лучше решение и часть потенциальных пользователей от SObjectizer-а это отвернет. Но зато оно оправдано с точки зрения перспективы. Все равно популяризация SObjectizer-а идет медленно, поэтому когда информация о SObjectizer-е разойдется более-менее широко, то C++17 уже не будет проблемой. Т.е. уже в 2019-ом выбор C++17 был не то, чтобы однозначно хорошим решением, но уж точно не рискованным.

Сейчас, в 2020-ом, мы начинаем делать прототип для нового решения сразу на C++17. И это уже воспринимается как само собой разумеющийся выбор. Благо у заказчика всего одна целевая платформа и на ней по дефолту доступен gcc-8.

Ну и еще раз повторю сказанное раньше неоднократно: попрограммировав немного на C++14 уже сложно возвращаться на C++11, хотя 14-е плюсы от 11-ых не сильно отличаются. Тоже самое ощущение и после программирования на C++17: на 14-й стандарт уже некоторая ломка :)

Какой из этого вывод?

Наверное, их два.

Во-первых, пора уже переставать считать C++11 современным C++ ;)

Во-вторых, времена меняются, сидеть по 10-15 лет на древних компиляторах C++, наверное, можно. Но тут нужно отдавать себе трезвый ответ на вопрос "А зачем мне это все нужно и оправдано ли это?"

пятница, 15 мая 2020 г.

[prog.c++] Любопытное про производительность разных диспетчеров в SObjectizer

Занимаюсь сейчас разработкой нового диспетчера для SObjectizer в составе so5extra. Это asio_one_thread диспетчер. Т.е. создается отдельный экземпляр asio::io_context и на отдельной нити для этого экземпляра запускается цикл обработки событий (т.е. дергается метод io_context::run()). Вся обработка событий агентов, привязанных к такому диспетчеру, выполняется только средствами Asio. По сути, собщения на агентов на этом диспетчере доставляются через asio::post.

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

По сути, диспетчер уже готов и осталось его только задокументировать. Поэтому руки дошли до того, чтобы с помощью нового диспетчера повторить штатный пример ping-pong из SObjectizer-а. И получаются интересные результаты замеров времени работы этого примера.

Когда pinger и ponger работают на контекте одной нити (т.е. привязаны к одному io_context-у):

$ time ./target/gcc_7_5_0__x86_64_linux_gnu/release/sample.so_5_extra.disp.asio_one_thread.ping_pong -r 2700000
Configuration: separate dispatchers: no, requests: 2700000

real    0m1,056s
user    0m1,042s
sys     0m0,005s

Т.е. несколько меньше 3M ping-pong/sec, что весьма неплохо.

На штатном one_thread-диспетчере из SObjectizer-а выходит чуть больше 3M msg/sec:

$ time ./target/gcc_7_5_0__x86_64_linux_gnu/release/sample.so_5.ping_pong -r 2700000
Configuration: active objects: no, requests: 2700000

real    0m0,768s
user    0m0,750s
sys     0m0,008s

Самая веселуха начинается когда агентов разносишь на разные нити. В случае штатного one_thread-диспетчера получается:

$ time ./target/gcc_7_5_0__x86_64_linux_gnu/release/sample.so_5.ping_pong -r 2700000 -a
Configuration: active objects: yes, requests: 2700000

real    0m3,479s
user    0m5,092s
sys     0m1,795s

Т.е. порядка 776K ping-pong/sec.

А вот у нового asio_one_thread-диспетчера лучший результат вот такой:

$ time ./target/gcc_7_5_0__x86_64_linux_gnu/release/sample.so_5_extra.disp.asio_one_thread.ping_pong -r 2700000 -s
Configuration: separate dispatchers: yes, requests: 2700000

real    0m18,394s
user    0m11,465s
sys     0m14,112s

Порядка 146K ping-pong/sec или почти в пять раз хуже.

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

пятница, 8 мая 2020 г.

[prog.c++] Теперь и SObjectizer, и RESTinio перечислены в списке Awesome C++

В общем-то мелочь (на самом деле нет), а очень приятно: теперь и SObjectizer, и RESTinio перечислены в важном для C++сообщества списке Awesome C++.

Для нас, как разработчиков SObjectizer и RESTinio, это важно потому, что в мире C++ люди нередко начинают поиск подходящих для себя инструментов именно с Awesome C++. В этом смысле Awesome C++ -- это нечто вроде "желтых страниц". И теперь оба наших основных OpenSource продукта на этих "желтых страницах" перечислены.

RESTinio в Awesome C++ попал достаточно бысто. А вот включения туда SObjectizer-а пришлось ждать в течении нескольких лет. Поэтому большое спасибо всем тем, кто отдал свой голос за добавление наших разработок в этот престижный список!


Как обычно добавлю, что мы очень внимательно относимся ко всем пожеланиям и предложениям наших пользователей. Поэтому если вам чего-то не хватает в SObjectizer/so5extra, RESTinio или json_dto, то дайте нам знать. Постараемся учесть ваше мнение.

Так же, если у кого-то есть сложности в освоении и/или использовании SObjectizer и RESTinio, то не стесняйтесь, спрашивайте. Обязательно поможем. И еще с большим энтузиазмом поможем вам, если вы решите заказать у нас разработку, в которой будут использованы SObjectizer или RESTinio ;)

четверг, 7 мая 2020 г.

[prog.c++.fantasy] А что, если кто-то возьмет и форкнет C++?

И будет развивать свой диалект независимо от комитета. Причем вменяемый диалект, с преферансом и куртизанками прямо сейчас, а не после принятия C++26 или C++29?

Звучит вроде бы дико. Но вот мне давеча попалась ссылка на экспериментальный язык Circle. Это форк C++17 с дополнительными возможностями по метапрограммированию, но не только. Поскольку сейчас в C++ мне больше всего не хватает паттерн-матчинга для удобной и надежной работы с optional, variant и expected, то я пробежался по описанию того, что в Circle сделано в плане поддержки паттерн-матчинга.

И знаете что? Выглядит впечатляюще.

Вот пример:

#include <cstdio>

int main() {

  struct foo_t {
    int x, y, z;
  };
  foo_t obj { 345 };

  int Z = 6;
  @match(obj) {
    // Test an expression against the initializer.
    [_, _, 3]    => printf(".z is 3\n");  // structured binding
    [  .z: 4]    => printf(".z is 4\n");  // designated binding

    // Is Z a test/expression or a binding? If the clause fails, it's got to
    // be a test.
    [_, _, Z]    => printf("Z must be a binding\n");
    _            => printf("Z must be an expression\n");
  };

  return 0;
}

И вот какая мысль внезапно посетила мою голову: а вот что, если автора Circle возьмет "под крыло" какой-нибудь крупный игрок на рынке? Типа Facebook-а, Amazon-а, Bloomberg-а или IBM... И проект превратится из усилий энтузиаста-одиночки во вполне себе живой OpenSource продукт с солидной финансовой поддержкой за плечами.

Понятно дело, что C++ силен своей совместимостью и возможностью, при необходимости, перевести на свеживе стандарты даже старые кодовые базы. И поэтому форки C++ как бы обречены, потому что мало кому хочется потерять это свойство C++.

Но вот если форк обещает совместимость с C++17 (т.е. старые кодовые базы на него перетащить не проблема) и, при этом, более оперативное и гладкое развитие в будущем? Развитие под управлением "мягкого диктатора", а не в результате компромиссов комитета? Когда вещи, типа метапрограммирования, паттерн-матчинга и дешевых исключений в нем появляются с темпом в 6-9 месяцев, а не спустя 4-5-6 лет обсуждений в комитете?

Лично я сильно сомневаюсь, что найдется кто-то, кто вложится в поддержку Circle. Но, думаю, если бы такой нашелся, то перспективы бы открылись интересные.

воскресенье, 3 мая 2020 г.

[prog.c++] По поводу стенаний на счет сложности выразительного C++ кода

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

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

Благо, формат этого текстового файла можно было согласовать с той стороной, которая данные в него записывала. Поэтому был выбран ну очень тривиальный для парсинга вручную формат, даже проще csv.

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

При этом во мне еще была жива память о реализованном на продвинутых C++14 шаблонах easy_parser из RESTinio. И я уверен, что если бы была возможность сделать тот же самый разбор, но с применением easy_parser, то и времени бы это заняло в районе получаса, ну может быть часа. И кода было бы написано меньше. И надежность всего этого была бы выше. И работало бы это все быстрее.

Можно сказать, что на собственной шкуре проверил историю, которую когда-то рассказывал Максим Янченко (aka jazzer с RSDN), про быструю и дешевую реализацию парсеров на Boost.Spirit.


Какова мораль?

Переусложнить можно все что угодно.

Но фичи C++ действительно дают возможность писать меньше, а результат получать качественно.

И если кто-то не может это воспринять из-за каких-то личных тараканов в голове, и предпочитает клепать спаггети-код из-за нежелания познакомиться, например, с шаблонами C++, то давайте все-таки называть вещи своими именами: это из-за неасиляторства. А не из-за того, что сложный выразительный код на C++ не нужен.

пятница, 1 мая 2020 г.

[prog.c++] К сожалению, не всегда есть возможность применять SObjectizer, а хотелось бы...

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

Как правило это сопровождение и доработка уже существующих решений, которые заказчиком продолжают эксплуатироваться. Но у заказчиков либо уже нет собственных специалистов по C или C++, или же нет свободных ресурсов, поэтому и привлекаются люди со стороны.

Ну а раз это старый чужой код, то SObjectizer-а там нет и в помине.

И вот на этом фоне временами становится очевидно, насколько бы проще оказалось бы доработка/сопровождение, если бы SObjectizer или что-то подобное в проекте использовалось бы.

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

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

Но ни акторов, ни CSP в проекте не было, запросы к ресурсу шли из разных рабочих нитей, причем сам ресурc не был отдельной нитью, это был объект, методы которого можно было дергать из разных тредов одновременно. Поэтому пришлось делать систему очередей на mutex-ах и куче condition_variables. Что оказалось сильно сложнее, чем если бы использовались SObjectizer-овские агенты.

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

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

Опять же, была бы возможность использовать SObjectizer-овских агентов, вообще никаких забот: таймеры -- это важная и неотъемлемая часть SObjectizer-а, поэтому агенты могут использовать сколько угодно таймеров и когда они этого захотят.

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


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

Поэтому еще раз предлагаю тем, кто пишет код на C++, посмотреть в сторону SObjectizer-а.

Это бесплатный и свободный инструмент с большой историей. С поддержкой. С возможностью доработать под ваши нужды.

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

Поэтому зачем нужно жрать кактус и пердолится с голой многопоточностью когда можно взять SObjectizer или какой-то подобный инструмент... Я не понимаю.


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

Ну и да, если вам нужна помощь в разработке на C++, то вы можете обратиться к нам. Даже если это не связано, ни с SObjectizer, ни с RESTinio.