avva: (Default)
Задолжал объяснение странной функции waste() в очень старой версии компилятора C Денниса Ритчи. Этот компилятор был написан для компьютера PDP-11. Он работает в два прохода, как свойственно многим компиляторам (почему именно? разузнайте, если вам интересно). Эти два прохода - отдельные программы c0 и c1, первая пишет в временный файл, вторая читает из его и пишет в файл ассемблера. Когда это необходимо, первый проход передает второму напрямую синтаксическое дерево (AST, Abstracy Syntax Tree) данного выражения в исходном коде. Это дерево хранится в памяти как массив, содержащий ссылки-указатели внутрь самого себя. c0 "сериализует" это дерево просто записью массива как набора чисел во временный файл, а потом c1 читает его из файла на то же самое место в памяти, в котором оно было в c0, и поэтому все внутренние указатели продолжают работать.

Но как найти место в памяти, которое гарантированно имеет одинаковый адрес в c0 и в c1? Ритчи для этого использует сам код компилятора, в котором функции выставлены в известном порядке и первая загружается по известному фиксированному адресу в том конкретном компьютере и в той версии Юникса. В c0 для этого буфера AST используется место, которое занимает код функции init(), которая к этому моменту уже не нужна. В c1 не оказалось такой удобной функции, и Ритчи написал waste(), чтобы просто зарезервировать нужное число байтов.

Я не знаю подробностей насчет того, как устроена AST, почему первый проход должен передавать ее второму (обычно второму проходу нужно лишь зафиксировать адреса переменных и функций в уже готовом коде, но возможно Ритчи по-другому распределил работу), и как именно обеспечивается загрузка по идентичному адресу. Возможно, я найду время разобраться в этом (интересно!), и посмотреть на это дело в работе. Warren Toomey (он смог запустить этот компилятор в 2008-м) прислал мне ссылку на репозиторию юникса за июнь 72 года (DoctorWkt/unix-jun72 на гитхабе), где лежат скомпилированные c0 и c1, не вполне ясно, какой версии компилятора, но скорее всего в симуляторе PDP-11 apout, который написал Уоррен, можно будет их запустить, скомпилировать компилятор и скомпилировать им себя, и тогда уже будет легче разбираться. Привожу эту информацию на случай, если кто-то захочет попробовать, дайте знать, если получится.
avva: (Default)
Поиск джуниор-программиста в 2025, рассказ от 37 signals. От себя замечу: обратите внимание, что больше 95% кандидатов были отсеяны на уровне HR.

Основатель, Джейсон Фрид: "Мы только что провели отбор на позицию младшего программиста в 37signals. Из 1600 кандидатов мы выбрали двоих. Зарплата составляет $146,000.

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

Подробнее об отборе, разработчик Хорхе Манрубиа:

"Как мы решили проблему фильтрации 1600 кандидатов:

Андреа и Бетани (HR) взяли список из 1600 кандидатов и выбрали 75 лучших. Решение принималось на основе сопроводительных писем и резюме (хотя резюме здесь не играло большой роли). Никакой автоматизации. Мы попросили этих 75 выполнить программистское задание.

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

Мы провели собеседования с этими шестью, чтобы выбрать двух лучших.

Здесь нет никакого "решения". Невозможно сделать этот процесс предсказуемым. Я уверен, что мы упустили отличных людей, и я знаю, что это игра вероятностей, а не гарантий. Любой новый сотрудник в новой компании — это взаимная ставка. Время покажет, сработает ли это для обеих сторон.

Моя единственная уверенность в том, что такой подход к найму требует ОЧЕНЬ много работы от нескольких человек, и мы относимся к этому очень серьёзно."

(квитанции: 1, 2)
avva: (Default)
waste.png

Этот код - часть исходников ранней версии компилятора C, написанного Деннисом Ритчи. Олдскульным программистам на C я предлагаю задачу - разобраться, зачем нужна странная функция waste() и как компилятор ее использует. Этот отрывок из файла c10.c.
avva: (Default)
Много занимаюсь сейчас тренировкой нейронных сетей (не очень больших). Для этого неплохо иметь мощные GPU, и удобно делать это в облаке (хотя в зависимости от бюджетов и нужд и о возможности купить свои не стоит забывать). Я пользовался как стандартными лидерами индустрии в последние месяцы (AWS, Google Cloud), так и специализированными дешевыми сервисами (Lambda Labs, DataCrunch dot io). Я новичок в этой области, постепенно набираюсь опыта.

Общие впечатления:

- дешевые сервисы намного дешевле. Одна виртуальная машина с H100 стоит сейчас примерно В ПЯТЬ РАЗ больше в Google/AWS (11/12 долларов в час), чем в дешевых сервисах (2-2.5 доллара в час).

- везде устроено примерно одинаково, если вы можете затратить время на то, чтобы технически разобраться. Везде резервируешь Ubuntu-based VM, подключаешься к ней по SSH, присоединяешь к ней storage volume. Цена хранения данных, CPU, памяти машины итд. везде ничтожна по сравнению с ценой GPU. Везде нужно самому выбирать, в каком районе держать данные и резервировать машины. Одно важное отличие, на которое стоит обратить внимание: можно ли остановить машину и не платить, или остановленная машина продолжает собирать платеж (в таком случае обычно можно все равно оставить ее root volume, и быстро поднять новую машину с ней).

- основное неудобство дешевых сервисов с моей точки зрения: нет гарантии того, что нужный тип машины с нужным кол-вом нужных GPU будет в наличии, когда вам нужно. Сейчас они есть, а завтра нет. Если закачал кучу данных в данный регион и не можешь запустить тренировку, это сильно мешает. В AWS/Google с такими проблемами (в нужных мне небольших масштабах) не сталкивался.

- второе неудобство это что везде все по-своему, свое устройство storage volumes, свой API для командной строки итд.

- мне пока нравится datacrunch dot io, но не рекомендую его вот совсем уж сильно, я пробовал только два дешевых сервиса. Из нескольких сайтов, сравнивающих цены, что я видел, мне особенно понравился getdeploying dot com (выберите рубрику Cloud GPUs в нем). Полагаю, что самые супер-дешевые варианты скорее всего имеют свои недостатки (availability/reliability), хотя не проверял. Конкуренция очень высокая, и это хорошо

- если хотите что-то оспорить/добавить, всегда рад.
avva: (Default)
Программисты, как вы используете языковые модели в своей работе и как не используете? Какие модели или дополнительные фреймворки предпочитаете? Что работает хорошо, а что плохо, сейчас, в апреле 2025?

Дам несколько ссылок по теме, и потом расскажу про себя.

1. https://www.anthropic.com/engineering/claude-code-best-practices - Антропик рассказывают, как они рекомендуют пользоваться Клодом для написания кода, и что у них работает лучше, а что хуже.

2. https://crawshaw.io/blog/programming-with-llms
3. https://simonwillison.net/2025/Mar/11/using-llms-for-code/

Два полезных поста от программистов о том, как они используют LLMы. Был еще отличный пост 1-2 месяца назад, где автор показывал подробно, как он пытался использовать LLM для небольшой игры на Lisp-like языке, и было хорошо видно, с чем модель справляется хуже, но я потерял эту ссылку. Если найду или кто-то мне кинет, добавлю сюда.

Мои личные мнения и предпочтения:

Последний год я пишу 80% времени на Питоне и 20% на C++. Я не использую autocomplete, и не пользуюсь специализированными средами (Cursor, GitHub Copilot, Claude Code). Мое использование моделей можно выделить в три категории:

- во-первых, и это наверное 70% использования, я задаю вопросы о том, какие есть библиотеки для тех или иных нужд и какая между ними разница; какой API следует использовать для чего-то, и почему; какой алгоритм может подойти к такой-то задаче; как объяснить такой-то баг или такую-то непонятную ошибку. Очень важно стараться задавать модели вопросы так, чтобы ответ можно было легко проверить, или как альтернатива, чтобы в случае неправильного ответа это стало быстро понятно (fail fast).

- реже (20% случаев), я прошу LLM сгенерировать мне кусок кода, делающий что-то конкретное и четко определенное, используя API, который я знаю нетвердо. Например, мне нужно нарисовать график в pyplot или изменить существующий график неким образом, который мне не очевидно, как сделать. Я не помню API pyplot и никогда его подробно не изучал, так что плохо понимаю, чего в нем добиться легко, а чего очень тяжело. Если LLM справляется с задачей, я настаиваю на том, чтобы внимательно прочитать и понять каждую строчку кода перед тем, как внести в репозиторий.

- совсем редко (5% случаев), я прошу LLM написать мне небольшую законченную программу, которая делает что-то четко определенное. Скажем, я хочу нарисовать определенные фигуры в 3D-пространстве, и менять параметры их размеров и расположения с помощью каких-то кнопок в графическом интерфейсе. Или мне нужно использовать библиотеки доступа к файлам нейронных моделей ONNX, чтобы внести определенные изменения в интерфейс уже готовой нейронной сети. В обоих случаях я понимаю, что надо сделать, и нет никаких особых сложностей, но разбираться в незнакомых API и писать весь код, склеивающий их вместе, заняло бы часы, вместо 5-10 минут на то, чтобы сформулировать запрос, получить программу, проверить, что она работает и внимательно прочитать код. Я не пользуюсь моделями таким образом для центральной функциональности моего продукта, только для программ "на периферии": что-то конвертировать, визуализировать, быстро изменить что-то в структурированных файлах итд.
avva: (Default)
quadratic2.png

А вы знаете про альтернативную формулу решений квадратного уравнения?

Вместо "минус бэ плюс-минус корень... поделить на два а", в ней "2це поделить на минус бэ минус-плюс корень...".

Ну, разобраться в том, что она дает ровно те же два решения, не очень тяжело.

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

Но вот зачем это все надо? Те, кто знают, уже усмехаются и кивают, а я расскажу.

Это нужно, если обычная формула заставляет сделать "катастрофическое сокращение". Что такое катастрофическое сокращение, спросите вы? Это когда мы делаем вычитание двух чисел, очень близких друг к другу. Из-за того, как вещественные числа записываются в памяти компьютера (вспоминайте эти странные слова: мантисса... экспонента...),
происходит потеря точности.

Например, если b очень большое в сравнении с a,c, то получится, что дискриминант sqrt(b^2-4ac) очень близок по значению к b. Тогда операция -b+sqrt(D) потеряет почти все значащие биты в представлениях b и sqrt(D). Мы получим какое-то решение, но оно легко может на 10-20% отличаться от истинного. А второе решение, где берется минус, будет в порядке.

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

Все старое опять становится новым. Когда-то в 70-х и 80-х годах прошлого века, такие трюки были обыденным делом для программистов. Потом мы все привыкли к 64-битным вещественным типам и обленились, хотя конечно эксперты в определенных областях знали и это и гораздо более продвинутые техники численного анализа. А сейчас в нейронных сетях один из главных трендов - снижение точности до 32- и 16-битных представлений, чтобы выиграть скорость и память на миллионах и миллиардах параллельных операций внутри GPU. Не то чтобы с 64-битными числами не надо никогда следить за точностью, но в 32- и 16-битных эта проблема опять выходит на передний план.
avva: (Default)
Исключительно подробный и очень интересный (для тех, кто в теме, как минимум) пост одной дотошной индийской исследовательницы о том, как она искала работу в области языковых моделей буквально пару месяцев назад. У нее есть Ph.D. и публикации, она искала роль ML Research Scientist или что-то в этом роде.

Она рассказывает о том, как устроен процесс в AI-стартапах, в компаниях-единорогах (это стартапы, выросшие до размеров больше миллиарда долларов в оценке) и в больших техно-компаниях типа Google, Apple, Amazon, Meta итд. Дает примерные размеры предложенных зарплат включая бонусы и акции (150-200 тысяч долларов в год в стартапах, 345-400 тысяч долларов в год в больших компаниях), рассказывает о том, что прошло хорошо и что не понравилось, какие виды интервью были, какого рода вопросы задавали, как она рекомендует к интервью готовиться. В общем, интересно.
avva: (Default)
Недавно помогал ребенку подготовиться к контрольной по программированию (школьному предмету; они учат C#). Главной темой были связные списки и работа с ними, что в данном случае было работой с классами типа Node<T>, с методами Get/SetValue(), Get/SetNext().

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

Подумав немного, я решил не разбирать пока эти старые контрольные и их решения, а подтянуть основы. Сидя рядом, просил ребенка писать код НА БУМАГЕ, решающий очень простые задачи одну за другой. Немедленно обсуждали написанное, ошибки в нем, исправляли их и шли дальше. Задачи такие (во всех случаях предполагаем переменную list типа Node<int>, указывающую на первый элемент списка, возможно равную null, если список пустой):

- проверить, пустой ли список
- проверить, есть ли в списке минимум 3 элемента
- проверить, есть ли в списке ровно 3 элемента
- проверить, верно ли, что второй элемент списока равен 4 (не забывать проверки существования элементов)
- если третий элемент списка существует, изменить его значение на 12
- если есть минимум 2 элемента, удалить второй
- распечатать все элементы списка
- проверить, есть ли в списке элемент со значением 5
- если есть хотя бы один элемент, вставить новый элемент с значением 10 на второе место в списке
- удалить все элементы, равные 5, предполагая, что первый не такой
- то же самое, но без предположения, что первый не такой
- найти все элементы в списке, равные 2, и для каждого такого, если следующий тоже 2, а предыдущий не 2, удалить этот следующий
- найти элемент со значением 13, и если после него есть следующий, поменять их местами
- найти минимальный элемент
- вставить элемент на правильное место в отсортированном списке

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

После того, как все эти задачи решаются без сложностей, без ошибок и практически без рассуждений, можно переходить к задачам типа "поменять порядок на обратный" или "найти и удалить все дубликаты в списке с помощью двух вложенных циклов". Не надо с них *начинать*, если основы не делаются быстро, правильно и без сомнений. А это произойдет, когда ментальные образы станут ясными и четкими и будут правильно отражать происходящее на удобном уровне абстракции. Начинающий программист часто не понимает, насколько важны эти ясность и четкость. В таком случае задача наставника - понять это и показать на живых примерах, как и почему они важны.
avva: (Default)
Давно не покупал книг на бумаге, но захотелось чего-то для души, чего-то доброго. Книги о вечном, не всей этой мути в соц. сетях. Книги, с которой можно вечером в кровати полежать при теплом ламповом свете.

cuda.jpeg
avva: (Default)
Я заметил, что когда я запускаю команду git commit -m "причина..." в командной строке в Windows (если вы не программист, то просто примите за данное, что мне надо это часто делать), то она нормально работает, если нет закрывающих кавычек. Разумеется, я это обнаружил случайно, потому что палец сорвался на кнопку Enter, мне бы не пришло в голову самому попробовать такую извращенную идею.

Теперь передо мной стоит ужасная дилемма.

9ljkaz.jpg
avva: (Default)
(может быть интересно программистам)

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

Итак, три задачи:
1) проверить, что строка из скобок типа ((())()()())) правильно сбалансирована
2) дана матрица из нулей и единиц, где 0 - вода, а 1 - суша, найти кол-во "островов". Суша соединяется по 4 направлениям, не по диагонали
3) построить структуру данных, дающую insert(value), delete(value), sample_uniform_random() -> value, все операции быстрее, чем O(N).

Я не понял, это задачи на предварительное интервью (phone screen) или "настоящее". По моему опыту из Гугла, этот набор задач немного сложнее, чем типичное предварительное, и немного легче, чем настоящее. Но в принципе годные задачи, как мне кажется.

Про первую задачу: Грант написал, что самое быстрое решение было за 10 секунд, некий соревновательный программист из Казахстана. Я подумал: объяснил за 10 секунд? - ну так и я могу. Но оказалось, что чувал буквально за 10 секунд написал код, "a Python two-liner". Это, конечно, жесть. Мне стало любопытно, и я заморочился написанием двухстрочного решения на Питоне, и через пару минут (никак не 10 секунд, да), вымучил следующее:

def balanced(a):
cnt=0; b=[cnt:=cnt+(1 if i=="(" else -1) for i in a]
return not any([c<0 for c in b])

Про вторую задачу: можно делать DFS, но я бы обошелся простым проходом по матрице с слиянием островов. Если интересует только число островов, слияние можно сделать за O(1) через уровень индирекции, без сложных структур данных.

Третью задачу можно по-разному решать, напишу свой вариант в комментах.
avva: (Default)
В HN обсуждают, как некоторые программисты обходятся без autocomplete, и пишут код не в IDE.

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

От себя, как еще одного представителя этого племени, скажу так. Я много лет писал код в vim. Последний год нужно работать полностью под Windows по ряду причин, и я пишу код (на C++ и Питоне) в Notepad++. Все варианты autocomplete отключены, включая автоматическое добавление закрывающих скобок (это меня особенно раздражает почему-то).

Среда IDE слишком отвлекает изобилием всего в ней.

ripgrep в командной строке находит использования нужной функции, изредка (2% случаев) ему нужно дать regexp, чтобы нашел именно то, что нужно, и это не проблема.

Print debugging работает лучше (для меня), чем отладчик, в большинстве случаев. Я не настроен фанатически против отладчиков; бывают базы кода и проблемы, в которых без них не обойтись. Но редко.

Большинство времени, в которое я занимаюсь написанием кода, я на самом деле читаю код или смотрю в экран и думаю или смотрю в пространство и думаю. На клац-клац-клац уходит меньшая часть времени. Поэтому оптимизировать клац-клац-клац с помощью autocomplete не так важно, как может показаться.

ору

Dec. 20th, 2024 01:49 am
avva: (Default)
Сегодня узнал, почему код на SQL принято писать заглавными буквами, хотя в принципе язык позволяет маленькими тоже и любые сочетания: "Просто надо орать на базу данных. Она тогда немного быстрее работает."
avva: (Default)
Проект tinygrad - библиотека на Питоне для программирования нейронных сетей - поставил целью ограничить размер исходников и не разрастаться и не надуваться кодом, как другие такие проекты. Это и из названия видно. Они поставили 10 тысяч строк исходного кода в качестве жесткого лимита.

Увы, от принципа "you are what you measure" не убежишь, поэтому их код выглядит часто вот так (тыкабельно):

Gd54YRVW4AAW2rK.jpeg

Поучительно.

AoC

Dec. 1st, 2024 11:36 am
avva: (Default)
Напоминаю, что сегодня начинается Advent of Code - испытание в виде задачек на программирование, которые даются в течение 25 первых дней декабря. Каждый день в полночь открывается новое задание, в двух частях (вторая тяжелее первой). Первые несколько дней задания легкие, потом постепенно усложняются, но не до уровня крутых соревнований. Есть состязание между теми, кто присылает решение как можно быстрее, со списками лидеров, которые ждут полуночи и в течение пары минут шлют ответ, но я советую не обращать внимания на эти гонки, а просто решать в свое удовольствие.

Многие используют Advent of Code, чтобы научиться писать на новом для себя языке. Я так делал дважды в прошлом, и хочу попробовать и в этот раз, но пока не решил, на каком языке. Хотя я не писал нетривиальное количество кода на Rust и Go, мне кажется, что достаточно знаю о них из прочитанного с годами, что не хочется сейчас - лучше что-то более незнакомое. На Zig делал этот конкурс пару лет назад. Относительно известные "старые" языки: Lisp, Haskell, Forth, SmallTalk - когда-то знал или пробовал. Пока что размышляю между OCaml и Crystal. Хотите посоветовать мне что-то другое?
avva: (Default)
Продолжение и окончание истории об испорченных данных, начало см.
https://avva.livejournal.com/3705558.html

Испорченные данные возвращаются!

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

Когда я писал свою запись, программа, сравнивающая отличающиеся файлы из Америки и в Израиле, еще не закончила работу, но все указывало на то, что так и есть. Через пять часов после того, как я запостил, я посмотрел на логи и увидел, что все видимо сложнее. Так в итоге и оказалось.

Вернемся к началу и проведем учет того, что у нас есть. А есть у нас три массива данных, в идеале идентичных, назовем их O, A и B. O - это данные экспериментов, которые записывались на встроенный диск компьютера во время самих экспериментов. A и B - это подключаемые внешние SSD-диски. Размер диска O всего 1 терабайт, а данных генерировалось сотни гигабайт в день, поэтому во время исследования мы периодически сбрасывали данные с O на A и B, а потом стирали на O. Несколько раз мы торопились и копировали только на A или только на B, а потом отдельно копировали между ними - но это было реже. В каком порядке в точности что копировалось откуда и куда, сейчас уже никто не помнит. В конце всей этой вакханалии на O почти ничего не осталось, а на A и B были идентичные - по замыслу - копии всех данных. A остался в Америке, а B уехал в Израиль. И все было бы хорошо, если бы не плохой бит в памяти компьютера. (Да, в следующий раз мы все сделаем по-другому и лучше).

Итак, я скопировал все файлы A, у которых контрольная сумма отличается от B, в одно место с B, и запустил программу, которая находит все отличия. Программа написала мне лог-файл из 350 тысяч строк такого типа:

filename: offset 139709: 51 != 55, diff. -4

Для каждого различия написано, в каком файле и на каком смещении, приведен байт из Α и байт из B, и разница между ними.
Когда я писал запись, в этом файле, который продолжал создаваться, были только строки с разницей -4, что подтверждало следующую теорию (мне также казалось логичным, что порча была внесена во время одной сессии массивного копирования именно на B):

ТЕОРИЯ 1. A - это чистые исходные данные , а порча в B всегда состоит в том, что третий бит ставится в 1.

Но когда сравнение всех файлов закончилось, оказалось, что есть также много строк в другую сторону:

filename: offset 14467: 56 != 52, diff. 4

Это опровергает ТЕОРИЮ 1.

У нас есть файл всех изменений, который можно легко изучать с помощью текстового поиска (я настоятельно рекомендую программу ripgrep). Давайте разбудим нашего внутреннего Шерлока Холмса, усядемся поудобнее в мягком кресле, раскурим трубку и подумаем: что там могло случиться?

ТЕОРИЯ 2. A - это все еще чистые исходные данные, но порча в B иногда ставила бит в 1, иногда в 0.

Изменения в бинарных файлах не могут легко помочь подтвердить или опровергнуть эту теорию. Текстовые CSV файлы - другое дело. В них встречаются только символы: 0-9, английский алфавит (только первая строка заголовка, нерелевантна, слишком редка, чтобы попасть под порчу), запятая, точка (десятичная), перенос строки (коды 13 и 10 - перенос строки под Windows это два символа, а эти файлы писала программа в C++ под Windows).

Быстро находим много строк такого типа:

filename.csv: offset 1234: 14 != 10, diff. 4

Это опровергает ТЕОРИЮ 2: эта порча создалась изменением переноса строки 10 в 14, но в файле A как раз записано 14, значит, он испорченный. К сожалению, чистого экземпляра всех данных у нас просто нет. Значит ли это, что все потеряно?

Предположим, что между A и B есть разница: которую можно схематически обозначить так: xXxxxxxx и xxxxxxXx. Есть +4 в одном байте и -4 в другом, в разных файлах или даже в одном. Казалось бы, это значит, что бит менялся случайным образом. Но есть и другая возможость: что в исходном O было xxxxxxxx, и бит менялся в сторону увеличения дважды, при записи O->A и O->B, в разных местах.

ТЕОРИЯ 3. A и B оба испорчены, но во время копирования 3-й бит менялся произвольным, случайным образом.

ТЕОРИЯ 4. A и B оба испорчены, но во время копирования 3-й бит всегда ставился в 1.

Если верна ТЕОРИЯ 3, то у нас нет шансов восстановить чистые данные. Кое-где, как в различии 14 != 10, мы знаем, что там было 10, но если это 51 != 55 (цифры "3" и "7"), мы никак не можем узнать, какая цифра правильная. И в бинарных файлах нет шансов узнать.

Если же верна ТЕОРИЯ 4, то мы можем восстановить чистые данные почти целиком: надо просто исправить все байты в B, которые на 4 больше байтов в A, а те, которые на 4 меньше, оставить как есть - они как раз правильные. Крайне маловероятно, что где-то есть порча, попавшая на тот же байт в A и B в разное время копирования. Возможно, мы упустим небольшое количество испорченных данных, которые были скопированы с O на A, а потом без изменений с A на B, но тут уж ничего не поделаешь - они вообще не в списке наших измененных файлов.

Можно ли понять, какая из теорий верна? Можно. Если верна ТЕОРИЯ 3, то должно быть свидетельство именно падения бита из 1 в 0. Различие типа 51 != 55, где оба байта законны в CSV, не может таким считаться. Различие типа 10 != 14 доказывает как раз обратное: что было возрастание бита из 0 в 1, потому что 14 в исходном файле не было. Какой символ имеет выставленный 3-й бит, но если его сбросить, получается символ, который не бывает в CSV?

Это запятая. ASCII-код 44, если сбросить третий бит, выходит 40 - код левой скобки "(", которая в CSV не встречается. Запятых в CSV-файлах много, больше чем переносов строки. Примеров "10 != 14" в моем файле десятки. Примеров "40 != 44" ни одного. Вывод: ТЕОРИЯ 3 неверна, и все-таки бит всегда выставлялся в 1. Верна ТЕОРИЯ 4, и мы можем восстановить побитые файлы. Скрипт, который это делает, работает прямо сейчас.

(честности ради должен добавить: изначально я думал, что одна только запятая доказывает мои рассуждения, и мне нравилась эта драматичность. Оформляя эту запись, я осознал, что и точка, и carriage return (код 13, часть переноса строки) тоже дают свидетельства того же самого. Уже не так драматично, но истина дороже)
avva: (Default)
(для программистов и сочувствующих)

Слетали летом в командировку в Америку, получили много экспериментальных данных разных людей, привезли обратно в Израиль и стали анализировать. Данные занимают примерно два терабайта, уместились на одном SSD-диске. В Америке у партнеров осталась оригинальная копия (странное словосочетание, если задуматься).

Через пару месяцев обнаружили, что данные слегла подпорчены. Там на 99% бинарные файлы, а 1% - большие файлы .CSV с числами, числами, числами внутри. По ряду причин все данные хранятся как обычная большая файловая иерархия (а не, скажем, zip-архивы или база данных).

Код на питоне читает CSV-файлы с помощью библиотеки Pandas, которая подбирает для каждой колонки наиболее узкий тип данных - т.е. если в файле в 3-й колонке везде числа с плавающей точкой, это будет float, а если иногда что-то другое, будет string для всех. Обнаружилось, что в некоторых файлах числовые данные читаются как string, что потом приводит к странным результам, когда с ними делают числовые операции. Когда я стал разбираться, почему так, обнаружил, что мегабайтном файле, целиком состоящем из чисел, записано в одном месте "число" -0.053208=523, и этот знак = все портит. Всего таких испорченных csv-файлов оказалось мало, около 3%. Но я не знал, есть ли подобные ошибки также в бинарных файлах.

Если бы данных было меньше, я бы просто скопировал из Америки. Но копировать 2TB было немного болезненно, и заодно хотелось разобраться, откуда взялась порча и сколько ее. Поэтому я сделал следующее:

- запустил рекурсивный md5sum на все дерево в Америке и дома (10 миллионов файлов)
- скопировал файлы с контрольными суммами в одно место и сравнил, нашел, сколько файлов отличаются по сумме (300 тысяч), сгенерировал список этих файлов
- в Америке сделал zip-архив только с оригиналами испорченных файлов (80GB), скопировал в Израиль, раскрыл
- набросал скрипт, который прошелся по оригиналам, сравнил с испорченными копиями.

Оказалось следующее:

- испорчен примерно один байт из каждых 300 килобайт, т.е. 0.0003% данных
- порча есть в файлах всех типов, и бинарных, и CSV, просто в CSV заметили раньше
- порча всегда одного вида: третий бит с конца становится 1, неважно, был он до того 0 или 1. Т.е. значение байта увеличивается на 4 (или не меняется, если и так этот бит стоял).
- при этом в файлах CSV, состоящих почти целиком из цифр и запятых, происходит следующее. Запятые не меняются, их ASCII-код содержит этот бит. Цифры 0,1,2,3 переходят в 4,5,6,7 и мы эту порчу не заметили. Цифры 4,5,6,7 остаются как есть. И только 8,9 переходят в <,= и это дает вышеописанный эффект в чтении файла из питона. Поэтому только когда очень редкая порча попадает именно на текстовый файл, и в нем на одну из этих двух цифр, получается испорченный файл, который мы замечаем.

На данный момент моя лучшая версия - это плохая память в десктопе или лаптопе, на котором мы копировали все данные с диска-оригинала на диск, который взяли с собой домой. Видимо, сбоит один бит в одном байте памяти. Копировали с помощью Windows-программы robocopy. То, что в длинных последовательностях бинарных файлов одного размера ошибка почти всегда оказывается на одном оффсете от начала файла, косвенно подтверждает это объяснение. Альтернатива - это что какая-то херня происходит в контроллере SSD-диска, кажется маловероятным, но я недостаточно понимаю, как там все устроено. Других вариантов вроде нет.

Урок на будущее: после копирования большого кол-ва данных отдельно проверить, что копия идентична оригиналу, с помощью программы сравнения файлов/директорий.

(Сначала я хотел написать "копировать архивами, даже и без сжатия можно, просто чтобы была контрольная сумма", но если порчу вносит плохой бит в памяти во время *чтения* с диска, то архиватор создаст отличный архив с правильной суммой и плохими данными)

Прикольно.
avva: (Default)
Я задремал сегодня днем и мне приснилось, что у Бога есть возможность выводить чью-то жизнь в статус неслучайной. Если вам достался такой статус, то с этого момента ваша жизнь - это нарратив, и все с вами происходит по какой-то причине - включая ваши действия, у вас есть свобода воли. Не факт, что все будет в жизни хорошо, может как раз наоборот. Главное - что все неслучайно.

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

Во сне я обнаружил, что можно манипулировать в определенных рамках тем, кому достается этот статус. Я открыл какие-то хаки, как не гарантированно, но с неплохой вероятностью можно получить себе этот статус или подарить другому. Я пытаюсь этим поделиться с другими, но никто мне не верит, потому что инструкции кажутся тупыми, типа встань в таком-то месте враскоряку и прокукарекай глупо, или сделай такой-то нелепый жест. В конце концов меня приглашают к Богу на беседу, я прихожу и он мне рассказывает, что я все правильно открыл, но теперь это только головная боль для него закрывать все эти дырки и глитчи. Но вообще круто, говорит Бог, молодец, если хочешь, я тебе сделаю экскурсию, покажу, как тут все устроено, отвечу на твои вопросы. Я говорю, у меня один главный вопрос есть, можно сразу? Почему только сорок тысяч, почему нельзя большему числу людей дать неслучайную жизнь? Бог смотрит на меня, как будто я задал максимально идиотский вопрос, и говорит, ну как, ровно на столько хватает моего компьюта [1]. И тут я проснулся.

[1] вычислительных мощностей (профессиональный сленг)
avva: (Default)
Вас попросили дать оценку, когда будет выполнено задание, которое должно с вероятностью 90% занять один день, и 10% - два месяца. Ваш ответ?
avva: (Default)
Как же я раньше этого не понимал? Конечно же, "у верблюда два горба, потому что жизнь - борьба" это пословица про важность избыточности (redundancy), и опасность единой точки отказа (single point of failure).

July 2025

S M T W T F S
  12345
67 8910 1112
1314 1516 171819
20 212223242526
2728293031  

Syndicate

RSS Atom

Most Popular Tags

Style Credit

Expand Cut Tags

No cut tags
Page generated Jul. 25th, 2025 12:35 pm
Powered by Dreamwidth Studios