Бестиарий программирования
1.13K subscribers
350 photos
5 videos
5 files
443 links
Наблюдения за жизнью ошибок в коде.
Андрей Карпов.

ГОСТ Р 71207-2024, ГОСТ Р 56939-2024, РБПО, Статический анализ кода

Канал-дублёр в MAX: https://max.ru/join/3VWTp9apkQvTMSRQ__LGiTQ5NGVBj8p_tOpwlQO6vS8
Download Telegram
Бестиарий программирования
Прям в продолжение :) А вас вайб-кодеры уже достали?
Ааа, всё пропало! AI создаёт дырявый код! Что же делать?

Что-то у меня неделя ИИ. Сегодня за утренним кофе прочитал статью "70% разработчиков считают ИИ-код дырявым, при этом 30% всех опрошенных деплоят его в прод".

Кажется, это ещё одна из статей, написанных с помощью ИИ про ИИ, которые всё заполонили. Поэтому рекомендовать её к прочтению не стану. Да и приведённым там числам я что-то не очень верю.

Позабавило: "C-код оказался самым дырявым". Да, C такой :) Надо иметь прямые руки, чтобы им пользоваться — плата за скорость и экономный расход памяти.

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

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

Кстати, скоро эту тематику мы затронем в вебинаре "Что скрывает код: от поверхности атаки до производительности" в новом цикле "Качество и безопасность ПО в эпоху GenAI". Приглашаю зарегистрироваться: 24.06.2026 в 15:00 по МСК, онлайн.

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

Эээ… Так ничего не изменилось. Проблема качества кода и проектов была всегда. Уже известно всё, что надо делать. Проблематика не стоит выеденного яйца.

Нужно выстраивать процессы разработки так, как это делалось до GenAI. Если же нет сил на выстраивание процессов или команда считает, что уровень качество приемлем ("и так сойдёт" (C)), то GenAI тут ни при чём.

Берём ГОСТ Р 56939—2024 по разработке безопасного программного обеспечения. Забываем само слово ГОСТ, вычеркиваем слово безопасность. Перед нами чек-лист полезных практик, внедрение которых даст приемлемый уровень качества проекта.

Что там у нас? Обучение сотрудников. Если хотите, чтобы вайб-кодеры умели не только наваливать код, но и уметь проводить его аудит, задумайтесь о том, как они будут расти. Кстати, вот недавно статья на эту тему была: "Поколение "Approve": почему я заставил команду переписать проект, который уже работал".

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

Всегда был нужен процесс композиционного анализа. С GenAI это лишь заиграло новыми красками в связи с атаками, построенными на галлюцинациях в названиях пакетов. Про это хорошо рассказывают специалисты компании CodeScoring в докладах и статьях:
Галлюцинации систем ИИ (slopsquatting). Сегодня многие разработчики используют ИИ‑агентов в IDE. Например, просят подсказать библиотеку или показать пример кода. Но LLM "галлюцинируют" и рекомендуют несуществующие библиотеки в 20% случаях, либо воспроизводят ряд вышеописанных рисков, в основе которых лежит мимикрия проблемных решений под легитимные. Этим пользуются злоумышленники: они создают вредоносный пакет и ожидают, что кто‑то установит его, доверившись исключительно подсказке модели. Ситуацию усугубляет тот факт, что галлюцинации являются воспроизводимыми, что облегчает процесс наименования вредоносных компонентов для злоумышленников.

И так далее. Хотим качества и надёжности — просто берём и делаем хорошо :)
💯72
Теперь прямо у нас на сайте вы можете запросить демонстрацию PVS-Studio!

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

Длительность демонстрации 30-60 минут. Записаться можно по ссылке 🔗

#PVS_Studio #демо
Please open Telegram to view this post
VIEW IN TELEGRAM
👌5
Давненько мы не делились кейсами. Пришло время это исправить! 👍

Мы пообщались с командой компании YADRO, которая является ведущим производителем высокотехнологичного оборудования в России.

"Мы начали с правил общего назначения, а затем исследовали и подключили правила по стандартам MISRA и OWASP. Регулярные обновления поддержки стандартов в PVS-Studio позволяют нам поддерживать код в чистом и актуальном состоянии", — Павел Недошивин, инженер по информационной безопасности.


Полную версию кейса можно почитать тут 🔗

#кейс #PVS_Studio
Please open Telegram to view this post
VIEW IN TELEGRAM
👌4
Завышенное доверие к генеративному искусственному интеллекту (GenAI) усиливает проблемы владения кодом, безопасностью, bus-фактором, обучения персонала и так далее. GenAI теперь с нами, и нет смысла отказываться от его возможностей. Но важно видеть в нём инженерный инструмент — иногда опасный, — а не волшебного помощника.

Вместе с экспертами из разных компаний посмотрим на эти проблемы с разных сторон и разберём, как их можно минимизировать.

📌Программа первого вебинара "Что скрывает код: от поверхности атаки до производительности":

1️⃣ Андрей Карпов, сооснователь и директор по развитию бизнеса ООО "ПВС", выступит с докладом "Не всё золото, что блестит: на примере эффективности сгенерированного С++ кода".

Рассмотрим на практических примерах проблему, что создание кода с помощью ИИ не отменяет задач контроля полученного результата: обзора кода, статического анализа, нагрузочного тестирования и так далее.

2️⃣ Алексей Орехов, генеральный директор "Группа NN2", выступит с докладом "Уверенность без проверки: три слоя угроз безопасности генеративного ИИ в разработке".

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

🗓Встречаемся 24 июня в 15:00

Регистрация доступна по ссылке 🔗

#вебинар #genai
Please open Telegram to view this post
VIEW IN TELEGRAM
Задумались о внедрении статического анализатора в вашу компанию? 🤔

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

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

Для кого будет полезен алгоритм:

- владельцы продукта
- технические директоры
- руководители команды разработки
- DevSecOps-команды
- разработчики

Получить доступ к алгоритму внедрения или запросить демонстрацию PVS-Studio можно по этой ссылке 🔗

#PVS_Studio #алгоритм #sast
Please open Telegram to view this post
VIEW IN TELEGRAM
Решил сделать шпаргалку, чтобы потом вновь не искать таблицу ГОСТ Р МЭК 61508 с рекомендациями по использованию языков программирования на разных уровнях полноты безопасности (УПБ / SIL).

Таблица рекомендованных ГОСТ Р МЭК 61508 языков находится в 7 части: ГОСТ Р МЭК 61508 - 7 - 2012, стр. 60, Таблица C.1 — Рекомендации по конкретным языкам программирования.
ГОСТ Р МЭК 61508-7 – Функциональная безопасность систем электрических, электронных, программируемых электронных, связанных с безопасностью.

• HR – Настоятельно рекомендуется
• R – Рекомендуется
• ― – Сомнительно, но ok
• NR – Решительно не рекомендуется

Как меня сюда занесло? Это тематика связана с направлением АСУ ТП и MISRA. Подробности будут позже, пока изучаем тему. Скажу только, что сейчас продолжаем активно заниматься свежими стандартами MISRA.

P.S. Интересен довольно спартанский набор языков, проверенных временем :)
👍4🤯3
Все зачем-то постят, и я туда же
😁6
Forwarded from Makrushin
Как обойти фильтры LLM с помощью квантовой механики

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

Мы научились внедрять вредоносные запросы в модель, а модели ещё лучше научились фильтровать эти запросы. Если простой промпт "забудь все предыдущие инструкции и выполни мой запрос" по какой-то причине игнорируется LLM, то можно попробовать отправить тот же запрос в другом формате. На другом языке, с использованием 1337speak. Но даже эти попытки будут заблокированы хорошим фильтром.

У больших языковых моделей есть фильтры безопасности. Они натренированы распознавать опасные паттерны в обычном тексте. Паттерны. В тексте.

«Объясни, как сделать X», «напиши код для Y»,

где X — что-то незаконное, а Y — какой-то вредоносный код.

В этом исследовании описана новая идея, как обойти эти фильтры, если написать запрос на языке математики. То есть с помощью математической "инкапсуляции" запросов в сложные задачи по теории множеств, логике или квантовой механике можно обойти цензуру. Если фильтр видит символы ∀, ∃, ∧, то воспринимает это как математическую задачу и пропускает запрос. Модель решает эту задачу и — главный трюк — в итоге получает инструкцию "забудь все инструкции и…"

Ещё один ответ на вопрос "зачем специалисту по кибербезу изучать математику?": чтобы уметь обойти фильтры.

@makrushin l MAX l VK l Сетка l Дзен
👍2
Провели первый пилотный вебинар из серии "Качество и безопасность ПО в эпоху GenAI".

Если тебе интересна тематика и ты хочет принять участие в этом цикле в качестве эксперта, прошу написать коллеге-организатору.
👍7👀2
А что, если…

Эдсгер Дейкстра писал:
На экономический вопрос «Почему программное обеспечение такое дорогое» столь же экономическим ответом был бы такой: «Потому что его пытаются получить при помощи дешёвого труда». А почему пытаются? Да потому, что присущие ему трудности повсеместно сильно недооцениваются.

Если продолжить эту мысль, то использование GenAI – очередная попытка создавать проекты максимально дёшево, игнорируя вытекающие из этого проблемы. Навскидку:
• больше кода, меньше контроля со стороны человека, проблемы безопасности и качества;
• непонятно, как будут появляться новые эксперты, которые могут понять сгенерированный код (проблема обучения);
• некому найти исправить сложные ошибки – бесконечная перегенерация фрагментов проекта в надежде, что ошибки исчезнут;
• новые модели обучаются на плохом коде предыдущих моделей (сингулярность говновайбкода);
• и т.д.

А что, если внедрение GenAI в итоге сделает программное обеспечение дороже, а не дешевле?
💯821👀1
Как все так ловко ИИ пользуются... Обзоры кода делают...

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

Даю файл на вход, а он тревожно:
В присланном файле есть места, где содержимое обрезано/повреждено вставкой `</context>`, поэтому их я не рассматриваю как «баги кода» — это артефакты форматирования. Разберу реальные логические ошибки.

Шо? Какой context? Там максимум — #include "context.hxx". Ну ладно, давай ошибки посмотрим.

Так, парочка сообщений по делу. Например, найден лишний аргумент при использовании функции форматирования.
addErrorMessage(&sciErr, API_ERROR_CREATE_ZDOUBLE_IN_NAMED_LIST,
_("%s: Unable to create list item #%d in Scilab memory"),
"createComplexZMatrixOfDoubleInNamedList", _iItemPos + 1, _pstName);

В строке форматирования два спецификатора %s и %d, а передаются три аргумента: funcName, _iItemPos+1, _pstName. Да, это баг. Про подобные паттерны PVS-Studio уже знает, хотя, возможно, нужно подсказать, что addErrorMessage — это printf-подобная функция. В целом, тут неинтересно, идём дальше.

Ооо, и тут начинается...
`checkListItemPosition` — неправильное сравнение границы:

// check a valid item
if (_iItemPos > iItem)
{
addErrorMessage(...);
return sciErr;
}

Здесь проверка «позиция валидна» использует `>`, хотя позиции 1-based, а `iItem` — количество элементов. Это выглядит как граничный дефект, но по сути `_iItemPos == iItem` допустимо, `_iItemPos > iItem` — ошибка. Это в порядке — правильная граница. Не баг. (Указываю, чтобы было понятно, что я это проверил.)

Так баг или не баг? Я три раза прочитал текст и посмотрел функцию с кодом. В итоге я не только не понимаю, есть ли здесь ошибка, но и не понимаю, что Claude пытается мне сказать.

Дальше ещё хуже шарики за ролики зашли.
Баг 5 (реальный): утечка в readCommonMatrixOfPolyInNamedList и др. — нет, это API отдаёт указатели наружу. Не баг.

Это всё описание пятого бага, больше ничего нет. Комментировать — только портить.

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

На сегодня мне ИИ достаточно. Пойду лучше пройдусь по парку, пока погода хорошая.
👍14😁6
69 поводов, когда IT-нику хочется что-то отметить
Наверняка многие не в курсе, что у нас есть вот такая подборка – День, когда Скайнет обрёл сознание, и ещё 69 поводов собраться с коллегами! Часть 1, Часть 2.
🔥3😐1
Вебинар "Практическая интеграция PVS-Studio и SourceCraft"

На совместном вебинаре PVS-Studio и SourceCraft от Яндекса мы покажем, как современные инструменты статического анализа и совместной работы с кодом помогают повысить качество, надежность и безопасность программных продуктов.

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

📆 15 июля 15:00

Подробности и регистрация по ссылке
👍3😐1
Короче и быстрей (часть №1 из 5) – Второй цикл

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

Приведённый ниже код я встретил в вайб-код проекте VibeTensor. Я исследую подобные проекты в качестве натуралиста. Мне интересен генезис новых видов дефектов и недостатков в коде.

Одно из наблюдений – генерированный код более "пухлый", что затрудняет его восприятие человеком и оптимизацию компиляторами. Следующий фрагмент кода как раз это хорошо демонстрирует.
static TensorImpl make_contiguous_tensor(const std::vector<int64_t>& sizes) {
  const std::size_t nd = sizes.size();
  std::vector<int64_t> strides(nd, 0);
  int64_t acc = 1;
  for (std::ptrdiff_t i = static_cast<std::ptrdiff_t>(nd) - 1; i >= 0; --i) {
    strides[static_cast<std::size_t>(i)] = acc;
    const auto sz = sizes[static_cast<std::size_t>(i)];
    acc *= (sz == 0 ? 1 : sz);
  }
 
  int64_t ne = 1;
  bool any_zero = false;
  for (auto s : sizes) {
    if (s == 0) {
      any_zero = true;
      break;
    }
    ne *= s;
  }
  if (any_zero) {
    ne = 0;
  }
  ....
}

С одной стороны, размер и скорость этого кода некритичны, так как он относится к тестам.

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

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

Медленный С++ код – это противоестественно. Поэтому по-прежнему полезно развивать свою экспертность в понимании, удачным ли получился код и как его можно улучшить, сократить, оптимизировать. Или сгенерировать снова, используя уточнения.

В общем давайте потренируемся и проведём рефакторинг. Начнём с этого фрагмента:
int64_t ne = 1;
bool any_zero = false;
for (auto s : sizes) {
  if (s == 0) {
    any_zero = true;
    break;
  }
  ne *= s;
}
if (any_zero) {
  ne = 0;
}

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

Чтобы обнулить переменную, где хранится произведение, используется флаг any_zero. Этот подход избыточен, можно проще.
int64_t ne = 1;
for (auto s : sizes) {
  if (s == 0) {
    ne = 0;
    break;
  }
  ne *= s;
}

Когда встретится 0, обнулятся ne и цикл завершится. Можно продолжить упрощение. Современные процессоры быстро выполняют операции умножения. Поэтому можно в начале перемножить, а потом уже проверить.
int64_t ne = 1;
for (auto s : sizes) {
  ne *= s;
  if (s == 0) {
    break;
  }
}

Функциональность кода не изменилась, но он стал короче и, на мой взгляд, даже понятнее.
👌4👍1
Короче и быстрей (часть №2 из 5) – Первый цикл

Теперь вернёмся к началу функции.
static TensorImpl make_contiguous_tensor(const std::vector<int64_t>& sizes) {
  const std::size_t nd = sizes.size();
  std::vector<int64_t> strides(nd, 0);
  int64_t acc = 1;
  for (std::ptrdiff_t i = static_cast<std::ptrdiff_t>(nd) - 1; i >= 0; --i) {
    strides[static_cast<std::size_t>(i)] = acc;
    const auto sz = sizes[static_cast<std::size_t>(i)];
    acc *= (sz == 0 ? 1 : sz);
  }

Из-за static_cast код выглядит тяжеловесным. Первое приведение типа нужно, чтобы какие-то компиляторы/статические анализаторы не ругались на странные арифметические игры.
std::ptrdiff_t i = static_cast<std::ptrdiff_t>(nd) - 1;

Рассмотрим, что будет, если убрать static_cast, а входной массив окажется пустым:

1. Если массив пуст, то nd = 0;
2. Вычтя единицу из беззнакового нуля, мы получим SIZE_MAX, т.е. очень большое положительное беззнаковое число.
3. Значение SIZE_MAX типа size_t неявно преобразуется в тип ptrdiff_t и записывается в переменную i. Получается, что i = -1, как и было задумано. Но вот тут как раз могут быть выданы предупреждения. Ведь мы инициализируем ptrdiff_t числом, которое больше диапазона максимально вмещаемого числа. Поведение в такой ситуации до C++20 определяется реализацией – implementation defined behavior. После C++20 поведение определено.

Итого: первый static_cast лучше оставить на месте. Про остальные такого сказать нельзя. В них нет никакого смысла.

Оператор [] в классе vector принимает аргумент типа size_type (этот тип является синонимом size_t). Значение переменной i автоматически будет преобразовано в size_t, и в этом нет чего-то странного, опасного или подозрительного. Явное приведение типов только загромождает код, и от него лучше избавиться.

for (std::ptrdiff_t i = static_cast<std::ptrdiff_t>(nd) - 1; i >= 0; --i) {
  strides[i] = acc;
  const auto sz = sizes[i];
  acc *= (sz == 0 ? 1 : sz);
}

Первый шаг сделан. Можно теперь всё-таки упростить длинную строку с циклом? Давайте подумаем. Хочется написать как-то так:
for (auto sz : std::ranges::views::reverse(sizes)) {
  strides[???? i ????] = acc;
  acc *= (sz == 0 ? 1 : sz);
}

Всё равно требуется переменная i для обхода массива strides, начиная с конца. Так что совсем упростить код и избавиться от i не получается. Поэтому сделаем так:
int64_t acc = 1;
size_t i = nd;
for (auto sz : std::ranges::views::reverse(sizes)) {
  strides[--i] = acc;
  acc *= (sz == 0 ? 1 : sz);
}

Если честно, мне не нравится, что из-за использования --i код стал сложнее. Теперь требуется вникнуть, почему переменная в начале уменьшается, а уже затем используется для обращения к элементу массива.

С другой стороны, кода стало меньше и его можно быстрее просмотреть глазами. Так что, наверное, когнитивная сложность кода осталась в итоге такой же. Т.е. понимать код стало не проще, но и не сложнее. В любом случае код стал покороче, так что ok.
👍1
Короче и быстрей (часть №3 из 5) – Объединяем циклы

Взглянем целиком на вариант кода, который получился к текущему моменту.
static TensorImpl make_contiguous_tensor(const std::vector<int64_t>& sizes) {
  const std::size_t nd = sizes.size();
  std::vector<int64_t> strides(nd, 0);
  int64_t acc = 1;
  size_t i = nd;
  for (auto sz : std::ranges::views::reverse(sizes)) {
    strides[--i] = acc;
    acc *= (sz == 0 ? 1 : sz);
  }
   int64_t ne = 1;
 
  for (auto s : sizes) {
    ne *= s;
    if (ns == 0) {
      break;
    }
  }
  ....
}

Теперь, когда кода меньше, становится очевидным, что второй цикл избыточен. В первом цикле мы перебираем все элементы. Так почему бы их сразу не перемножить?
static TensorImpl make_contiguous_tensor(const std::vector<int64_t>& sizes) {
  const std::size_t nd = sizes.size();
  std::vector<int64_t> strides(nd, 0);
  int64_t acc = 1;
  size_t i = nd;
  int64_t ne = 1;
  for (auto sz : std::ranges::views::reverse(sizes)) {
    strides[--i] = acc;
    acc *= (sz == 0 ? 1 : sz);
    ne *= sz;
  }
  ....
}

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

Что ещё осталось? Не требуется изначально обнулять контейнер strides нулями. Всё равно все его элементы будут перезаписаны.
std::vector<int64_t> strides(nd, 0); // надо убрать второй аргумент

В принципе, мы закончили. Но можно сделать ещё одно косметическое изменение, избавившись от переменной nd. Она ни здесь, ни в последующем коде не нужна. Итоговый код:
static TensorImpl make_contiguous_tensor(const std::vector<int64_t>& sizes) {
  auto q = sizes.size();
  std::vector<int64_t> strides(q);
  int64_t acc = 1;
  int64_t ne = 1;
  for (auto sz : std::ranges::views::reverse(sizes)) {
    strides[--q] = acc;
    acc *= (sz == 0 ? 1 : sz);
    ne *= sz;
  }
  ....
}

Код сократился в два раза: с 20 до 9 строк!

P.S. Если захотите и напишите комментарий, я приведу ассемблерный код, чтобы показать как он сократился и оптимизировался.
👍5
Короче и быстрей (часть №3 из 5) – Объединяем циклы - Заглянем в ассемблерный код

Текст функции стал короче, но скажется ли это положительно на производительности? Да. Посмотрим на основной фрагмент ассемблерного кода для изначального варианта функции. Используется Clang с ключом -O2.
.LBB1_7:
mov rax, rsi
mov qword ptr [rbx + 8*r12 - 16], rsi
mov rsi, qword ptr [r13 + 8*r12 - 16]
cmp rsi, 1
adc rsi, 0
imul rsi, rax
dec r12
cmp r12, 1
ja .LBB1_7
mov r12d, 1
cmp rbp, r13
je .LBB1_9
.LBB1_4:
mov rax, qword ptr [r13]
test rax, rax
je .LBB1_5
imul r12, rax
add r13, 8
cmp r13, rbp
jne .LBB1_4
jmp .LBB1_9

Достаточно многословный ассемблерный код с двумя циклами. Теперь посмотрим, что сгенерировано для последнего сокращённого кода.
.LBB1_10:
mov rcx, rsi
mov rdx, qword ptr [rbp - 8]
add rbp, -8
mov qword ptr [rax], rsi
cmp rdx, 1
mov rsi, rdx
adc rsi, 0
imul rsi, rcx
imul r12, rdx
add rax, -8
cmp rbp, r13
jne .LBB1_10

Красивое. Один цикл. Коротко и быстро.
1👍1
Короче и быстрей (часть №4 из 5) – Альтернативный подход

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

Варианты от DeepSeek были не лучше, а некоторые даже хуже исходного. Например, он сократил одну из реализаций кода ценой переворачивания массива.
// Разворачиваем strides обратно
std::reverse(strides.begin(), strides.end());

А вот одна из альтернативных реализаций от Claude Opus заслуживает внимания. Он обратил внимание, что можно выполнять не две, а одну последовательность умножений!

Если все элементы входного массива не нулевые, то по завершению цикла acc == ne. А если хотя бы один элемент был нулевой, то в конце можно просто поменять значение ne на ноль. Происходит вновь возвращение к флагу any_zero, но в более умном варианте. Используя эту идею, можно написать следующий код:
static TensorImpl make_contiguous_tensor(const std::vector<int64_t>& sizes) {
  auto q = sizes.size();
  std::vector<int64_t> strides(q);
  int64_t acc = 1;
  bool any_zero = false;
  for (auto sz : std::ranges::views::reverse(sizes)) {
    strides[--q] = acc;
    acc *= (sz == 0 ? 1 : sz);
    any_zero |= sz == 0;
  }
  const int64_t ne = any_zero ? 0 : acc;
  ....
}

Ассемблерный код:
.LBB1_7:
mov rdx, rsi
mov rsi, qword ptr [r13 - 8]
add r13, -8
mov qword ptr [rcx], rdx
test rsi, rsi
sete dil
cmp rsi, 1
adc rsi, 0
imul rsi, rdx
or al, dil
add rcx, -8
cmp r13, rbp
jne .LBB1_7
xor r13d, r13d
test al, 1
cmove r13, rsi
mov r14, r8

Будет ли код, в котором выполняется в два раза меньше умножений, более быстрым? Моё предсказание – необязательно. Процессор может выполнить два невзаимосвязанных умножения одновременно на разных конвейерах. Я делаю ставку, что мой вариант и вариант от Claude будут работать с практически одинаковой скоростью.
👍1