Visual Studio Code отнимает 13% ресурсов CPU из-за мерцания курсора

3dc3830f6313207fe36950c4401543da.png

Забавная проблема #22900 на этой неделе привлекла особое внимание пользователей Github.

Подробное описание проблемы — в репозитории редактора кода Visual Studio Code (vscode). Open source разработчик Джо Лисс (Jo Liss) известна как создатель Broccoli и других свободных библиотек. На странице проекта она обратила внимание, что Visual Studio Code использует 13% вычислительных ресурсов процессора, если окно находится в фокусе. Из-за этого впустую расходуется заряд аккумулятора на ноутбуке. Что могло бы быть причиной столь странного поведения программы?
Джо Лисс предположила, что активность CPU связана с рендерингом мерцания курсора — изменение состояния курсора происходит два раза в секунду, то есть каждые 500 мс (2 fps).

Для воспроизведения проблемы следует проделать следующие шаги:

  1. Закрыть все окна Visual Studio Code.
  2. Открыть новое окно (File → New Window).
  3. Открыть новую вкладку с пустым файлом (File → New Tab). Курсор мигает.
  4. В мониторе ресурсов вы увидите ненулевое потребление вычислительных ресурсов (13% на слабом ноутбуке с OS X, около 5–7% на мощном GNOME Shell с Wayland (Ivy Bridge Graphics)).
  5. Переключиться на окно другого приложения (Cmd+Tab). Курсор больше не виден.
  6. Потребление CPU программой Visual Studio Code снижается практически до нуля.


Ему кому-то нужно, вот таймлайн записи в Developer Tools: TimelineRawData-20170321T114212.json.zip (см. скриншот выше).

Если приблизить на один фрейм, то можно заметить, что несмотря на частоту мерцания 2 fps, основной поток выполняет некую работу на 60 fps, то есть рендерит что-то каждые 16 мс.

4f95407680850a6bce4d375e3f93ddb6.png

Если ещё больше приблизить, то становится видна конкретная большая работа, которую выполняет рендеринг курсора на 60 кадрах/с. Это периодические циклы «Update Layer Tree» / «Paint» / «Composite Layers», то есть обновление дерева слоёв

b01b71cde2c01ac92c37ee9c1ecae11e.png

Разработчик обращает внимание, что в других приложениях macOS Sierra 10.12.3, в том числе Chrome и TextEdit, курсор мерцает без заметного потребления ресурсов CPU.

Пользователи редактора Visual Studio Code могут отключить мерцание курсора в программе. В этом случае потребление CPU снижается до 0%. Циклы «Update Layer Tree» / «Paint» / «Composite Layers» всё равно работают, но только каждые 500 мс, а не каждые 16 мс.

"editor.cursorBlinking": "solid"

Этот забавный глюк в Visual Studio Code напоминает классическую проблему с тормозным индикатором в npm. В версии npm 3.5.2 при включенном индикаторе хода выполнения операции эта операция выполнялась примерно на 50% медленнее, чем без индикатора.

$ rm -r node_modules
$ npm set progress=false
$ time npm install
npm install 19.91s user 2.66s system 71% cpu 31.667 total

$ rm -r node_modules
$ npm set progress=true
$ time npm install
npm install 33.26s user 3.19s system 74% cpu 48.733 total


Конечно, потребление ресурсов CPU при мерцании курсора имеет совсем иные причины, чем замедление npm с активным индикатором прогресса. О причинах проблем с курсором можно догадаться, если посмотреть на почти такой же баг с анимацией ключевого кадра CSS в браузере Chrome. Там разработчики пишут, что в JavaScript мерцающий курсор отнимает нормальные 1,2% ресурсов CPU, а в CSS почему-то в 6 раз больше, то есть 7–8%.

Код вроде корректный:

@keyframes monaco-cursor-blink {
        50% {
                opacity: 0;
        }
        100% {
                opacity: 1;
        }
}

.cursor-blink {
        animation: monaco-cursor-blink 1s step-start 0s infinite;
}


Но проблема в том, что движок Chromium принудительно переводит эту анимацию в 60 fps, заставляя выполнять работу каждые 16 мс.

Так вот, редактор Visual Studio Code, очевидно, использует самый логичный подход для реализации функции мерцающего курсора: это функция step с анимацией ключевого кадра CSS. А этот баг в Chromium до сих пор не исправили полностью, хотя он тянется уже больше двух лет. Так что Chrome осуществляет полный цикл рендеринга каждые 16 мс, как и положено для 60 кадров в секунду. Возможно, теперешнее обсуждение позволит привлечь внимание к старому багу — и у разработчиков наконец-то дойдут руки до него.

Разработчики Visual Studio Code признались, что изначально эта функция была реализована на JavaScript, но примерно год назад они переключились на CSS. В текущей реализации если окно не в фокусе, то анимация деактивируется и излишнего потребления ресурсов процессора не происходит, но вот с активным окном действительно проблема. Разработчики считают, что в такой ситуации есть смысл вернуться с CSS обратно на JS.

Коллеги советуют подумать также над тем, чтобы реализовать курсор в виде анимированного gif’а. Можно генерировать такой файл автоматически, в зависимости от цветового оформления редактора. Правда, здесь может быть сложность с зуммированием: всё-таки растровая графика станет расплывчатой при зуммировании.

Но в конце концов разработчики из Microsoft всё-таки решили вернуться на старый добрый JS-метод setInterval для мерцания курсора — и потребление CPU сразу снизилось в несколько раз.

© Geektimes