вторник, 1 января 2030 г.

О блоге

Более двадцати лет я занимался разработкой ПО, в основном как программист и тим-лид, а в 2012-2014гг как руководитель департамента разработки и внедрения ПО в компании Интервэйл (подробнее на LinkedIn). В настоящее время занимаюсь развитием компании по разработке ПО stiffstream, в которой являюсь одним из соучредителей. Поэтому в моем блоге много заметок о работе, в частности о программировании и компьютерах, а так же об управлении.

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

понедельник, 31 декабря 2029 г.

[life.photo] Характерный портрет: вы и ваш мир моими глазами. Безвозмездно :)

Вы художник? Бармен или музыкант? Или, может быть, коллекционер? Плотник или столяр? Кузнец или слесарь? Владеете маленьким магазинчиком или управляете большим производством? Реставрируете старинные часы или просто починяете примус? Всю жизнь занимаетесь своим любимым делом и хотели бы иметь фото на память?

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

среда, 27 марта 2024 г.

[work] Открыт для сотрудничества в качестве C++ разработчика

В виде (суб)контракта с нашей компанией СтифСтрим.

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

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

Работаю не быстро, но качественно, беру недорого.

Оценить мой уровень можно, например, про проекту aragata, реализованному мной практически в одиночку. Код можно увидеть на GitHub-е, на Хабре есть две статьи о том, что это и как работает: вводная статья и описание сделанных по результатам нагрузочных испытаний оптимизаций + вот этот пост.

В качестве дополнительных примеров: timertt (+ документация), so5extra (+ документация) -- эти проекты так же написанные мной самостоятельно.

Связаться со мной можно через eao197 на gmail тчк com. Если кому-то интересен профиль на LinkedIn, то вот.


Это сообщение повисит какое-то время вверху. Потом будет видно, имеет ли смысл пытаться дальше оставаться в C++.

[prog.thoughts] "Универсальный" против "специализированного" на примере из SObjectizer-а

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

В SObjectizer агенты подписываются на сообщения: если подписка есть, то сообщение до агента может дойти, а если подписки нет, то сообщение точно не дойдет. А раз есть подписки, то их нужно как-то хранить. Соответственно, возникает простой вопрос: "Как хранить?"

Фокус в том, что заранее неизвестно, сколько у агента будет подписок. Может быть две, может быть двадцать две, может быть две тысячи и двадцать две. А может и двести тысяч. Ну мало ли. Никто же не запрещает 😉

Как по мне, так это означает, что невыгодно в агенте хранить подписки одним и тем же способом вне зависимости от количества этих самых подписок. Ведь нет контейнеров, которые бы отлично работали бы при любом количестве элементов. Так, если у нас всего две подписки, то hash-таблица избыточна, как и бинарное дерево поиска (где каждый узел -- это отдельный объект в динамической памяти). А если у нас 100500 подписок, да они еще и активно создаются/уничтожаются, то непрерывный вектор здесь не вариант, т.к. вставки в середину (как и удаления из середины) дороги.

А раз так, значит напрашивается введение отдельного понятия, subscription_storage. Т.е. некого интерфейса, который будет скрывать детали хранения подписок. Соответственно, в агенте хранится ссылка на subscription_storage, а вся работа с подписками ведется через интерфейс subscription_storage.

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

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

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

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

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

ЗЫ. У SObjectizer-а давеча состоялся очередной релиз. Как раз была добавлена еще одна реализация subscription_storage. И таки дошли руки сделать описание этой штуки в Wiki-проекта.

среда, 20 марта 2024 г.

[prog.cmake] Попытался заглянуть в документацию по CMake. Пригорело. Нехило так пригорело :(

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

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

PS. Пожалуйста, не нужно спрашивать у меня чем заменить CMake. Уже почти 20 лет пользуюсь собственной системой сборки, написанной на Ruby. Когда-то даже прикладывал усилия чтобы продвинуть свой лисапед в массы, но не преуспел. На этом считаю свой долг по улучшению C++ной экосистемы полностью выплаченным.

вторник, 19 марта 2024 г.

[prog.c++.kill'em-all] Еще пример C++ного кода от которого у меня изрядно подгорает

Вот не нужно писать кроссплатформенный код вот так:

enum class status { not_started, started, shutting_down, stopped };

#if defined(PLATFORM_WINDOWS)
[[nodiscard]] const wchar_t * to_str(status st) noexcept {
  switch(st) {
    case status::not_started: return L"not_started";
    case status::started: return L"started";
    case status::shutting_down: return L"shutting_down";
    case status::stopped: return L"stopped";
  }
}
#else
[[nodiscard]] const char * to_str(status st) noexcept {
  switch(st) {
    case status::not_started: return "not_started";
    case status::started: return "started";
    case status::shutting_down: return "shutting_down";
    case status::stopped: return "stopped";
  }
}
#endif

вот не надо, пожалуйста.

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

Сделайте хотя бы так:

// Эта кухня должна жить в отдельном заголовочном файле.
#if defined(PLATFORM_WINDOWS)
  using platform_char_type = wchar_t;
  #define STRING_LITERAL(str) L##str
#else
  using platform_char_type = char;
  #define STRING_LITERAL(str) str
#endif

enum class status { not_started, started, shutting_down, stopped };

[[nodiscard]] const platform_char_type * to_str(status st) noexcept {
  switch(st) {
    case status::not_started: return STRING_LITERAL("not_started");
    case status::started: return STRING_LITERAL("started");
    case status::shutting_down: return STRING_LITERAL("shutting_down");
    case status::stopped: return STRING_LITERAL("stopped");
  }
}

четверг, 14 марта 2024 г.

[prog.flame] Самодокументирующися код против документированного, наглядно

Намедни в LinkedIn поиронизировал на счет "самодокументирующегося кода". В очередной раз 😎

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

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

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

Под катом оригинальный фрагмент с комментариями. Такими, какими они и были написаны при разработке. Но сперва этот же фрагмент, но вообще без комментариев. Именно так "самодокументирующися" код и выглядит, по моему (не)скромному опыту.

class DefaultThreadPoolScheduler final : public Scheduler
{
   ...

private:
   struct WorkerData
   {
      std::mutex _lock;
      std::condition_variable _wakeupCondition;

      Scheduler::TaskUniquePtr _taskToRun;

      bool _shutdownInitiated{ false };
   };

   using WorkerDataContainer =
      std::vector<std::reference_wrapper<WorkerData>>;

   std::latch _allWorkersStartedLatch;

   std::mutex _lock;

   TasksContainer _tasksQueue;

   ThreadPool _threadPool;

   WorkerDataContainer _availableWorkers;

   bool _shutdown{ false };
};

void DefaultThreadPoolScheduler::doWork() noexcept
{
   WorkerData thisWorkerData;

   {
      std::lock_guard schedulerLock{ _lock };
      _availableWorkers.push_back(std::ref(thisWorkerData));

      _allWorkersStartedLatch.count_down();
   }

   bool shutdownIntitiated{ false };
   while( !shutdownIntitiated )
   {
      std::unique_lock workerLock{ thisWorkerData._lock };

      if( TaskUniquePtr taskToRun = std::move(thisWorkerData._taskToRun); !taskToRun )
      {
         shutdownIntitiated = thisWorkerData._shutdownInitiated;
         if( !shutdownIntitiated )
         {
            thisWorkerData._wakeupCondition.wait(workerLock);

            shutdownIntitiated = thisWorkerData._shutdownInitiated;
         }
      }
      else
      {
         workerLock.unlock();
         taskToRun->run(Scheduler::RunCondition::Normal);
         completeTaskThenTryGetNext(std::move(taskToRun), thisWorkerData);
      }
   }
}