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

О блоге

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

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

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

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

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

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

пятница, 12 июня 2026 г.

[prog.c++] Похоже, что SObjectizer-5 будет использоваться в еще одном проекте

Больше двух лет сотрудничаю с интересным проектом. Занимался в рамках этого проекта разными задачами, начинал вообще с замены C++ REST SDK на RESTinio, а потом пошло поехало по нарастанию сложности 😎. Не все из этого было связано с многопоточностью, но многое.

Многопоточность здесь самая обычная -- std::thread, std::mutex, std::condition_variable, немного std::atomic-ов. Ну и специфика больше про параллельную обработку данных, нежели про событийно-ориентированное программирование.

Местами об отсутствии SO-5 в проекте приходилось жалеть, но, по большому счету, всерьез только один раз. Мне кажется, что та задачка на агентах решалась бы проще, чем на std::thread. Плюс еще пара-тройка мест, где простой агент с периодическим сообщением, на мой взгляд, оказался бы практичнее, чем выделенная нить с ручным циклом вокруг std::this_thread::sleep_for. Т.е. в недавнем прошлом от SO-5 полезный выхлоп вряд ли был заметным.

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

Это не точная формулировка задачи, но некоторое вполне уместное приближение. Делать это на больших объемах данных в один поток грустно по скорости, а распараллелить не так-то и просто (есть нюансы, в которые не могу вдаваться).

Такое распараллеливание средствами Taskflow было сделано, но некоторый осадочек остался:

  • в Taskflow нет встроенной поддержки таймеров. Одна из идей о том как оптимальнее поделить общий объем работы на отдельные кусочки базировалась на том, чтобы контролировать процесс через некоторые интервалы времени. Типа начнем считать на текущем треде, но поставим отложенную на 250ms задачу. Если к моменту ее запуска вычисление не завершится, то задача возьмет на себя часть оставшейся работы. Если же вычисление успеет закончится, то надобность в отложенной задаче отпадет. Только вот провалидировать эту идею из-за отсутствия таймеров в Taskflow сходу не получилось;
  • не был понятно как в Taskflow реализовать квотирование имеющихся ресурсов для того, чтобы одно вычисление не захватило все ресурсы себе, а оставшиеся вычисления сидели бы на "голодном пайке". При этом на горизонте маячит фича по приоритетам вычислений, т.е. каким-то высокоприоритеным вычислениям такая узурпация ресурсов разрешается, а вот низкоприоритетным -- нет.

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

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

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

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

Не знаю, приживется ли SO-5 в проекте окончательно. Развитие идет динамично и у команды очень легкое отношение к включению или изъятию тех или иных зависимостей. Это я с большой осторожностью отношусь к тому что включать, а что нет, и могу долго раздумывать над тем, можно ли обойтись без какой-нибудь внешней библиотеки. Но здесь ребята более шустрые и рисковые -- если подвернулось что-то потенциально полезное, то сходу добавили. Если не оправдало себя, то не менее быстро выбросили 🙂

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

Пока же я рад происходящему и есть два воодушевляющих фактора:

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

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

Так что будем пробовать и смотреть что из этого получится.

понедельник, 8 июня 2026 г.

[life.audiophilia.diy] Попробовал давеча 10mm динамики в наушниках-затычках. На свою голову...

Поскольку в области 15.4mm и 14.8mm динамиков для наушников-вкладышей перепробовал уже практически все, что заслуживало внимания, то решился на эксперимент с 10mm динамиками для внутриканальных наушников. Но т.к. заушные мониторы мне не подходят, то остановился на форм-факторе "затычек".

Взял вот эти недорогие (по меркам 10mm динамиков) драйверы:

И установил их вот в такие корпуса (из черного дерева):

Немного помучался с подбором амбушюр...

Но результат получил такой, что даже и не знаю как охарактеризовать.

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

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

В общем, в затычках как будто бы выходишь на совершенно другой уровень.

Однако, есть подозрение, что все это далеко не бесплатно для ушей. Чтобы все эти прелести воспринимать мне нужны амбушюры размера L. И хоть я и выбрал самые мягкие из имеющихся, но все равно ощущение, что вставляешь толстые палки в слуховые каналы. Это ощущение практически исчезает минут через 7-10 после начала прослушивания, хотя того комфорта, который есть со вкладышами, когда "вставил и забыл", нет и близко. Поэтому когда наушники извлекаешь, то чувствуешь пусть и небольшое, но физическое облегчение. А такой дискомфорт, хоть он и не сильный, вряд ли полезен для слуха.

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

понедельник, 1 июня 2026 г.

[prog.c++] Попробовал познакомиться с модулями C++20 и чего-то недопонял

Провел пару простых экспериментов с модулями C++20 и получил странные результаты.

Эксперименты проводились под Windows с VS2022 и VS2026 (обновления от мая 2026-го) и ArchLinux с GCC 16.1 и clang 22.1.

Ожидаемые мной результаты (т.е. отсутствие проблем компиляции/линковки) получились только с clang 22.1 и libc++. А вот с GCC и VC++ случились какие-то проблемы, которые мне сложно объяснить.

Во всех случаях сборка осуществлялась через CMake и Ninja.

Исходные коды описанных ниже тестовых программ можно найти в этом репозитории.


Эксперимент первый (case_001 из упомянутого репозитория). Очень простой модуль. Декларация в файле hello.ixx:

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

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

Фильмы

Полный Такос (Operation Taco Gary's, 2026). Совершенно укуренный и классный фильм. Мне зашел, но из-за его специфичности рекомендовать не могу -- наверняка понравится не всем. Однако, хороший пример того как за дешево можно снять приличную юмористическую фантастику.

Эдем (Eden, 2024). Хорошая история, хорошая операторская работа, отличная игра актеров. Но вот рассказана эта история так, что не особо и цепляет. Поэтому самые сильные впечатления производят реальные фотографии и кадры хроники, показанные в самом-самом конце.

Они убьют тебя (They Will Kill You, 2026). Неплохая черная комедия в стиле фэнтези с реками крови и грудами отрубленных конечностей.

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

Нормал (Normal, 2025). Не понял что это было: для комедии вроде бы слишком нудный, для боевика слишком комедийный. Посмотреть можно, но лучше ничего особенного не ждать.

Сезон охоты ("Blood of Man" или "Hunting Season", или "Mermaid", 2025). Добротно, но слишком уж неторопливо и маловато экшОна. По сути, весь фильм держится на харизме Мэла Гибсона.

Мумия ("The Mummy" или "Lee Cronin's The Mummy", 2026). Для своего жанра, в принципе, нормально. Но манера съемки у фильма такая, что воспринимается это все как театральная постановка и поэтому погружения в атмосферу ужаса не происходит.

Охота ("Hunt" или "Heon-teu" или "헌트", 2022). Есть русский бунт, бессмысленный и беспощадный. А есть корейские боевики, не менее бессмысленные и беспощадные 🙂 А если для вас все корейцы на одно лицо и их имена вы не запоминаете вообще, то следить за логикой происходящего на экране будет совсем сложно.

Мститель (Protector, 2025). Что будет, если смешать "Рембо", "Заложницу" и "Джона Уика" в очень-очень бюджетном кино, да еще и с типа крутой теткой в главной роли? А вот унылое говно под названием "Мститель" и получится.

Пастбища богов (2025). Местами красивые пейзажи. Пожалуй, это единственное, что есть хорошего в данном "кино".

Сериалы

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

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

Гнев (Man on Fire, первый сезон, 2026). Первые серии бодренькие, затем какие-то тягомотные сопли, а в финале какой-то сплошной маразм. Так что можно смело пройти мимо.

вторник, 26 мая 2026 г.

[prog.c++.bugs] Похоже наткнулся на баг в GCC 12/13 под Linux-ом. Или нет.

Дело было так: есть некий объемный и сложный шаблон класса-контейнера. Для тестирования было создано приложение с юнит-тестами на базе Google.Test. В состав этого приложения входит порядка 30 (тридцати) .cpp-файлов. В некоторых из них происходит следующее:

namespace
{

template<typename T>
struct test_traits : public my_container::default_traits<T> {
  static constexpr std::size_t key_size = 3;
};

/* namespace anonymous */

TEST(my_container, some_test)
{
  my_container::my_map<int, test_traits> map;
  ... // какие-то действия с map.
}

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

Все это работало до тех пор, пока не был добавлен еще один .cpp-файл, в котором было практически тоже самое:

namespace
{

template<typename T>
struct test_traits : public my_container::default_traits<T> {
  static constexpr std::size_t key_size = 3;
  static constexpr my_container::mode use_mode =
      my_container::mode::versioned;
};

/* namespace anonymous */

TEST(my_container, some_test_versioned)
{
  my_container::my_map<int, test_traits> map;
  ... // какие-то действия с map.
}

И вот тут-то в some_test_versioned с map стали происходит странные вещи: возникали segmentation faults там, где их быть не должно было. Попытки отладить код приводили к тому, что отладчик показывал, что отрабатывают не те ветки if-ов. А отладочные печати содержали совсем не те значения, которые должны были бы быть.

Было полное ощущение, что GCC сошел с ума.

Проект, в рамках которого все это делается, собирается VC++ под Windows и GCC под Linux-ом. Под Linux-ами используются GCC 12 и 13. Конкретно я работаю с GCC 13, но проверил и под GCC 12. Сам проект уже не очень маленький, плюс подтягивает кучу зависимостей разного калибра (включая Folly и Abseil). Все это к тому, что мероприятие по перекомпиляции проекта под какой-то свежий GCC или clang -- это попытка с негарантированным результатом. Может повезти, а может и нет.

Под Windows проверил, там ничего подобного нет, все работает как и положено. А вот под Linux-овым GCC -- проблемы.

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

После этого все описанные выше магические проблемы разом исчезли.

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

Но на 100% не уверен. Может быть здесь дело еще и в том, что у my_container::map есть шаблонный параметр шаблона, т.е.:

namespace my_container
{

template<typename T, template<typenameclass Traits>
class map { ... };

/* namespace my_container */

Поэтому его параметризация в тесте идет не конкретными типами, а шаблоном:

TEST(my_container, some_test_versioned)
{
  my_container::my_map<
    int// Это конкретный тип.
    test_traits // А это шаблон, который развернется в конкретный
                // тип уже внутри map.
  > map;
  ... // какие-то действия с map.
}

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