Сохранёнки программиста
6.55K subscribers
1.17K photos
59 videos
10 files
1.86K links
Заметки и ссылки на будущее, чтобы изучить когда будет время.

Разместить рекламу: @tproger_sales_bot

Правила общения: https://tprg.ru/rules

Другие каналы: @tproger_channels

Другие наши проекты: https://tprg.ru/med
Download Telegram
Переписать с нуля — один из семи вариантов, и обычно худший

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

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

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

В нашем канале Tproger разобрали подробнее.

@prog_stuff
Samsung встроила вычислительные блоки прямо в LPDDR5X

Samsung продолжила линию Processing-in-Memory: в LPDDR5X-9600 у каждого из 16 банков появился PIM-блок с MAC-деревом, регистрами инструкций и векторов активаций. Память выполняет часть вычислений там, где лежат данные, и работает через стандартный протокол LPDDR5X. Особые адреса строк включают режимы по принципу MMIO-регистров.

Устройство напоминает жёстко ограниченный SIMD: одна операция и один операнд распространяются на все 16 банков. Их суммарная внутренняя пропускная способность достигает 614 ГБ/с, тогда как обычное обращение к памяти через внешнюю шину упирается в 76,8 ГБ/с.

Каждый PIM-блок держит четыре операции MAC в INT8 или FP8 за такт данных, то есть восемь за такт без учёта удвоенной передачи. На четырёхбитных весах пропускная способность удваивается, и пакет выдаёт 2,4 TOPS. Восемь чипов дают 9,6 INT8 TOPS, как NPU Intel Meteor Lake, и требуют 128 ГБ системной памяти. Архитектуру разобрали по материалам Hot Chips 2026.

@prog_stuff
🤯1
OpenOffice не печатал по вторникам, и виноват был Erlang

В багтрекере Ubuntu годами висела жалоба: печать из OpenOffice иногда не работает. Пользователи спорили, чей это баг, потому что из других программ печаталось нормально. Один снёс и переставил пакет, отчитался в четверг, что помогло — и через две недели, во вторник, написал, что не помогло.

Через четыре месяца жена одного из разработчиков Ubuntu сформулировала точно: OpenOffice не печатает по вторникам. Показать мужу она этого не могла, потому что была среда.

Разгадка оказалась в утилите file, которая определяет тип файла по сигнатурам в начале. OpenOffice записывает в PostScript дату создания, и по вторникам она принимает вид %%CreationDate: (Tue MMM D hh:mm:...).

В базе сигнатур лежало правило для Erlang JAM-файла:

4 string Tue Jan 22 14:32:44 MET 1991 Erlang JAM file - version 4.2

Пробелы в образце не экранированы, поэтому сравнивались только первые три символа. Правильная запись — 4 string Tue\ Jan\ 22\ 14:32:44\ MET\ 1991. По вторникам PostScript опознавался как Erlang JAM и до принтера не доезжал.

Правил в file больше 1600, и порядок проверки решает: ложное совпадение с Erlang случалось раньше, чем проверка на PostScript. История целиком — в собрании курьёзных багов.

@prog_stuff
😁1
Одна JSON-колонка в SQLite вместо спроектированной заранее схемы

David Leadbeater показал приём, который сохраняет смысл спустя годы: документ целиком лежит в одной колонке SQLite, а востребованные поля постепенно становятся частью схемы. Заранее перечислять всю структуру JSON не требуется.

Поле извлекается через json_extract внутри generated column. Виртуальную колонку можно индексировать обычным CREATE INDEX, после чего запрос использует индекс как для привычного столбца. Когда появляется новый способ поиска, схема расширяется через ALTER TABLE; в статье план запроса проверяется командой EXPLAIN QUERY PLAN.

Generated columns есть в SQLite начиная с версии 3.31. Leadbeater разбирает варианты VIRTUAL и STORED, ограничение NOT NULL и индексы.

Границы у приёма явные. Колонку STORED таким способом через ALTER TABLE добавить нельзя, а некорректный JSON ломает вставку. Получается постепенная схема: исходный документ остаётся целым, а наружу выходят только поля, которым действительно понадобились ограничения или быстрый поиск.

@prog_stuff
Писать код и строить систему — разные работы

Эссе, которое разбирают на Hacker News, начинается с наблюдения: перевести идею в инструкции для машины и решить, какие инструкции вообще должны существовать, — два разных занятия. Пример автора: «обрабатывать входящие события и обновлять данные». Синхронно или через очередь? Ровно один раз или хотя бы один? Что делать, если потребитель лежал три часа? Ни один из вопросов не про синтаксис.

Главный тезис: каждая оптимизация тратит сложность где-то ещё. Кеш даёт задержку и забирает инвалидацией, микросервисы дают независимые деплои и приносят распределённые отказы. Работа инженера — решать, где этой сложности место.

В нашем канале «Tproger» разобрали подробнее.

@prog_stuff
Почтовый CSV Японии рвёт строки посередине слова — и парсить его приходится всей стране

Пол О'Лири МакКанн, автор питоновской библиотеки posuto, разобрал ken_all.csv — файл, в котором Почта Японии раздаёт базу всех почтовых индексов. Формат легендарный: о нём регулярно ругаются в соцсетях, а один твит описывает грешников, которых в аду заставляют парсить этот файл вечно.

Внутри: если название района длиннее 38 символов, строка разрезается на несколько — в случайном месте, не по границе слова, — а остальные поля дублируются в каждой. В поле названия встречаются скобочные примечания «для читателя», ссылающиеся на порядок строк, — в формате, где строки должны быть самодостаточны. Поля не квотируются. На один индекс может приходиться до 66 строк.

Причину разрезания никто не объяснил; автор предполагает буфер фиксированной длины в системе тридцатилетней давности. Урок переживёт сам файл: данные, которые готовили для человеческого глаза, мстят машинному парсеру.

Статья — Parsing the Infamous Japanese Postal CSV

@prog_stuff
Как разобрать чужой бинарный формат: полный проход от файла сохранения до готовой схемы

Вам достался файл без спецификации: двоичный файл настроек прибора, сохранение игры, кеш программы. Автор ImHex — бесплатного открытого hex-редактора — годами отвечал на этот вопрос одной фразой «посмотри декомпилированный код того, кто его пишет», и наконец разобрал процесс целиком на файле сохранения игры FEZ.

Первый взгляд в hex-редактор уже даёт три вывода: в файле видны читаемые строки вроде DOT_LOCKED_DOOR_A, значит он не сжат и не зашифрован; в начале нет сигнатуры, значит по ней формат не опознать. Дальше автор идёт к тому, кто файл пишет. FEZ написана на C#, её сборки декомпилируются почти в исходник; в EasyStorage находится класс PCSaveDevice, а в нём строка "SaveSlot" + index — то самое имя файла. Рядом метод Save, который заводит буфер на 40 960 байт и заполняет его через BinaryWriter. Порядок вызовов записи и есть структура файла.

Остаток разбора — перенос этого порядка в Pattern Language, встроенный язык описания форматов ImHex: struct с полями, размещение по адресу через оператор @, отдельные типы под строки, списки и перечисления. Схема одновременно и парсит файл, и документирует находки, и проверяет их: ошиблись в поле — данные перестанут сходиться.

Общий рецепт автор в конце сводит к четырём шагам: проверить, не известен ли формат (magic-детект ImHex, binwalk); найти код, который файл читает или пишет; опознать в нём привычные кирпичи — числа, строки, булевы значения, структуры; записать всё это схемой. Для .NET декомпилятор — Rider, для нативного кода — Ghidra, IDA или Binary Ninja, для JVM — Recaf.

@prog_stuff
👍2
Ваш бинарник в Alpine может быть на 26% медленнее из-за libc, а не из-за кода

musl — лёгкая libc, на которой собраны образы Alpine и самодостаточные статические бинарники «один файл — и работает». Джонатан Эллис, сооснователь DataStax и многолетний глава проекта Apache Cassandra, замерил цену этого удобства на своей рабочей нагрузке.

Со штатным аллокатором musl проигрывает разгромно, причём на обычных четырёхъядерных машинах, а не только под высокой конкурентностью. Замена на mimalloc убрала большую часть разрыва, но 26% остались: профилирование указало на базовые процедуры работы с памятью внутри самой libc.

В нашем канале «Tproger» разобрали подробнее, включая то, что автор оставил для musl.
METR разобрала, как у неё украли API-ключ: приложение, написанное с ИИ, при сбое проверки прав пускало всех

Лаборатория, которая тестирует модели для крупных разработчиков, опубликовала отчёт о двух инцидентах. В марте исследователь поднял на личном EC2-инстансе маленькое веб-приложение за входом через Google. В коде была ошибка fail-open: если проверка авторизации не срабатывала, приложение считало пользователя допущенным. Инстанс несколько дней смотрел в интернет, атакующий вытащил ключ к моделям, добавил свой SSH-ключ и три недели гонял чужие запросы.

Заметить было трудно: за кредиты METR не платила деньгами, так что счёт не рос, а внутренняя панель METR не показывала отклонённые по лимитам запросы. В мае пришла вторая волна: сканирование, перебор паролей, попытки через OAuth-токены и ошибка в публичном просмотрщике транскриптов с read-only SQL.

Выводы для любой команды: ключи только в секрет-хранилище, приложения с доступом к ключам делать fail-closed, на каждый ключ лимит и алерт по числу запросов, а не только по деньгам. Полный разбор цепочки на сайте.

@prog_stuff
Один параметр readahead сократил обращения к диску в 12 раз, а io_uring тут ни при чём

Фернандо Симойнс разбирал, почему его встраиваемая СУБД на io_uring не выигрывала у обычного чтения на сканах. Тест: запрос Q6 из TPC-H по таблице в 1,2 GiB, файл открыт с O_DIRECT, запросы на чтение уходят в очередь io_uring.

Без упреждающего чтения база отправила 195 тысяч запросов по 4 KiB, и почти столько же дошло до устройства: ядру нечего было склеивать. С окном readahead в 32 страницы запросов в очереди стало даже больше, 218 тысяч, но до диска добралось около 16 тысяч. Ядро объединило 93% соседних чтений, средний запрос вырос с 4,37 до 56,53 KiB.

Цена тоже есть: поток sqpoll, который опрашивает очередь вместо системных вызовов, съел 65% циклов процессора. Вывод автора в том, что io_uring не ускоряет сам по себе, а только даёт очередь, и без прогрева этой очереди последовательными чтениями выигрыша нет.

@prog_stuff
Посмотрите, как выглядят 128 килобайт, если каждый бит — отдельное ферритовое колечко

Spacelab — европейская лаборатория, которую возили в грузовом отсеке шаттла. Компьютеры NASA туда не поставили: программа была европейской, и внутри работали три одинаковых французских миникомпьютера Mitra 125 MS. Один вёл саму лабораторию, второй эксперименты, третий стоял резервом.

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

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

@prog_stuff
👍21
JetBrains не пропатчила собственный TeamCity, и через него вынесли бэкап сервиса Cadence, AWS-учётки и файлы из S3

Компания обновила отчёт об инциденте. Cadence, облачный сервис для запуска вычислений из PyCharm, использовал TeamCity для оркестрации задач. В нём была CVE-2026-63077 с выполнением команд без аутентификации, и с 8 по 24 августа атакующие сидели на api.cadence.jetbrains.com. Обнаружили 23-го, отключили 24-го. Формулировка JetBrains: «сервер должен был быть пропатчен, но не был».

Что утекло: логины, имена, почта и IP пользователей; полный бэкап сервера за 2024 год; AWS IAM-пользователи с учётными данными; файлы в S3-бакетах JetBrains. Мог быть доступен исходный код, который пользователи синхронизировали из PyCharm. Затронуты ли бакеты клиентов, компания пока не установила.

Рекомендация JetBrains тем, кто подключал Cadence: считать скомпрометированным всё, к чему у сервиса был доступ, от ключей облаков до SSH и ключей подписи. Хронологию и список секретов на замену разобрали на сайте.

@prog_stuff
🙈2
Спрячьте задержку автодополнения между нажатием и отпусканием клавиши

Рюртьян Пул развивает Wirewiki — сервис для просмотра DNS-записей доменов, их истории, делегирования и настроек почты. Главная навигация там ищет среди 240 миллионов доменных имён, поэтому автор добился появления подсказок уже в следующем кадре.

Запрос уходит на keyDown, сразу после нажатия клавиши. Сервер получает набранный префикс и подготавливает варианты для всех допустимых следующих символов, а интерфейс показывает результат на keyUp. В бюджет попадают длительность нажатия, пауза до следующей клавиши и длительность следующего нажатия.

Автор набрал 100 доменов в обычном темпе и получил p99 в 121 мс. Задержку он считает от keyUp до готовности результата: p99 в 0 мс означает, что в 99% случаев данные приходят раньше отпускания клавиши. Для сравнения, экран с частотой 60 Гц обновляется каждые 16,7 мс.

Перерасход трафика ограничен алфавитом домена: 38 символов дают максимум (38 + 1) × 8 = 312 имён в ответе. На практике запрос приносит до 5 КБ, после сжатия — около 2,5 КБ. Приём стоит проверить там, где сеть быстрее пальцев пользователя.
Не всё же работать, иногда можно и поиграть.

Разобрали пять способов пополнения баланса Steam. Один зачисляет деньги на кошелёк по логину за одну-две минуты с комиссией около 6%. Другой показывает итоговую сумму ещё до оплаты и подходит для регулярных небольших пополнений. Третий — маркетплейс, где условия зависят от выбранного продавца, но сделки защищены гарантиями площадки. Четвёртый предлагает пополнение и по логину, и через продажу игровых предметов почти без комиссии. Пятый не берёт свою комиссию, но добавляет разницу за счёт курса конвертации.

По каждому — реальная комиссия, лимиты и на что смотреть, чтобы не наткнуться на мошенников. Складывайте в сохранёнки, вдруг пригодится.
Интерпретатор WebAssembly ускорили в 2,2 раза, и автор объяснил каждую оптимизацию

Wasmi нужен там, где JIT запрещён или не помещается: плагины, микроконтроллеры, песочницы, смарт-контракты. Версия 2.0 по замерам проекта исполняет код в 2,2 раза быстрее 1.0 по геометрическому среднему, а пост Робина Фрайлера читается как учебник по устройству быстрых интерпретаторов.

Три источника ускорения: четыре режима dispatch вместо одного switch-loop (auto-dispatch сам выбирает threaded-код, где компилятор позволяет), новое IR с аккумуляторными регистрами вместо стековых смещений и append-only CodeMap, где вызов функции на горячем пути не делает ни одного lookup. Ячейки стека стали 64-битными, поэтому SIMD больше не стоит 8% обычному коду.

Отдельная история про компилятор: Rust 1.92 включил DestinationPropagation, и CoreMark у интерпретатора Stitch упал с 3000 до 2200. Похожую проблему нашли и в Wasmi, после исправления результат вырос с 2800 до 4200. Вывод: после обновления toolchain бенчмарки интерпретатора надо перепрогонять. Сравнение с Wasm3, WAMR и Wasmtime Pulley разобрали на сайте.

@prog_stuff
❤‍🔥2
Утечку горутин теперь ищет сборщик мусора: как устроен новый профиль в Go 1.27

Обычный goroutine-профиль показывает, сколько горутин заблокировано, но не говорит, проснутся ли они. Команда Go объяснила, как профиль goroutineleak решает это через достижимость: горутина живая, если она не заблокирована или если канал либо мьютекс, на котором она ждёт, достижим из другой живой горутины. Всё, что осталось недостижимым после обхода, разбудить некому.

На схеме из блога видно, куда это встроено в маркировку GC. Худший случай, цепочка горутин, каждая из которых держит примитив следующей, даёт O(n²) за цикл, поэтому профиль советуют снимать раз в несколько часов, а не постоянно. Не ловит ожидание сети, файлов и самодельных спинлоков. Пример с 116 зависшими воркерами и что включить у себя, на сайте.

@prog_stuff
Форк SQLite с ветками и merge дошёл до беты, и авторы честно назвали цену записи

DoltLite заменяет в SQLite B-tree на content-addressed Prolly Tree, а всё остальное, от парсера SQL до тестового harness, оставляет родным. Из-за этого база получает Git-подобные операции: branch, merge, diff, rebase, cherry-pick, reset, push и pull на свой remote или DoltHub. 31 августа проект объявил бету и версию 0.50.0.

Цифры совместимости серьёзные: 100% sqllogictest из 5,8 млн запросов и 99,46% из 892 277 TCL-тестов самого SQLite. Оставшиеся 4809 расхождений упираются в архитектуру: нет rowid, chunk вместо page, нет WAL рядом с файлом.

Цена: в памяти чтение медленнее на 10%, запись на 60%; для файловых баз чтение на уровне SQLite, а вот мелкие autocommit-записи в 3,1 раза дольше, 400 мкс против 125. Каждое изменение порождает новые chunk с хешами, и на одиночной вставке это не амортизируется. Где такая база уместна, а где SQLite останется быстрее, разобрали на сайте.

@prog_stuff
Прототип Cloudflare ужал текст в кэше в 2,8 раза, потратив на это несколько процентов CPU

Казалось бы, текст в интернете и так сжат. Но по замерам Cloudflare около 71% текстовых ответов приходят от origin-серверов без Content-Encoding: сжимает уже edge, а на диск кэша объект ложится сырым. При этом HTML, JSON, CSS и JS дают 67,3% запросов и 22,3% байтов, а картинки и видео 21,4% запросов и 63,3% байтов, их пересжимать бессмысленно.

Прототип Cache Transcoding внутри Pingora сжимает подходящие ответы zstd уровня 3 при записи в кэш, между дата-центрами гоняет их сжатыми и распаковывает перед клиентом. Кодирование стоит 4,31 нс на байт, декодирование 1,56 нс, коэффициент 2,834x на двух тестовых объектах, а не на всём кэше. Порог 4 КиБ отсёк мелочь и потерял всего 1% выигрыша.

Главная фраза статьи: стоимость кодирования платится один раз, когда объект попадает в кэш. Приём переносится на nginx с proxy_cache и Varnish без изменений в идее; порядок действий и где считать CPU разобрали на сайте.

@prog_stuff
Один младший бит: как реализация FMA вскрыла одинаковую ошибку в Rust std, std::simd и musl

Shnatsel переносил формально верифицированный алгоритм fused multiply-add в библиотеку fearless_simd, добавил тесты на миллион субнормальных значений и получил расхождение. Дальше оказалось, что f32::mul_add в стандартной библиотеке Rust ошибается ровно так же, как и fmaf() в musl, а контрпример к первой версии патча нашла языковая модель.

Ошибка живёт в программной эмуляции FMA, на железе с аппаратной инструкцией её нет: затронуты старые Intel без AVX2, Hygon и 32-битный ARM. Автор не берётся оценить практический ущерб, но называет сценарий, где это больно: детерминированные симуляции, которые обязаны давать одинаковые биты на разных машинах. Проверка из трёх hex-чисел и состояние патчей на сайте.

@prog_stuff
👍2
Разработчик сделал бэкенд компилятора, чтобы написать игру для Game Boy на чистом Rust

Game Boy не является целью Rust даже на уровне Tier 3: процессор SM83 не поддерживается LLVM. Автор под ником zlfn начинал с SDCC и библиотеки GBDK, а закончил собственным форком LLVM с бэкендом Z80/SM83 и форком rustc с целью sm83. Теперь cargo-gb build собирает crate в ROM, а cargo-gb run сразу запускает эмулятор.

Внутри есть Rust-библиотеки для графики, звука, ввода и консоли, упаковка кода в банки по 16 КиБ и собственный линкер. Готовый ROM sprite.gb можно скачать и запустить в любом эмуляторе; исходник примера лежит в examples/sprite.

Автор предупреждает, что проект не тестировался вне его машины и это ранняя стадия. Но это редкий пример, когда «Rust везде» доказано не докладом, а бэкендом компилятора под восьмибитную приставку. Репозиторий на GitHub.

@prog_stuff
❤‍🔥3
Стековый байткод против регистрового: замер на одном языке, одном железе и честной методике

Максим Шевалье-Буавер, в прошлом автор YJIT для Ruby, переписала VM своего языка Plush с стековой модели на регистровую и разложила, откуда взялось ускорение. Инструкция стала 64-битным словом с тремя операндами; a + b из трёх диспетчеризаций превратилось в одну. Сама смена модели дала 1,55 раза по геометрическому среднему, слитые сравнения-переходы, непосредственные операнды и свёртка констант добавили ещё 25%.

Методика: 11 чередующихся запусков на тест, медиана, повтор всего набора. Медиана ускорения 2,07 раза, максимум 3,37, тесты GC не сдвинулись. Против Lua 5.5.1 на fib быстрее на 24%. Главный урок автора: в интерпретаторе считайте инструкции, а не такты. Полный разбор на сайте.

@prog_stuff