Сегодня мы расскажем вам историю об одном интересном баге в Яндекс.Браузере, исправление которого привело к значительному ускорению отрисовки во всем проекте 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.
Один не совсем обычный баг
Меня зовут Кирилл, я работаю в группе внутренних компонентов Яндекс.Браузера в Новосибирске. В один не совсем прекрасный день коллеги из тестирования Яндекс.Браузера воспроизвели проблему с проигрыванием видео через 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.