Visual Studio Code отнимает 13% ресурсов CPU из-за мерцания курсора
Забавная проблема #22900 на этой неделе привлекла особое внимание пользователей Github.
Подробное описание проблемы — в репозитории редактора кода Visual Studio Code (vscode). Open source разработчик Джо Лисс (Jo Liss) известна как создатель Broccoli и других свободных библиотек. На странице проекта она обратила внимание, что Visual Studio Code использует 13% вычислительных ресурсов процессора, если окно находится в фокусе. Из-за этого впустую расходуется заряд аккумулятора на ноутбуке. Что могло бы быть причиной столь странного поведения программы?
Джо Лисс предположила, что активность CPU связана с рендерингом мерцания курсора — изменение состояния курсора происходит два раза в секунду, то есть каждые 500 мс (2 fps).
Для воспроизведения проблемы следует проделать следующие шаги:
- Закрыть все окна Visual Studio Code.
- Открыть новое окно (File → New Window).
- Открыть новую вкладку с пустым файлом (File → New Tab). Курсор мигает.
- В мониторе ресурсов вы увидите ненулевое потребление вычислительных ресурсов (13% на слабом ноутбуке с OS X, около 5–7% на мощном GNOME Shell с Wayland (Ivy Bridge Graphics)).
- Переключиться на окно другого приложения (Cmd+Tab). Курсор больше не виден.
- Потребление CPU программой Visual Studio Code снижается практически до нуля.
Ему кому-то нужно, вот таймлайн записи в Developer Tools: TimelineRawData-20170321T114212.json.zip (см. скриншот выше).
Если приблизить на один фрейм, то можно заметить, что несмотря на частоту мерцания 2 fps, основной поток выполняет некую работу на 60 fps, то есть рендерит что-то каждые 16 мс.
Если ещё больше приблизить, то становится видна конкретная большая работа, которую выполняет рендеринг курсора на 60 кадрах/с. Это периодические циклы «Update Layer Tree» / «Paint» / «Composite Layers», то есть обновление дерева слоёв
Разработчик обращает внимание, что в других приложениях 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 сразу снизилось в несколько раз.