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

О блоге

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

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

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

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

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

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

понедельник, 9 сентября 2024 г.

[prog.c++] Сюрприз с размером производного класса в разных компиляторах

С размером вот этой простой структуры на x64 при выравнивании по умолчанию все понятно:

struct base {
    void * m_ptr;
    short m_sh;
    char m_ch;
};

Вполне ожидаемо имеем 16 байт. Из которых реально используются 11, а остальное -- это выравнивание на границу 8 байт (т.к. указатель на 64-х битовых платформах занимает 8 байт).

А вот что с размером вот этой не менее простой структуры?

struct derived : public base {
    char m_mask;
};

А вот тут сюрприз!

Результат зависит от компилятора: для VC++ получается 24 байта (и в этом есть некоторая логика), тогда как для GCC и clang -- 16 байт (и в этом также есть логика).

Но лично мне представляется, что логика GCC/clang как-то логичнее. Тогда как поведение VC++ стало неприятным открытием. Можно даже сказать как серпом...

Посмотреть самому можно на godbolt: https://godbolt.org/z/sbo8jPcxE

пятница, 6 сентября 2024 г.

[prog.c++] В склерозник: красивый кусок кода для подсчета смещения полей объекта и их выравнивания

В LinkedIn встретил ссылку на реализацию тупла без использования рекурсии: тыц. Реализация активно эксплуатирует C++ный fold expression и std::integer_sequence. Вот прям отличная демонстрация того, как эти фичи могут (и должны?) применяться.

Еще очень удачно совпало, что ссылка эта попала мне на глаза вскоре после того, как я проделал в чем-то похожую работу. Правда, у меня ситуация была чуть сложнее, ведь в тупле все N полей присутствуют всегда, поэтому размер всех туплов одного типа одинаков и фиксирован. Тогда как в моем случае объекты могут состоять из разного набора полей, поэтому нужно размер каждого объекта определять индивидуально, да и расположение полей в каждом объекте может быть уникальным. Так что в своей реализации без метапрограммирования на базе рекурсии я не смог обойтись. Возможно, еще и потому, что у меня мало опыта с fold expression и std::integer_sequence.

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

Этот кусочек кода отвечает за подсчет расположения полей внутри объекта с учетом их правильного выравнивания. В результате своей работы функция calculate_positions возвращает std::array размером (N+1), где элементы 0..(N-1) содержат смещение i-го поля, а элемент N -- общий размер объекта.

Оригинал кода можно увидеть здесь, а вот чуть-чуть модифицированная мной версия:

template <class T>
struct PositionWrapper {};

template <std::size_t _last, std::size_t... _is>
struct Positions
{
  static consteval auto to_array() {
    return std::array<std::size_tsizeof...(_is) + 1>{_is..., _last};      
  }
};

template <class T, std::size_t _last, std::size_t... _is>
consteval auto operator+(
  const Positions<_last, _is...>& /*_sizes*/,
  const PositionWrapper<T>& /*_w*/)
{
  if constexpr (_last % alignof(T) == 0) {
    constexpr auto last_new = _last + sizeof(T);
    return Positions<last_new, _is..., _last>{};
  } else {
    constexpr auto last_corrected = (_last / alignof(T) + 1) * alignof(T);
    constexpr auto last_new = last_corrected + sizeof(T);
    return Positions<last_new, _is..., last_corrected>{};
  }
}

template <class... Types>
consteval auto calculate_positions() {
  return (Positions<0>{} + ... + PositionWrapper<Types>{}).to_array();
}

понедельник, 2 сентября 2024 г.

[prog.c++] Конструирование объекта в котором физически могут отсутствовать некоторые поля

На прошлой неделе делал интересную штуку: С++ный объект, в котором могут физически отсутствовать некоторые части.

Пришлось заняться этим потому что в текущем проекте было большое количество объектов вида:

struct data {
  mandatory_field_one m_one;
  mandatory_field_two m_two;
  mandatory_field_three m_three;

  std::vector<first_opt_attribute_type> m_first_type_attrs;
  std::vector<second_opt_attribute_type> m_second_type_attrs;
  std::vector<third_opt_attribute_type> m_third_type_attrs;
};

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

Объектов типа `data` было много, в некоторых случаях десятки миллионов. И на таком количестве хранение пустых std::vector внутри миллионов объектов типа `data` оказывается очень расточительным (ведь каждый пустой std::vector -- это, как минимум, 24 байта -- size, capacity + указатель на блок с данными).

Объекты же `data` создавались в динамической памяти и ссылки на них хранились как std::unique_ptr.

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

В общем, хотелось бы, чтобы поля m_first_type_attrs, m_second_type_attrs и m_third_type_attrs таки оставались внутри `data`, но ничего бы не потребляли, если были пустыми.

Здесь бы очень ко двору пришлись бы массивы вроде чего-то такого:

struct data {
  mandatory_field_one m_one;
  mandatory_field_two m_two;
  mandatory_field_three m_three;

  std::size_t m_first_type_attrs_count;
  first_opt_attribute_type m_first_type_attrs[m_first_type_attrs_count];

  std::size_t m_second_type_attrs_count;
  second_opt_attribute_type m_second_type_attrs[m_second_type_attrs_count];

  std::size_t m_third_type_attrs_count;
  third_opt_attribute_type m_third_type_attrs[m_third_type_attrs_count];
};

Но в C++ таких массивов нет. Это во-первых. А во-вторых, даже такой способ хранения, будь он возможен, все равно был бы расточительным. Ведь если для объекта нет атрибутов, то поля *_attrs_count с нулевыми значениями в нем все равно есть. Три поля std::size_t -- это 24 байта, умножаем на десяток миллионов объектов `data` и теряем пару десятков мегабайт на ровном месте 🙁

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

Получилось что-то вроде такого:

// Описание части, которая в объекте присутствует всегда.
struct data_header {
  mandatory_field_one m_one;
  mandatory_field_two m_two;
  mandatory_field_three m_three;

  // Вспомогательные типы для идентификации опциональных полей.
  struct first_attr_tag {};
  struct second_attr_tag {};
  struct third_attr_tag {};

  // Типы опциональных полей.
  using first_attr_vec = compound::vec<first_attr_tag, first_opt_attribute_type>;
  using second_attr_vec = compound::vec<second_attr_tag, second_opt_attribute_type>;
  using third_attr_vec = compound::vec<third_attr_tag, third_opt_attribute_type>;
};

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

std::vector<second_opt_attribute_type> attrs{...};

auto my_data = data::build(
  // Данные для инициализации фиксированной части должны быть всегда.
  data_header{...},
  // А вот опциональные данные нужны только те, которые в объекте присутствуют.
  data::second_attr_vec::move_from(attrs));

// Доступ к полям фиксированной части возможен напрямую.
std::cout << my_data->m_one << std::endl;
std::cout << my_data->m_two << std::endl;

// Доступ к опциональным полям нужно проверять.
if(my_data->has_field<data::second_attr_tag>()) {
  // Поле есть, можно с ним работать.
  for(const auto & a : m_data->get_field_ref<data::second_attr_tag>()) {
    ...
  }
}

Не буду вдаваться в подробности, т.к. все это делалось в закрытом проекте. Но скажу, что весь фокус тут в функции-фабрике build, которая вычисляет сколько же места потребуется (с учетом необходимых выравниваний) и создает обычный динамический массив std::byte. А уже дальше внутри этого массива посредством placement new размещается все то, что в объекте должно присутствовать.

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

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

Что напрягало, как это функциональный стиль C++ного метапрограммирования (работа велась в рамках C++20). Вся эта рекурсия по спискам типов... 😓 Если сталкиваешься с этим самым метапрограммированием раз в пару лет, то непросто на эти рекурсии перестроится.

Еще, конечно же, доставляла тема с std::launder. Но к ней мне придется вернуться еще раз, как минимум.

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

ЗЫ. Кстати говоря, пригодился трюк вот из этой статьи: "Pulling a single item from a C++ parameter pack by its index".

воскресенье, 1 сентября 2024 г.

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

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

Фильмы

Ненасытные люди (Greedy People, 2024). Как по мне, так это что-то вроде "Фарго", но на минималках. Совершенно не шедевр, но смотреть интересно, а на фоне остального современного шлака, как вообще выглядит нормально сделанным кино.

Зачинщики (The Instigators, 2024). Не очень понял что это было. Для комедии мне не хватило юмора, для серьезной криминальной драмы не хватило серьезности и драмы. Такое впечатление, что материала было на минисериал, но почему-то решили сделать один полнометражный фильм, в котором не удалось раскрыть и десятой доли всех промелькнувших в кадре персонажей.

Нефариус (Nefarious, 2023). Мне почти что зашло. На мой личный вкус первые 3/4 фильма просто шикарные, все держится на диалогах и актерской игре. А вот развязку, имхо, можно было сделать и покруче. Так что финал слегка разочаровал, но вот то, что ему предшествовало мне понравилось.

Материнский инстинкт (Mothers' Instinct, 2024). Качественно снято. Но! Во-первых, никто из героев не вызвал сопереживания. Во-вторых, за происходящим оказалось следить не интересно и, есть ощущение, что авторы как-то и не сильно пытались зрителя запутать. Общее впечатление: добротно сделано, но не цепляет.

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

Смерч 2 (Twisters, 2024). Красочно сделанный динамичный аттракцион. Но все настолько шаблонно, а персонажи настолько картонные, что все последующие события прочитываются наперед уже после первых 15 минут фильма.

Отчаянные наследники (El favor, 2023). Средненькая комедия из категории "богатые тоже плачут", местами откровенно глупая, местами смешная.

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

Сериалы

Мошенники (первый сезон, 2023). Отлично, посмотрел с большим удовольствием.

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

Фурия (Furia, первый сезон, 2021). Во-первых, очень затянуто. Во-вторых, слишком уж часто возникает вопрос "Что за фигню нам здесь показывают?" Так что первый сезон не зашел, желание посмотреть второй сезон не возникло.

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

Пара фильмов, которые я затрудняюсь адекватно оценить.

Бордерлендс (Bordarlands, 2024). Не понял на кого кино рассчитано: для взрослых оно слишком детское, для детей слишком взрослое. На меня произвело впечатление откровенного треша, но такого, про который можно сказать "настолько плохо, что даже хорошо".

Покажи мне Луну (Fly Me to the Moon, 2024). Не являюсь ценителем жанра комедийных мелодрам, так что понятия не имею, как этот фильм оценивать. Но если брать отдельно сюжет, отдельно операторскую работу и отдельно игру актеров, то в целом это один из наиболее добротных и вменяемых фильмов за последние несколько месяцев.

пятница, 30 августа 2024 г.

[job.flame] Откуда такой акцент на софт-скиллз в последние годы?

Позволю себе немного пофлеймить в теме, в которой не разбираюсь (с другой стороны, а разве можно как-то иначе? 😉)

Когда я начинал работать, ни о каких софт-скиллах и речи не было. Это, по моим ощущениям, вообще тема текущего десятилетия. А реальный акцент на этих самых софт-скиллах делается в последние 3-4 года.

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

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

А давеча посмотрел случайно два интервью с нейробиологом Вячеславом Дубыниным:

ЛЮДИ ТУПЕЮТ! Причина - не алкоголь или никотин. Вячеслав Дубынин.

Вячеслав Дубынин про поиски себя, выгорание, аффирмации. Как работа влияет на мозг?

И у меня появилась другая версия. Практически медицинская 🙂

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

Мы с раннего детства проводили много времени в разного рода коллективах: будь то детский сад, школа, кружки, спортивные секции, пионерские и спортивные лагеря, не говоря уже про двор. Сплошной реал, никакой виртуальности. В котором навыки этого самого общения, как и навыки существования в рамках коллектива, прививались прямыми и незатейливыми способами. Уже к 4-5-м классам школы практически каждый на своей шкуре усваивал, что трепать языком нужно поменьше, за слова принято отвечать, да и получить в лыч за эти самые слова можно очень даже легко и непринужденно. А можно и не за слова, а просто потому, что оказался не в то время и не в том месте. Всякое бывало... В общем, прямые и незатейливые способы иногда оказывались настолько жесткими и суровыми, что не все вписывались в эти условия, но это уже тема другого разговора.

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

Что наводит меня на следующую мысль: а что если у молодого поколения, которое вместо игры в "войнюшку" всем двором, чатилось в соцсетях, просто оказались недостаточно развиты те самые нейросети для общения и социализации, которые естественным образом прокачивались у нас, старпёров, в нашем далеком уже детстве?

Т.е. тупо часть мозга, которая у поколения 1960-х, 1970-х и 1980-х была хорошо развита вот просто потому, что другого выхода-то и не было, у поколения 2000-х уже просто на недоразвитом (в биологическом смысле) уровне. И, грубо говоря, нынешние 20-летние не могут в человеческое общение на таком же уровне, на котором умели мы.

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

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


Ну ладно, эта медицинская версия слишком уж попахивает стариковским ворчанием по поводу никчемной молодежи. Так что вот еще одна.

Ранее (лет 25 назад) в найме роль HR была не столь существенна, как сейчас. А вот нонче, судя по постам в LinkedIn, практически только через HR.

Могут ли HR адекватно оценить хард-скиллы соискателей? Сильно сомневаюсь. Может и есть единицы таких продвинутых, но именно что единицы.

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

Но эта версия, честно говоря, слишком банальна. А потому и не интересна 😎