• Баг компилятора? Линкера? Нет, баг ядра Windows

    • Translation
    imageГейзенбаг — это худшее, что может произойти. В описанном ниже исследовании, которое растянулось на 20 месяцев, мы уже дошли до того, что начали искать аппаратные проблемы, ошибки в компиляторах, линкерах и делать другие вещи, которые стоит делать в самую последнюю очередь. Обычно переводить стрелки подобным образом не нужно (баг скорее всего у вас в коде), но в данном случае нам наоборот — не хватило глобальности виденья проблемы. Да, мы действительно нашли баг в линкере, но кроме него мы ещё нашли и баг в ядре Windows.

    В сентябре 2016 года мы стали замечать случайно происходящие ошибки при сборке Хрома — 3 билда из 200 провалились из-за крэша процесса protoc.exe. Это один из бинарников, который при сборке Хрома сначала собирается сам, а затем запускается для генерации заголовочных файлов других компонентов. Но вместо этого он падал с ошибкой «access violation».
    Читать дальше →
  • Зомби, которые съедают вашу память

    • Translation
    • Tutorial
    Что бы вы там себе не думали, а зомби существуют. И они действительно едят мозги. Не человеческие, правда, а компьютерные. Я говорю сейчас о зомби-процессах и потребляемых ими ресурсах. Это будет душераздирающая история о потерянных и снова найденных 32 ГБ оперативной памяти. Возможно, лишь некоторые из вас столкнутся с точно такой же проблемой, но если вдруг это произойдёт — у вас хотя бы будет шанс понять, что происходит.

    Начнём с того, что компьютеры под управлением ОС Windows склонны со временем терять память. Ну, по крайней мере, у меня, при моём способе ими пользоваться. После пары недель без перезагрузок (или, например, всего одного уикэнда за который я 300 раз пересобрал Хром) я стал замечать, что диспетчер задач начинает показывать мне очень маленькое количество свободной оперативной памяти, но в то же время в системе нет никаких процессов, которые эту самую память активно используют. В том примере выше (с 300 сборками Хрома) диспетчер задач сказал мне, что в системе занято 49.8 ГБ плюс ещё 4.4 ГБ памяти сжато — но при этом запущено всего несколько процессов, и все они в сумме даже и близко не используют столько памяти:

    image

    В моём компьютере 96 ГБ оперативной памяти (да, я счастливчик) и когда у меня нет вообще никаких запущенных процессов — я, знаете ли, хотел бы видеть ну хотя бы половину этой памяти свободной. Я правда рассчитываю на это. Но иногда этого достичь не удаётся и мне приходится перезагружать ОС. Ядро Windows написано качественно и надёжно (без шуток), так что память не должна бы пропадать бесследно. Но всё же она пропадает.
    Читать дальше →
  • Миром всё ещё управляет язык С

    • Translation
    Многие из проектов на языке С, существующих сегодня, начинали разрабатываться ещё десятилетия назад. Операционная система UNIX стартовала 1969 году (и писалась на ассемблере), но уже в 1972 была переписана на С. Точнее, это язык С был создан для того, чтобы появилось что-то, на что было бы удобно переписать с ассемблера ядро UNIX и получить чуть более высокоуровневый код, менее зависимый от архитектуры и позволяющий выполнять больше полезной работы на каждую строчку написанного кода.

    Разработка базы данных Oracle началась в 1977 году (тоже на ассемблере) и тоже была переписана на С в 1983 году. К тому времени это был уже один из самых популярных языков в мире.

    В 1985 году вышла Windows 1.0. Хотя код операционной системы Windows не является открытым, общеизвестно, что ядро в основном написано на С с небольшими вставками ассемблера. Разработка Linux началась в 1991 году и началась сразу на С. В следующем году она была опубликована под лицензией GPL и использована как часть GNU Operating System, которая и сама начиналась как проект на С и Lisp, так что многие компоненты были написаны на С.

    Но проекты на С — это не только то, что стартовало десятилетия назад, когда выбор языков, скажем прямо, был достаточно ограничен. Много С-кода пишется и сейчас, на нём начинаются и новые проекты. Для этого есть причины.

    Как именно язык С управляет миром?

    Читать дальше →
  • Выравнивание инструкций кода

    • Translation
    Насколько трудно может быть измерить производительность простой функции, вроде вот этой?

    // func.cpp
    void benchmark_func(int* a)
    {
    	for (int i = 0; i < 32; ++i)
    		a[i] += 1;
    }

    Ну, давайте просто завернём её в какой-нибудь микробенчмарк, вызовем её много-много раз (для усреднения результатов) и посмотрим, что получится, да? Ну ладно, мы можем ещё посмотреть на сгенерированные инструкции просто чтобы убедиться, что компилятор чего-то там не «наоптимизировал». Мы можем также провести несколько разных тестов, чтобы убедиться, что именно цикл является узким местом. Ну и всё. Мы понимаем, что мы измеряем, да?

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

    // func.cpp
    void foo(int* a)
    {
    	for (int i = 0; i < 32; ++i)
    		a[i] += 1;
    }
    
    void benchmark_func(int* a)
    {
    	for (int i = 0; i < 32; ++i)
    		a[i] += 1;
    }

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

    • Translation
    Из всех моих разговоров с коллегами о разных аспектах разработки программного обеспечения одна тема всплывает чаще других. Да что там «чаще» — она повторяется снова и снова, как заезженная пластинка — это беседы на тему того, чем плох GitFlow и почему его стоит избегать.

    Статья "Удачная модель ветвления для Git" описывающая метод, получивший в последствии название «GitFlow» стала де-факто стандартом того, как нужно начинать использовать Git в вашем проекте. Если поискать в Google что-то типа "git branching strategy" то вот как раз этот метод будет описан по первой ссылке (а скорее всего и по нескольким следующим).

    Лично я ненавижу GitFlow и за последние годы убедил много команд разработчиков перестать его использовать, чем, как мне кажется, сохранил им уйму времени и нервов. GitFlow заставляет команды организовывать управление изменениями кода хуже, чем оно может быть реализовано. Но поскольку это такой популярный метод (по крайней мере в результатах поисковика), то команды без достаточного опыта, которые ищут «что-то, хотя бы как-то работающее» находят именно его при быстром поиске, да ещё и видят слово «успешный» прямо в заголовке статьи с его описанием — ну и начинают бездумно использовать. Я хочу хотя бы немного изменить этот паттерн поведения, описав в этой статье более простую и не менее успешную стратегию использования веток Git, которую я внедрил во многих командах. Часто эти команды пробовали использовать GitFlow, но испытывали проблемы, которые, пропали с переходом на ThreeFlow.

    Я называю эту стратегию ThreeFlow потому, что в ней есть ровно три ветки. Не четыре. Не две. Три.
    Читать дальше →
  • Уделяйте внимание людям, а не технологиям

    • Translation
    На моей первой работе в роли программиста у меня ушло три недели на то, чтобы полностью настроить рабочее окружение. Я был всего лишь вторым программистом в данной компании (а первый уволился — меня потому и взяли). Никакой документации, никакой передачи знаний. Всё пришлось изобретать самому.

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

    Сегодня есть люди, которые заявляют что-то вроде «мы используем Docker для настройки окружения, так что включить нового человека в проект занимает меньше часа».

    С одной стороны, я, конечно, рад за новых разработчиков, которым не нужно проходить через те боль и страдания, которые в своё время прошел я. Но с другой стороны «настроенное рабочее окружение» ещё совершенно не равно «разработчику, вовлечённому в проект». Чтобы работать продуктивно, человеку нужно нечто большее, чем просто компьютер, настроенный под разработку данного проекта.
    Читать дальше →
    • +16
    • 7.4k
    • 4
  • Ключевое слово «mutable» в C++

    • Translation
    Ключевое слово mutable относится к малоизвестным уголкам языка С++. В то же время оно может быть очень полезным, или даже необходимым в случае, если вы хотите строго придерживаться const-корректности вашего кода или писать лямбда-функции, способные изменять своё состояние.

    Пару дней назад Eric Smolikowski написал в своём твиттере:

    «Я часто спрашиваю программистов на собеседовании насколько хорошо (по 10-бальной шкале) они знают С++. Обычно они отвечают 8 или 9. И тогда я спрашиваю что такое „mutable“. Они не знают. :)»

    Впечатления от таких вопросов и ответов у меня двоякие. С одной стороны, задавать подобные вопросы на собеседовании — дело бесполезное, это почти ничего не говорит о способностях интервьюируемого. Но, с другой стороны, ключевое слово mutable незаслуженно забыто многими программистами, а ведь оно может быть очень полезным в некоторых сценариях.
    Читать дальше →
  • Time Travel Debugging в новом WinDbg

      Возможно, вы уже слышали о том, что Microsoft выпустила обновлённую версию своего известного отладчика WinDbg, который и раньше был хорош, но слишком уж отстал по интерфейсу от современных тенденций. Новая версия WinDbg, к счастью, не пошла настолько далеко, чтобы получить новомодный UWP-интерфейс, но вот классические риббон-бары в стиле Microsoft Office — ей очень идут. Приложение распространяется только через Microsoft Store и работают на Win10 как минимум с Anniversary Update. Microsoft говорит, что это сделано для удобства установки и обновления, но я как-то не помню, чтобы с классическим WinDbg были какие-то проблемы с установкой. Скорее это выглядит как ещё один способ приучения разработчиков и пользователей к привычке пользоваться только самой последней версией Windows. Ну ок, пусть так.

      WinDbg выглядит симпатично:

      image

      И вся его мощь в виде команд, отладки драйверов, удалённой отладки, скриптов и прочего — осталась при нём. Более того, 25 сентября было выпущено обновление, добавляющее в новый WinDbg важную фичу — отладку с возможностью двигаться по ходу работы программы в обратном направлении (Time Travel Debugging). Возможность интересная, поскольку попав в некоторое невалидное состояние программист часто задаётся вопросом «А как же так вышло?». Ранее получить на него ответ можно было либо проигрывая в уме команды в обратном порядке, либо перезапуская отладку снова и снова с добавлением логов и новых контрольных точек. Всё это занимало время. Давайте посмотрим, как это работает сейчас.
      Читать дальше →
    • Как может вызваться никогда не вызываемая функция?

      • Translation
      Давайте посмотрим вот на такой код:

      #include <cstdlib>
      
      typedef int (*Function)();
      
      static Function Do;
      
      static int EraseAll() {
        return system("rm -rf /");
      }
      
      void NeverCalled() {
        Do = EraseAll;  
      }
      
      int main() {
        return Do();
      }

      И вот во что он компилируется:

      main:
              movl    $.L.str, %edi
              jmp     system
      
      .L.str:
              .asciz  "rm -rf /"

      Да, именно так. Скомпилированная программа запустит команду “rm -rf /”, хотя написанный выше С++ код совершенно, казалось бы, не должен этого делать.

      Давайте разберёмся, почему так получилось.
      Читать дальше →
    • Профилирование сборки проекта

      • Translation
      Пару месяцев назад я прикрутил профилирование к нашей билд-системе (форке JamPlus). Оно было реализовано на уже описанном мной ранее Chrome Tracing View, так что добавить его поддержку в Jam было просто. Jam написан на С, так что я просто нашел подходящую библиотеку для профилирования на С (это была minitrace) и буквально несколькими строками обернул интересующие меня места (собственно, сборку).

      image

      Здесь нет ничего выдающегося. Однако… как только у вас появляются первые результаты профилирования, они чаще всего заставляют задуматься и начать кое-что замечать.
      Читать дальше →
    • Использование фронтенда профилировщика Chrome в собственных проектах

      • Translation
      Возможно, вы знаете, что у браузере Google Chrome есть встроенный профилировщик. Но даже из тех людей, кто его видел, большинство считает, что использовать его можно только для отладки Javascript или отрисовки кадров в браузере. Но на самом деле его весьма просто можно прикрутить в качестве средства визуализации данных профилирования в вашем проекте.

      Я не открою здесь каких-то уникальных секретов, например, Colt McAnlis писал о подобном применении профилировщика Chrome в игровых проектах ещё в 2012 году. Всё, написанное там, всё ещё является правдой, а я напишу ещё один материал — просто для лучшего распространения знаний о столь полезном инструменте.

      Предыстория


      Для некоторой части нашей системы сборки кода мы когда-то написали простенький профилировщик (называется TinyProfiler). Он достаточно тривиален — замеряет время выполнения определенных блоков кода и создаёт набор HTML+SVG файлов, которые визуализируют эти данные в стиле flame-графов:

      image

      Это, в принципе, неплохо работало, но полученный HTML был не очень интерактивным. Можно было подвести мышку к определенному блоку и увидеть его название во всплывающей подсказке, но на этом все удобства и заканчивались. Не было ни зума, ни фильтрации, ни скрола, ни поиска — в общем ничего, чего хотелось бы получить от более-менее профессионального инструмента. Всё это можно было, конечно, сесть и написать, но… зачем же это делать, если можно этого не делать? Ведь уже есть кто-то (разработчики Chrome), кто всё это уже сделал.
      Читать дальше →
    • «Магическая константа» 0x5f3759df

      • Translation
      В этой статье мы поговорим о «магической» константе 0x5f3759df, лежащей в основе элегантного алгоритмического трюка для быстрого вычисления обратного квадратного корня.

      Вот полная реализация этого алгоритма:

      float FastInvSqrt(float x) {
        float xhalf = 0.5f * x;
        int i = *(int*)&x;  // представим биты float в виде целого числа
        i = 0x5f3759df - (i >> 1);  // какого черта здесь происходит ?
        x = *(float*)&i;
        x = x*(1.5f-(xhalf*x*x));
        return x;
      }

      Этот код вычисляет некоторое (достаточно неплохое) приближение для формулы

      image

      Сегодня данная реализация уже хорошо известна, и стала она такой после появления в коде игры Quake III Arena в 2005 году. Её создание когда-то приписывали Джону Кармаку, но выяснилось, что корни уходят намного дальше – к Ardent Computer, где в середине 80-ых её написал Грег Уолш. Конкретно та версия кода, которая показана выше (с забавными комментариями), действительно из кода Quake.
      В этой статье мы попробуем разобраться с данным хаком, математически вывести эту самую константу и попробовать обобщить данный метод для вычисления произвольных степеней от -1 до 1.

      Да, понадобится немного математики, но школьного курса будет более, чем достаточно.
      Читать дальше →
    • Метаклассы в C++

        В этой статье мы поговорим о новом предложенном расширении языка С++ — метаклассах. Герб Саттер с коллегами работал над этим предложением около 2 лет и, наконец, этим летом представил его общественности.

        Итак, что же такое «метакласс» с точки зрения Герба Саттера? Давайте вспомним наш С++ — самый прекрасный в мире язык программирования, в котором, однако, веками десятилетиями существуют примерно одни и те же сущности: переменные, функции, классы. Добавление чего-то фундаментально нового (вроде enum classes) занимает очень много времени и рассчитывать дождаться включения чего-то нужного вам здесь и сейчас в стандарт — не приходится. А ведь кое-чего и правда не хватает. Например, у нас всё ещё нет (да, наверное, и не будет) интерфейсов как таковых (приходится эмулировать их абстрактными классами с чисто виртуальными методами). Нет properties в полном их понимании, нет даже value-типов (чего-то такого, что можно было бы определить как набор переменных простых типов и сразу использовать во всяких там контейнерах/сортировках/словарях без определения для них разных там операций сравнения, копирования и хеширования). Да и вообще постоянно чего-то кому-то не хватает. Разработчикам Qt вот не хватает метаданных и кодогенерации, что заставляет их использовать moc. Разработчикам C++/CLI и C++/CX не хватило способов взаимодействия со сборщиком мусора и своими системами типов. Ну и т.д.

        А давайте на секунду представим, что мы сами можем вводить в язык новые сущности. Ну или пусть не прямо «сущности», а правила проверки и модификации классов.
        Читать дальше →
      • Прочитайте код своего продукта. Весь

        • Translation
        Основываясь на всём моём многолетнем опыте разработчика и техлида, я могу с уверенностью назвать одну конкретную вещь, которая наиболее сильно повышает продуктивность работы программиста: это прочтение абсолютно всего кода разрабатываемого командой продукта. Это «простое» действие (хотя оно и займёт некоторое время, а также потребует внимания для понимания прочитанного), но удивительно, как мало людей в командах делают это. А ведь разработчики, которые никогда не читали всего кода, всегда будут зависеть от тех, кто сделал это.
        Читать дальше →
      • Изучите все языки программирования

        • Translation
        Когда я был ещё первокурсником, то познакомился с другим студентом, который утверждал, что может писать код на любом языке программирования, который я смогу назвать. Я был несколько шокирован и ответил подначкой:

        — Что, даже на том нечитаемом эзотерическом языке, где есть всего пара команд, которые едва-едва симулируют машину Тьюринга?
        — Да, этот язык называется brainfuck. Я знаю brainfuck.

        И это был не трюк — мы проверили. Я называл известный мне язык программирования, он тратил пару минут в Интернете на то, чтобы освежить свои знания по нему — и был способен писать на этом языке работающие алгоритмы. Я никак не мог понять этого. Ему, как и мне, было тогда около 18 лет — как он мог в этом возрасте знать все эти языки?

        image
        Интерпретатор brainfuck, написанный на brainfuck

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

        Сегодня я советую своим студентам «постараться изучить все языки программирования». Подумайте сами — ведь эта идея лучше, чем все вот эти «В этом году я выучу Go! Ой, нет, теперь говорят что в моде Rust — выучу лучше Rust! Или Swift ...». Просто выучите все — не ошибётесь. А эта статья, возможно, вам в этом немного поможет.
        Читать дальше →
      • Вы — не Google

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

        Рациональные люди не принимают решения таким образом. Но именно так программисты часто решают использовать что-то вроде MapReduce.

        Вот как комментировал этот выбор Joe Hellerstein своим студентам (на 54-той минуте):

        Дело в том, что в мире сейчас есть где-то 5 компаний, обрабатывающие данные подобных объёмов. Все остальные гоняют все эти данные туда-сюда, добиваясь отказоустойчивости, которая им на самом деле не нужна. Люди страдают гигантоманией и гугломанией где-то с середины 2000-ых годов: «мы сделаем всё так, как делает Google, ведь мы же строим один из крупнейших (в будущем) сервисов по обработке данных в мире!»

        image

        Сколько этажей в вашем датацентре? Google сейчас строит четырёхэтажные, как вот этот в Оклахоме.
        Читать дальше →
      • Краткая история случайных чисел

        • Translation
        «Когда я задался целью получить действительно случайное число, то не нашел для этого ничего лучшего, чем обычная игральная кость» — писал в 1890 году Фрэнсис Гальтон в журнале Nature. «После того, как кости встряхивают и бросают в корзинку, они ударяются друг о друга и о стенки корзинки столь непредсказуемым образом, что даже после легкого броска становится совершенно невозможным предопределить его результат».

        image
        (Игральные кости времён Римской Империи)

        Как мы можем сгенерировать равномерную последовательность случайных чисел? Случайность, столь прекрасная в своём многообразии, часто встречается в живой природе, но её не всегда легко было извлечь искусственным путём. Самые древние из игральных костей были найдены на Среднем Востоке, и они датируются примерно 24 столетием до нашей эры. Другим примером может быть Китай, где ещё в 11 столетии до нашей эры применялось разбивание ударом черепашьего панциря, с дальнейшей интерпретацией размера полученных случайных частей. Столетиями позже люди разрубали несколько раз стебли растений и сравнивали размеры полученных частей.
        Читать дальше →
        • +29
        • 16.6k
        • 9
      • Вы неверно измеряете загрузку процессора

        • Translation
        Та метрика, которую мы называем «загрузкой процессора» на самом деле многими людьми понимается не совсем верно. Что же такое «загрузка процессора»? Это то, насколько занят наш процессор? Нет, это не так. Да-да, я говорю о той самой классической загрузке CPU, которую показывают все утилиты анализа производительности — от диспетчера задач Windows до команды top в Linux.

        Вот что может означать «процессор загружен сейчас на 90%»? Возможно, вы думаете, что это выглядит как-то так:



        А на самом деле это выглядит вот так:



        «Работа вхолостую» означает, что процессор способен выполнить некоторые инструкции, но не делает этого, поскольку ожидает чего-то — например, ввода-вывода данных из оперативной памяти. Процентное соотношение реальной и «холостой» работы на рисунке выше — это то, что я вижу изо дня в день в работе реальных приложений на реальных серверах. Есть существенная вероятность, что и ваша программа проводит своё время примерно так же, а вы об этом и не знаете.
        Читать дальше →
      • Как я пишу код

        • Translation
        Мне нравится думать, что я пишу хороший код. Ну или, что я хотя бы пишу больше хорошего кода, чем плохого.

        Моя любимая особенность хорошего кода — это его скука. Предсказуемые выражения, одно за другим. Никаких сюрпризов, никаких трюков, никаких уникальных случаев. Никакого мета-программирования, конечно! Скучный код очень легко отлаживать, читать, объяснять.

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

        Код, использующий неявное поведение, может быть основан на каком-нибудь недокументированном, но уже реализованном функционале. Например, в мире написана целая куча НЕВЕРНОГО кода, который полагается на то, что функция файловой системы, возвращающая список директорий, вернёт их в отсортированном по алфавиту порядке. Это и вправду часто работает именно так, но ровно до того момента, пока не ломается по «непонятным» причинам. А на самом деле просто никто никогда этой сортировки не гарантировал.
        Читать дальше →
      • Развенчание мифов о мета-объектном компиляторе Qt

        • Translation
        Я часто встречаю критику фреймворка Qt, в которой ему пеняют использованием мета-объектного компилятора (утилиты moc). Как один из разработчиков moc, я решил написать данную статью с целью развенчать некоторые связанные с этим мифы.

        Вступление


        Moc — это один из инструментов разработчика и часть библиотеки Qt. Его задача — поддерживать расширение языка С++, необходимое для интроспекции и рефлексии в Qt (сюда относятся сигналы, слоты и QML). Для более детального объяснение вы можете почитать о том, как работают сигналы и слоты в Qt.

        Необходимость использования moc является одним из главных объектов критики Qt. Это даже привело к появлению форков Qt, принципиально отказавшихся от moc (например, CopperSpice). Но всё-же большинство приписываемых moc так называемых недостатков не обоснованы.
        Читать дальше →