Сегодня мы расскажем вам историю об одном интересном баге в Яндекс.Браузере, исправление которого привело к значительному ускорению отрисовки во всем проекте Chromium. И помогут мне в этом Кирилл drBasic Плешивцев и Вадим Lof Петров, специалисты из нашей команды, которым и посчастливилось разбираться с проблемой. Передаю им слово.



Один не совсем обычный баг

Меня зовут Кирилл, я работаю в группе внутренних компонентов Яндекс.Браузера в Новосибирске. В один не совсем прекрасный день коллеги из тестирования Яндекс.Браузера воспроизвели проблему с проигрыванием видео через Flash Player. И поскольку именно наша группа отвечает за эту часть браузера (медиа, кодеки, вот это все), задача досталась мне. Баг, скажем так, не претендовал на оригинальность. Клик по кнопке Play приводил к черному прямоугольнику вместо корректного воспроизведения видео. Этот симптом я встречал и раньше, поэтому рассчитывал на достаточно быструю локализацию проблемы. Но я ошибался.

Буквально в первые же минуты удалось выяснить, что черный прямоугольник возникает не всегда, а только для flash-элементов с типом transparent, т.е. полупрозрачных. Отлично, уже есть за что зацепиться при отладке. Собираю debug-версию браузера, запускаю, бага нет. А это уже тревожный звонок. Расхождения в работе debug и release версий — это всегда очень весело. Поэтому решаюсь собрать еще и релизную версию. Собрал, запускаю, бага нет.

Задумался. В чем отличия моей релизной сборки от той, что собирает сервер? Сходу вспомнил про компоновку библиотек. Разработчики собирают браузер в режиме shared_library. Это увеличивает количество dll, но зато сильно экономит время компоновки. Распространяется же браузер, собранный в режиме static_library, при котором собирается лишь несколько больших dll. Выставляю флаг static_library, делаю полную сборку. Наблюдаю, как link.exe медленно съедает всю оперативную память, но нет, 16 ГБ RAM хватит всем, компоновка завершается без допинга в виде файла подкачки. Запускаю. Бага нет.

Серьезно задумался. Вспомнил, что сборочный сервер собирает релизный Яндекс.Браузер с флагом official, который немного меняет поведение (подробнее расскажем чуть позже). Собираю с этим флагом. Дрожащей рукой запускаю браузер. Вы уже угадали? Бага нет.

Тут я не на шутку встревожился и начал думать изо всех сил. Через некоторое время обратил внимание на то, что сервер собирает Яндекс.Браузер с помощью Visual Studio 2013. А я же использовал 2015 версию. Собираю в 2013 версии. Запускаю. Баг есть! Кто бы мог подумать, что я так буду радоваться ошибке.

Если вы сейчас подумали, что вся проблема заключалась только в версии VS, то ошибаетесь. Баг действительно не воспроизводился в debug-версии браузера. Опытным путем удалось установить, что для появления ошибки с черным прямоугольником браузер должен быть собран не только с помощью VS 2013, но и в статичной компоновке с флагом official. О причинах такого странного поведения вы узнаете чуть позже.

Следующие два дня были не менее интересными. В ходе отладки мне удалось понять, что сам плагин Flash Player отрабатывает свою задачу корректно: видео воспроизводится. Его интеграция с браузером вопросов также не вызывала. Результат его работы передавался для отрисовки, но по каким-то причинам на экране мы видели совсем другое. А это значит, что баг нужно было искать в той части браузера, которая отвечает за рендеринг. И здесь я передаю слово Вадиму.

Оптимизируй это

Как вы уже поняли, теперь на связи Вадим. Работаю я в группе разработки рендеринг-движка Яндекс.Браузера в Москве. Несколько слов о том, как вообще происходит отрисовка в Яндекс.Браузере или Chromium. Все, что вы видите в окне браузера, есть результат совмещения различных слоев (веб-страница, интерфейс), почти как в Photoshop. За работу с этими слоями отвечает компонент Compositor (или Chrome's Compositor == CC). А вот для отрисовки уже каждого слоя СС вызывает специальную опенсорсную библиотеку Skia.

Вместе с Кириллом мы поняли, что следы бага уходят в Skia. Оставалось понять, куда именно. К счастью, у меня была ценная подсказка. Почти в самом начале Кирилл её упомянул. Речь о том, что проблема возникает только в случае flash-элементов с прозрачностью. Чтобы отрисовать на экране такой элементы, браузеру необходимо совместить картинку видео с фоном. И для этого в Skia есть специальная функция SrcATop, отвечающая за блендинг. Несколько минут поиска, и вот я уже нашел багрепорт со схожей проблемой в Chrome, который окончательно развеял все сомнения.

Ура. Мы локализовали источник проблемы вплоть до конкретной функции. А теперь, внимание. Этот участок кода не содержал никаких ошибок. Совсем никаких. И работал он идеально для любых сборок кроме самой финальной, которая и отправляется пользователям. Причем только для Visual Studio 2013. И вот в этот момент я понял, почему Кирилл называл этот баг «веселым».

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



В 2011 году проект Chromium стал настолько большим и сложным, что компоновщик Visual Studio 2010 однажды не смог слинковать его со всеми оптимизациями из-за нехватки ресурсов. Чтобы обойти проблему, разработчики решили по умолчанию оптимизировать все подпроекты (а их больше тысячи) не по скорости работы (/O2), а по размеру кода (/O1). И лишь для избранных и наиболее критичных, или для тех, чьи владельцы не проспали эту ситуацию, включили обратно /O2. Например, это сделали для CC и Skia. Вот только в 2013 году при рефакторинге в Skia оптимизацию случайно потеряли. И никто бы ничего не заметил, если бы еще через два года не случился еще один рефакторинг в Chromium, в результате которого часть кода сделали шаблонным и перенесли в header. И вот тут-то все и началось.

А началось вот что. Когда происходит сборка релизного браузера с флагом official, библиотеки, имеющие разные цели для оптимизации (по скорости, по размеру), оказываются в одной dll. Само по себе это не признак чего-то плохого. Например, в Visual Studio 2015 никаких проблем это не вызывает. Студия пыхтит час над оптимизацией и выдает вполне рабочий код. Но стоит нам заменить её 2013-й версией, и все ломается. Почему?

Функция SrcATop, которая отвечает за блендинг в Skia, принимает два параметра через регистры xmm0 и xmm1. И почти всегда она работает корректно. Но как мне удалось выяснить в ходе отладки, стоит добавить сюда VS 2013 и непростую оптимизацию, и функция вырождается до такой степени, что начинает возвращать в ответ содержимое первого регистра. Отсюда и появлялся неизменный черный фон вместо видео. Всему виной была неправильная кодогенерация в VS 2013.

Ускоряем веб-сёрфинг

При большом желании баг можно было «исправить» локально, немного подкрутив SrcATop. Но мне показалось неправильным, что у такого важного для отрисовки компонента, как Skia, отсутствует оптимизация по скорости. Поэтому я собрал новую сборку, в которой выставил для Skia оптимизацию по скорости. Баг, конечно же, пропал. Казалось бы, можно закрывать задачу и идти пить чай, но нет. Мне нужно было сделать еще кое-что.

Команда Яндекс.Браузера участвует в разработке Chromium уже не первый год. И это касается не только исправления ошибок. В свое время коллеги помогли с реализацией server push для HTTP/2 и со сборочной системой проекта для Windows. Поэтому и в этот раз я предложил решение проблемы и отправил на рассмотрение готовый коммит, который после небольшого обсуждения был принят.

Разработчикам из Chromium, так же как и мне, было интересно взглянуть на изменения в плане производительности браузера. Поэтому они прогнали целый комплекс performance-тестов. Практически все показатели для Windows подросли. Часть низкоуровневых тестов и вовсе показала улучшения в 2-3 раза. Интегральный FPS-тест для ключевых сайтов (иными словами, повседневный веб-сёрфинг) вырос на 6,5%. Отзывчивость на ввод улучшилась на 20-30%. В проекте Chromium далеко не каждый день случается оптимизация подобного уровня.



Учитывая, что VS 2010 уже давно не используется, я предложил попробовать включить оптимизацию по скорости для всего проекта. Тем более что обычная release-сборка (без флага official) всегда оптимизировалась по скорости целиком, и с тестированием никогда проблем не было. Но это уже совсем другая история.

P.S. Эта отдельно взятая проблема затронула лишь два офиса. На самом деле разработка Яндекс.Браузера ведется не только в Москве и Новосибирске. У нас есть команды еще и в Санкт-Петербурге, Екатеринбурге, Нижнем Новгороде, Минске и Киеве. И если вам было бы интересно к ним присоединиться, то заглядывайте на yandex.ru/jobs.

Комментарии (42)


  1. c4simba
    19.02.2016 16:16

    Если бага была в отрисовке слоев, то почему она не проявлялась в отрисовке других полупрозрачных компонентов?


    1. Wedmer
      19.02.2016 16:53

      Это уже вопрос к msvc2013, как я понял.


    1. Lof
      19.02.2016 17:14
      +8

      Не совсем понятно про какие компоненты идет речь.
      Но если предположить, что про что-то простое типа css свойства opacity, то тут надо немного рассказать как хром рисует веб-странички.
      Предположим, что у вас есть страничка с двумя статичными полупрозрачными изображениями.
      Хром ( а реально WebKit ) заранее просчитает финальную картинку, которая получается при смешивании этих двух статичных картинок и браузер уже нарисует финальную картинку.
      Однако, если что-то поменяется, надо будет перерисовать опять всю финальную картинку.
      Чтобы этого избежать, придумали слои. JS разработчики могут на это явно влиять через will-change свойство css. А раньше для этого часто использовали transform z = 0.
      В этом случае, WebKit отдаст браузеру две картинки, и он их смешает сам. С помощью skia.
      В первом случае бага не будет. Потому что там будет использован код WebKit'а.
      Во втором случае баг может быть.

      А еще может все дело в том, что использовался не подверженный багу тип смешивания. Некоторые работали нормально :)


      1. Fedcomp
        19.02.2016 22:52
        -4

        На самом деле уже не webkit а blink.


        1. Wedmer
          20.02.2016 13:23
          +1

          Вы код хорошо изучили, прежде чем такое писать?


          1. kos32
            23.02.2016 15:58
            +2

            не читая код, можно объяснить, почему все ссылаются на WebKit, если форк произошел почти 3 года назад?


            1. Gorthauer87
              24.02.2016 12:11
              +1

              Думаю потому, что большая часть кода там всё-равно осталась вебкитовская.


  1. oshibka404
    19.02.2016 17:10
    +17

    Если бы Агата Кристи жила в наше время, она писала бы такие детективы.


  1. Jeditobe
    19.02.2016 17:20
    +2

    В какой версии хрома этот фикс есть?


    1. BarakAdama
      19.02.2016 17:27
      +2

      Фикс попал в Chromium 47.


      1. luethus
        20.02.2016 13:48
        +3

        Товарищи! Как раз начиная с 47, в Chromium сломан (по крайней мере на OS X) прозрачный режим, то есть рендеринг в прозрачном окне (alphaEnabled). Он используется в nwjs, Electron и Chrome Apps для создания прозрачных окон. Пример глючного рендеринга есть здесь: github.com/atom/electron/issues/4157
        Так вот, может это вы нечаянно сломали? Мы на работе страдаем очень из-за этого.


        1. Smerig
          20.02.2016 15:15

          Это только Windows зацепило, не?


          1. luethus
            20.02.2016 15:24

            Возможно. Может ребята в курсе еще каких-нибудь изменений, связанных с этим всем.


        1. BarakAdama
          20.02.2016 16:41
          +3

          Нет, OS X не трогали, потому что там нет Visual Studio. Но баги в Chromium появляются и так регулярно, поэтому скорее совпадение.


  1. QtRoS
    19.02.2016 17:47

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

    P.S. А то, что в 2013 году имея такие бенчмарки взяли и прошляпили — это, безусловно, странно!
    P.S.S. Невольно порадовался на тему того, что "твой самый странный баг все равно не переплюнули" :)


  1. citius
    19.02.2016 18:18

    Станет ли мак-версия быстрее, как и виндовая?


    1. BarakAdama
      19.02.2016 18:28
      +1

      Нет, это только Windows зацепило.


  1. DjOnline
    19.02.2016 19:52
    +1

    Есть ли ещё простор для ускорения? Например вручную переписать на Asm этот кусок кода, добавить SSE2-4/AVX, как это делают в x264 например. Попробовать скопимилировать с помощью Intel C Compiler.


    1. CRImier
      19.02.2016 22:36

      Intel C Compiler

      Не надо. Потом ещё будут говорить, что Chrome на AMD тормозит.


    1. Lof
      20.02.2016 11:22
      +2

      Он уже на SSE (xmm это SSE регистр). А вообще у них уже все эти реализации есть: https://code.google.com/p/chromium/codesearch#chromium/src/third_party/skia/src/core/SkOpts.cpp&l=108


  1. u007
    19.02.2016 20:05

    Чтобы обойти проблему, разработчики решили пожертвовать скоростью? Это как понимать?


    1. Antelle
      20.02.2016 00:17

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


    1. Lof
      20.02.2016 11:26
      +3

      На самом деле, у них есть аргументы за оптимизацию по размеру кода.
      1) Размер бинарника увеличивается на 25%, а значит пользователям больше качать.
      2) Меньший по размеру код более cache-friendly.
      В общем, они пока решили все не оптимизировать по скорости. Можешь с ними поспорить вот тут https://bugs.chromium.org/p/chromium/issues/detail?id=548457 :)


  1. SOLON7
    19.02.2016 22:13
    -2

    Как бы было замечательно если бы был оффис в Алмате!!!


  1. mejedi
    19.02.2016 23:44
    +1

    Верно ли, что слои объединяются софтварно (не GPU)?


    1. Lof
      20.02.2016 11:28
      +1

      Корректный ответ — когда как. В данном случае — да.


  1. pavel_pimenov
    20.02.2016 03:43
    +1

    Если не секрет, расскажите почему разработчики на своей локальной машине уже используют VC++2015
    а на билд-сервере сборка релиза идет на VC++ 2013?


    1. BarakAdama
      20.02.2016 07:45

      Chromium тоже постепенно переходит на 2015 версию.


    1. fregate
      20.02.2016 07:48

      Вот, например, изза таких ошибок. Можно собирать и в 2010, а писать в 2015.
      Если у вас большая кодобаза, то оттестировать все на уровне чуть ли каждого байта — очень тяжелая задача.


  1. alexstz
    20.02.2016 08:16

    Такой пространный вопрос и немножко не в тему, но может знаете, почему на Mac (со встроенной графикой) Chrome работает заметно менее плавно, чем родной Safari? Что за хитрые оптимизации заставляют работать дефолтный браузер так быстро и непрожорливо (хотя насчёт последнего начиная с какой-то версии Chrome уже не так актуально)?


    1. Lof
      20.02.2016 11:34
      +3

      Боюсь, chrome всегда будет отставать от safari. Дело в том, что chrome имеет универсальную архитектуру, которая работает на множестве платформ.
      Safari же заточен под OS X. Не даром они не смогли поддерживать его под Windows и закрыли проект.
      Но есть одна настройка, которая увеличивает плавность скроллинга в chrome. Надо зайти на chrome://flags и включить GPU rasterization для всех слоев:

      image


      1. alexstz
        20.02.2016 12:19

        Спасибо, попробую!


      1. shamanchik
        23.02.2016 21:41
        +1

        Chrome Версия 48.0.2564.116 (64-bit), MBP MD313, El Capitan
        После включения стала тормозить прокрутка ленты ВКонтакте


  1. ALPINE63rus
    20.02.2016 09:26
    +1

    Я сейчас схлопочу минусов, но у меня остались вопросы:

    Всему виной была неправильная кодогенерация в VS 2013.
    Что, и всё? А подробности про вырождение той ошибочно-переоптимизированной функции? А вы писали разработчикам VS 2013 о проблеме? Уверены, что в VS 2015 бага уже нет? А может он только у вас перестал проявляться?

    Вообще, краткое содержание статьи мне показалось вот таким: "Мы искали баг в коде, но оказалось, что у VS 2013 кривой компилятор. А ещё мы выставили /O2 и порадовались, что скорость возросла. Конец."


    1. Biblusha
      20.02.2016 10:09

      ещё "Приходите к нам работать"


    1. BarakAdama
      20.02.2016 11:14
      +3

      Почему VS ведёт себя именно так, ответить смогут только её разработчики. Баг с неправильной кодогенерацией функции SrcATop при разных целях для оптимизации был исправлен в 2015, потому что он пропал не только у нас, но и для всего Chromium (в том числе для иных ситуаций с блендингом).


    1. Mixim333
      20.02.2016 16:18

      Писал в VS2013 свой сериализатор, запустил в Debug-версии, подсунул файл в ~200Мб, через пару минут он успешно отработал, результат был ровно таким, какой я ожидал; собрал тот же самый код в Release-версии, дал тот же самый файл, посмотрел результат, а он кардинально отличается от того, что должно быть — WTF — снова перехожу в Debug и снова все отлично (условную компиляцию и прочие трюки не использую). Немного позже собрал этот же код в VS2015 и в Debug и в Release версиях — все отлично отработало! Так и не смог понять, что это был за баг

      Так что такие проблемы случаются и на не очень больших и запутанных проектах


  1. krak
    21.02.2016 12:19
    +1

    Ребята, сделайте уже в Linux версии поддержку H.264. Chrome (из коробки), Vivaldi (с помощью доп.модулей) позволяют смотреть. А я, как дурак, пользуюсь ЯБ, а видео смотрю в другом. Служба поддержки только следующими релизами кормят.
    Может хоть тут до разработчиков достучусь?
    На всякий случай Ticket#15121617250381325.


    1. BarakAdama
      22.02.2016 11:34
      +1

      Спрошу у коллег. Спасибо за номер тикета.


  1. darkfrei
    23.02.2016 22:22
    +1

    А когда окончательно откажутся от флеша и закопают его подальше?


    1. Tairesh
      24.02.2016 07:38
      +1

      Сразу после вендекапца, видимо.


  1. Gorthauer87
    24.02.2016 12:14
    +1

    Интересно, а кто-нибудь пробовал собирать при помощи gcc под windows? Насколько браузер хуже/лучше работать начинает?