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

О блоге

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

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

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

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

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

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

воскресенье, 10 мая 2026 г.

[life.audiophilia.diy] Кратко о динамиках, с которыми довелось познакомиться и некоторые общие впечатления

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

Сперва, очень кратко, перечень с личными впечатлениями. Затем поделюсь своим субъективным взглядом на то, как меняется звук динамиков из магазинчиков на Aliexpress с увеличением их цены.

Итак, вот что вспоминается из недавних приобретений:

15.4mm 32ohm Ceramic Titanium PU Composite Diaphragm (золотистый композит на прозрачной диафрагме).

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

15.4mm 32ohm Ceramic Alloy Diaphragm (оранжевый композит на черной диафрагме).

Самые шикарные ВЧ и СЧ из того, что побывало у меня в руках. При этом полное отсутствие НЧ.

15.4mm 32ohm Ceramic Titanium Alloy Composite Diaphragm (голубой композит на прозрачной диафрагме).

Очень крутые ВЧ и СЧ. ВЧ особенно впечатляют по детализации и "деликатности", по сравнению с ними ВЧ от любимых мной LCP-динамиков воспринимаются уже как грубые, колкие и примитивные. С НЧ ситуация сложная. Во многих случаях мне баса хватает, но не всегда. Любителям накачки на низах точно не хватит. Объективно, громкость этих динамиков заметно падает на частотах ниже 80Hz, поэтому местами эти динамики ощущаются более "светлыми", чем конкуренты. Любопытный эффект: пару-тройку дней слушаешь с удовольствием и наслаждаешься их техничностью и мельчайшими деталями, а потом ловишь себя на том, что звук оказывается очень скучный. Да, техничный. Да, ровный. Да, с проработкой мельчайших деталей. Но очень скучный. Возможно как раз из-за некоторой нехватки НЧ. Тем не менее, это, пожалуй, самое интересное из последних приобретений. После знакомства с данными динамиками слушать любимые мной LCP-динамики стало сложнее, т.к. ВЧ в LCP гораздо "жестче" и с меньшим количеством "оттенков" и "нюансов". Хотя казалось бы... Ведь ранее по детализации у LCP практически не было конкурентов.

15.4mm 32ohm Cobalt Diaphragm. Отличные динамики с хорошей детализацией, очень приятной тоналкой и глубокими НЧ. Являются прямыми конкурентами самых первых динамиков из этого обзора, но чуть более резкие, контрастные и как раз более "прозрачные", чем динамики с керамическо-титаново-полиуретановым композитом. Мне показалось, что по детализации эти чуть чуть, но уступают всем трем перечисленным выше. Поэтому если хочется выслушивать самые-самые тонкие детальки, то нет. А вот если нужен объемный и тяжелый бас, то эти выигрывают. Поэтому-то я их редко слушаю, в какой-то момент складывается впечатление, что НЧ доминирует, а музыка звучит более гнетуще, чем хотелось бы.

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

Теперь же поделюсь субъективным мнением о тенденции, которую наблюдаю приобретая все более и более дорогие динамики.

У меня складывается ощущение, что чем дороже динамики, тем больше в них ВЧ и тем меньше НЧ.

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

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

Но, при этом, динамики с недостатком НЧ, как ни странно, производят серьезный ВАУ-эффект. За счет того, что НЧ мало и они отведены далеко назад, происходят две прикольные штуки:

  • лучше слышны мельчайшие детали на ВЧ и больше подробностей на СЧ (добавим сюда еще и то, что в техническом плане дорогие динамики реально более детальные и имеют меньше искажений). Поэтому при переходе с дешевых драйверов на дорогие просто поражаешься тому, насколько же больше ты слышишь нюансов в одной и той же музыке. Как раз то, что НЧ мало, и позволяет сконцентрироваться именно на ВЧ. Т.е. за счет отсутствия информации о НЧ невольно приходится довольствоваться ВЧ;
  • усиливается эффект 3-мерности. Когда НЧ мощные, то ударные и контрабас звучат как будто бы совсем рядом. А когда НЧ мало, то их звук доноситься как бы издалека. И кажется, что вокалист стоит прямо перед тобой, а барабанщик с контрабасистом где-то далеко за ним.

С непривычки эти два фактора поражают настолько, что первые несколько дней оказываешься совершенно очарованным новым звучанием и с большим интересом переслушиваешь всякое разное. При этом часто ловя этот самый ВАУ-эффект, когда старые и заслушанные до дыр композиции начинают звучать по новому. Особенно интересно для меня лично начинает восприниматься легкий инструментальный джаз (если этот жанр применим к таким группам как GoGo Penguin и Tingvall Trio) и тяжелый метал (скажем, альбом Senjutsu от Iron Maiden).

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

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

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

[prog.c++] Хочется странного, но теперь уже от std::map

В std::map начиная с C++17 есть отличный метод try_emplace. Он особенно хорош, когда конструирование mapped_type очень дешевое. Например, когда в качестве ключа у нас int, а в качестве значения -- структура с несколькими int-ами внутри. Тогда получается эффективно: попробовали вставить, если ключа в map еще нет, то из параметров сконструировали mapped_type и добавили в map новое значение. А если же в map ключ уже есть, то передача в try_emplace нескольких int-ов как параметров для конструктора mapped_type -- это копейки, на которые во многих случаях можно просто не обращать внимания.

Но вот когда у нас в качестве mapped_type какой-то "тяжелый" объект, вот тогда ситуация грустнее. Например, mapped_type -- это std::unique_ptr с указателем на класс с кучей собственных контейнеров внутри.

Если мы напишем что-то вроде:

auto [it, inserted] = my_map.try_emplace(key, std::make_unique<heavy_object>(...));

то это явная пессимизация -- ведь нам придется создавать heavy_object при каждом обращении к try_emplace, даже если ключ в map уже есть.

Я вижу два стандартных пути выхода из этой ситуации.

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

auto it = my_map.find(key);
if(it == my_map.end()) {
  it = my_map.emplace(key, std::make_unique<heavy_object>(...)).first;
}
// Теперь it указывает на объект внутри std::map.

Но здесь плохо то, что для вставки объекта поиск по std::map нужно будет делать дважды.

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

auto [it, inserted] = my_map.try_emplace(key, std::unique_ptr<heavy_object>{});
if(inserted) {
  // Была вставка. Теперь у нас в my_map лежит нулевой указатель, нужно
  // это исправить.
  it->second = std::make_unique<heavy_object>(...);
}

Но здесь плохо то, что нам нужно позаботиться об exception safety, ведь вызов make_unique может бросать исключение. И самое худшее, что можно сделать, это написать что-то вроде:

auto [it, inserted] = my_map.try_emplace(key, std::unique_ptr<heavy_object>{});
if(inserted) {
  // Была вставка. Теперь у нас в my_map лежит нулевой указатель, нужно
  // это исправить.
  try {
    it->second = std::make_unique<heavy_object>(...);
  }
  catch(...) {
    // Удаляем только что вставленный пустой указатель.
    my_map.erase(it);
    throw;
  }
}

Гораздо лучше было бы иметь что-то вроде scope(failure) из D. Что-то вроде:

auto [it, inserted] = my_map.try_emplace(key, std::unique_ptr<heavy_object>{});
if(inserted) {
  // Была вставка. Теперь у нас в my_map лежит нулевой указатель, нужно
  // это исправить.
  // Защищаемся от исключений.
  auto guard = at_failure(
    // Эта лямбда будет вызвана если выход из скоупа произойдет
    // из-за исключения.
    [&it, &my_map]() {
      my_map.erase(it);
    });
  it->second = std::make_unique<heavy_object>(...);
}

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

auto [it, inserted] = my_map.try_emplace(key, std::unique_ptr<heavy_object>{});
if(inserted) {
  // Была вставка. Теперь у нас в my_map лежит нулевой указатель, нужно
  // это исправить.
  // Защищаемся от исключений.
  do_with_rollback_on_exception(
    // Что хотим сделать.
    [&it, ...]() {
      it->second = std::make_unique<heavy_object>(...);
    },
    // Эта лямбда будет вызвана если первая лямбда бросит исключение.
    [&it, &my_map]() {
      my_map.erase(it);
    });
}

Но все эти приседания были бы не нужны, если бы был вариант try_emplace, который бы принимал не аргументы для конструктора mapped_type, а фабрику, которая может породить экземпляр mapped_type для вставки:

auto [it, inserted] = my_map.try_emplace_with_factory(key,
  // Эта лямбда будет вызвана, если объекта в map нет.
  [...]() {
    return std::make_unique<heavy_object>(...);
  });

К сожалению, такого варианта try_emplace_with_factory в std::map нет.

PS. Вышесказанное относится и к std::unordered_map.


Upd. В обсужении на LinkedIn посоветовали обходной маневр вида:

#include <map>
#include <string>

class simple_data {
    std::string _data;
public:
    simple_data(const char * s) : _data{ s }
    {}
};

class data_holder {
    std::string _data;
public:
    template<typename... Args>
    data_holder(Args && ...args) : _data{ std::forward<Args>(args)... }
    {}
};

template<typename F>
struct deferred {
    F _f;

    template<typename T>
    operator T() { return _f(); }
};

int main()
{
    std::map<int, simple_data> m1;
    m1.try_emplace(0, deferred{ []{ return simple_data{ "Hello, world" }; } });

    std::map<int, data_holder> m2;
//    m2.try_emplace(0, deferred{ []{ return std::string{ "Hello, world" }; } });
    m2.try_emplace(0"Hello, world");
}

Но не для всех случаев он будет работать. В частности для data_holder-а не сработает, т.к. у data_holder-а есть шаблонный конструктор (цынк).

понедельник, 4 мая 2026 г.

[prog] Любопытное из книги "C++ Ultra-Low Latency: Multithreading and Low-Level Optimizations"

Попалась в руки книга "C++ Ultra-Low Latency: Multithreading and Low-Level Optimizations". Начал ее листать, т.к. темой низкоуровневых оптимизаций на C++ никогда не занимался. Мне всегда было интересно писать корректно работающий код, который был бы понятным и сопровождабельным, который бы было просто использовать правильно, но сложно неправильно, но в плане скорости работы кода никогда не упарывался. В общем, как однажды сказали про мой код: "получение гарантий корректности времени компиляции при этом не используя Haskell" 🙂

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

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

Дело в том, что для меня всегда наиболее естественно было писать в стиле:

if(some_condition) {
  ... // тут много строчек кода с выполнением основной логики.
  ...
  ...
}
else {
  return some_error_code;
}

Т.е. большинство действий сосредотачивается именно в ветке then.

При этом регулярно встречал рекомендации, что в if-ах в then должны быть наиболее короткие блоки кода. Мол так код воспринимается лучше: когда в then короткий блок, то мы еще помним контекст когда доходим до else. А вот если в then длинный блок, то когда мы доберемся до else, то на фоне действий из then уже не будем понимать где находимся. Такие рекомендации декларируют в качестве "хорошего" стиля вот такой:

if(!some_condition) {
  return some_error_code;
}
else {
  ... // тут много строчек кода с выполнением основной логики.
  ...
  ...
}

Или даже вот такой:

if(!some_condition) {
  return some_error_code;
}

... // тут много строчек кода с выполнением основной логики.
...
...

Но оба эти стиля мне не нравятся на каком-то интуитивном уровне. Особенно последний (про этот стиль я уже высказывался: например, в контексте языка Go). Хотя, если мы в проекте придерживаемся принципов defensive programming, то начало метода/функции из if-ов для проверки входных параметров/состояния объекта, т.е. что-то вроде:

int f(int a, int b, int c) {
  if(a < 0) return invalid_parameter_a;
  if(b < 10 || b > 100) return invalid_parameter_b;
  if(c > 1000) return invalid_parameter_c;

  ... // Далее основная логика.
}

то такие короткие if-ы -- это нормально. Но когда проверки входных данных завершены и идет основной код метода/функции, то if-ы с короткими then или if-ы, в которых только return, на мой взгляд, ухудшают код (хуже только циклы, внутри которых короткие if-ы с continue).

И вот листая книгу "C++ Ultra-Low Latency: Multithreading and Low-Level Optimizations" вдруг натыкаюсь на подтверждение того, что привычный для меня способ написания if-ов имеет под собой обоснование еще и с точки зрения эвристик компилятора по обеспечению branch predictions.

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

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

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

Фильмы

Проект "Конец света"/Проект «Аве Мария» (Project Hail Mary, 2026). Хорошая, добрая сказочка. Отличное кино для семейного просмотра.

Удачи, веселитесь, не умирайте (или "Удачи, веселья, не сдохни!", Good Luck, Have Fun, Don't Die, 2025). В сюжет, конечно, лучше не вдумываться, но в целом смешно и бодро, временами сатира на современную диджилитизацию прям огонь.

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

Кровь на границе (Frontier Crucible, 2025). Неплохо, но я и положительно отношусь к жанру вестернов. А вот для тех, кто фильмы про Дикий Запад не любят, может быть скучно. Операторскую работу выделю отдельно, местами очень красивые кадры получились.

Наследник (How to Make a Killing, 2026). Не шедевр, но неплохо, мне зашло.

Я иду искать 2 (Ready or Not: Here I Come, 2025). Мне показалось, что это тоже самое, что и первый фильм. Поэтому если первый понравился, можно глянуть и второй, но не нужно ждать чего-то нового и необычного. А если первый не зашел, то от просмотра второй части лучше отказаться.

Битва на Шельде (The Forgotten Battle или De Slag om de Schelde, 2020). Довольно нудно и затянуто, хотя и трагично. Батальных сцен немного, они вызывают двойственные чувства -- взрывы если и не реальной пиротехникой сделаны, но выглядят внушительно, а вот выстрелы из стрелкового оружия нарисованы и компьютере и это сильно заметно.

Трофей (2025). Редкая бредятина, в происходящее на экране невозможно поверить.

Братья под огнем (Brothers Under Fire или Sierra Madre, 2026). Редкой дешевизны халтура. Не смотреть ни в коем случае.

Сериалы

Черное солнце (2023) и Черное солнце-2 (2025). Понравилось, особенно первый сезон.

Больница Питт (The Pitt, второй сезон, 2026). Снято на уровне первого сезона. Но не лучше. И ничего нового не добавляет. Поэтому ощущения странные: с одной стороны, вполне себе нормальное кино, с другой стороны, вполне можно.

Детектив Холе (Harry Hole или Jo Nesbo’s Detective Hole или Jo Nesbøs Harry Hole, первый сезон, 2026). Очень нудно и очень затянуто. Уложили бы все тоже самое в три серии, можно было бы смотреть, а так нудная нудятина.

Кино вне категории

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

пятница, 17 апреля 2026 г.

[prog.c++] Просмотрел чужой доклад о SObjectizer

Марко Арена сделал доклад о SObjectizer и этот доклад публично доступен на YouTube: [Milan Meetup] Concorrenza multiparadigma con SObjectizer (Marco Arena)

Выступление на итальянском языке, но с помощью Yandex Browser можно послушать в переводе.

Дополнительные материалы (слайды на английском и репозиторий с кодом) доступны здесь: https://lao.bz/mcs

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

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

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