Первобытное мышление или история магической единицы

34e9e6ea25adb059eeaef0f777395f3f.jpg

В своей книге «Первобытное мышление» Люсьен Леви-Брюль рассказывает, как люди из первобытных обществ, даже прожившие долгое время в цивилизованном обществе, продолжают верить в колдунов и магию. Его теория состояла в том, что первобытные народы склонны к так называемому дологическому мышлению, а представители современной цивилизации опираются в своих суждениях на логику. Леви-Брюль, однако, ошибался. Ниже я покажу, как люди из цивилизованного общества, получившие образование в ведущих ВУЗах страны, начинают верить в магию даже в той области, в которой они являются профессионалами. Имена, названия и высказывания изменены так, чтобы никто не нашёл себя и не обиделся.

Работал я тогда в компании среднего размера. Несмотря на средний размер, это была компания, которая продавала свой продукт в передовые страны запада, а это всё-таки показатель высокого качества. И работали там не простые программисты, а выпускники мехмата МГУ, МФТИ и прочих уважаемых ВУЗов. Но был в этой компании момент, который меня конкретно выбешивал: мне постоянно повторяли, что без особой необходимости не надо ничего менять. Как-то, помнится, нашёл я в коде строку

x = (a + b * c + d) * (g - sin(alpha) * cos(beta) ) * (cos(d5) + d8 + d11) / (k - t * p) / (d7 + d9 + d15);
y = (a + b * c + d) * (g - sin(alpha) * cos(beta) ) * (cos(d5) + d8 + d11) / (k - t * p) / (d7 + d9 + d15);
z = (a + b * c + d) * (g - sin(alpha) * cos(beta) ) * (cos(d5) + d8 + d11) / (k - t * p) / (d7 + d9 + d15);

Ну я взял и поменял на

x = y = z = (a + b * c + d) * (g - sin(alpha) * cos(beta) ) * (cos(d5) + d8 + d11) / (k - t * p) / (d7 + d9 + d15);

Не ускорения ради, а просто чтобы не мозолило глаз. Как на меня тогда напал наш группенфюрер…

Сейчас все модели сломаются! Потом скажут, что мы всё сломали! Я такие изменения не пропущу!

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

if (fabs (mult - 1.) > std::numeric_limits::epsilon ())  // this is needed because x * 1. != x if x << 1
{
    factor *= mult;
}

Как мы видим, стоит проверка, что если отличие множителя от единицы меньше численной точности представления (примерно 16 знаков для double), то умножение не производится. С точки зрения любого разумного человека эта проверка является совершенно бессмысленной, хоть и довольно безвредной (если конечно не считать вредом то, что людей заставляют верить в магию умножения на единицу). Однако автор кода оставил для нас комментарий, почему эта проверка важна: мол если множитель равен 1, то лучше не умножать, потому что это может изменить умножаемое число, если это число мало. Писал, очевидно, человек, который не знает, что в представлении с плавающей запятой числа хранятся так, что их относительная точность не зависит от того, равно число 1.e+100 или 1.e-100.

Мне стало интересно, как могло родиться такое, и я начал читать соответствующий «тикет». Не знаю, смогу ли я передать невероятное напряжение, захватившее меня во время расследования, но я чувствовал себя детективом, расследующим убийство в романе Агаты Кристи. Детективом, расследующим убийство разума.

Множитель mult в вышеозначенном коде ввёл некто, назовём его Вова, в процессе реализации нового функционала. В старых моделях этот множитель всегда равнялся единице и никак не должен был повлиять на их работу. Однако через какое-то время было обнаружено, что падает одна из моделей. Стали выяснять почему и оказалось, что движок требовал памяти больше, чем имелось на машине, в результате чего программа падала. Как часто бывает в таких ситуациях, когда умные люди, не имея дельных предложений, молчат, находится ограниченный, который предлагает какую-то херню. И такой человек немедленно нашёлся. Назовём его Коля. Коля «исправил» проблему: при превышении размера имеющейся памяти программа стала корректно завершаться, а не просто падать. Кроме этого весомого вклада в решение Коля обратил внимание, что падающая модель невероятно сложна: содержит аж на три элемента больше, чем другие модели, солвер не справляется и давайте использовать магическое слово «СЛОЖНАЯЗАДАЧА» для решения таких моделей. Как также обычно бывает, вместо того, чтобы сказать ограниченному, что он ограниченный, большинство соглашается с его предложениями, а умные люди продолжают молчать. Но оказалось, что модель всё равно продолжала падать, правда теперь уже на кластере.

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

В этот момент вмешался технических директор и начал спрашивать, что мол никого не удивляет, с какого перепуга узлу потребовалось 70 гигов памяти, но ему быстро объяснили, что причина в страшной модели (в сочетании со страшным колдунством конечно же), и он перестал мешать людям бороться с колдунством: в модели что-то подкручивали, что-то меняли, но колдунское проклятие было слишком сильно и ничего не помогало. К концу года проблему так и не победили и просто перестали обращать на неё внимание. Однако через несколько месяцев (!) некто, назовём его Муся, сделал наброс на вентилятор, написав, что мол Вова своим множителем сломал модель. Это ключевой момент в истории, поэтому обратим внимание терпеливого читателя на то, что модель и без изменений Вовы не работала уже больше года. Просто после изменений Вовы она стала выплёвывать другие ошибки.

По-хорошему Вова мог бы призвать участников дискуссии к разуму и объяснить, что его изменения вообще не затрагивали тот кусок кода, где происходило падение, и что оно происходило и до его изменений. Но какой разум с этими людьми? И Вова нашёл наилучшее решение проблемы: поставил условие, что если множитель равен единице, то не надо домножать на этот множитель. И заодно вкатил туда псевдонаучное объяснение, что мол x * 1. != x. Однако это бессмысленное изменение сделало главное: проблемная модель снова стала глючить точь-в-точь, как глючила до этого.

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

Итого: проблема решена, модель работает, и все верят в то, что маленькие числа лучше не умножать на единицу. Занавес.

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

© Habrahabr.ru