Как получить микросекундные тайминги в JavaScript после Spectre и Meltdown

Ситуация

При написании высокопроизводительного кода JavaScript стандартных инструментов профилирования, предлагаемых Chrome и др., Не всегда достаточно. Кажется, что они предлагают только детализацию на уровне функций, и поиск нужной информации может занять довольно много времени.

В .NET класс StopWatch дает мне именно то, что мне нужно: субмикросекундное разрешение произвольных фрагментов кода.

Для JavaScript performance.now() был довольно хорошим способом измерения производительности, но в ответ на Spectre и Meltdown все основные браузеры снизили разрешение даже до миллисекунды.

Чтобы процитировать MDN на performance.now ():

The timestamp is not actually high-resolution. To mitigate security threats such as Spectre, browsers currently round the result to varying degrees. (Firefox started rounding to 2 milliseconds in Firefox 59.) Some browsers may also slightly randomize the timestamp. The precision may improve again in future releases; browser developers are still investigating these timing attacks and how best to mitigate them.

Проблема

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

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

Существующие обходные пути

На ум приходят два варианта:

  1. Установите старую версию браузера, в которой не реализованы эти меры защиты.

Например, мне пришлось бы посвятить старую версию FireFox тестированию, а новую версию Chrome - браузеру. Это непрактично, так как мне нужно тестировать во всех браузерах (а также, желательно, во всех браузерах). Кроме того, новые оптимизации не реализованы в старых браузерах, поэтому тесты могут оказаться бесполезными.

  1. Реализуйте собственный таймер с помощью WebWorkers

Я видел различные старые сообщения в блогах по этому поводу, но ни одно из них, похоже, не достигло той высокой точности, которая мне нужна (в конце концов, для этого раньше был performance.now()).

Вопрос

Как мне получить эффективную версию до Spectre performance.now(), не прибегая к более старым версиям браузеров, виртуальным машинам и тому подобному?

Существуют ли в JavaScript какие-либо методы кодирования или библиотеки, обеспечивающие точность до микросекунд?

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

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

Чтобы дать хорошую рекомендацию: зачем вам этот точный тайминг? А вам нужны точные метки времени или точность интервалы?

Mike 'Pomax' Kamermans 01.05.2018 15:46

Учитывая то, что вы описываете, звучит как довольно низкоуровневые тайминги, по-видимому, это не полностью зависит от работы в браузере (где доступ к DOM, выполнение ajax и т.д. будет на порядки медленнее, чем любой запущенный код) - не могли бы вы вместо этого работать в среда node.js? У вас там process.hrtime.

James Thorpe 01.05.2018 15:47

@ Mike'Pomax'Kamermans Интервалы. Проще говоря, мне нужен «секундомер», который может сказать мне, сколько времени потребовалось для выполнения определенного фрагмента кода, и после попытки оптимизировать этот фрагмент кода сравнить два времени, чтобы увидеть, улучшила ли моя оптимизация производительность или нет.

Fred Kleuver 01.05.2018 15:56

Можете ли вы запустить код в цикле без того, чтобы оптимизатор JIT делал что-то, что разрушает ваш микробенчмарк?

Peter Cordes 01.05.2018 15:59

@JamesThorpe Ну и да, и нет. Я хочу измерить производительность чистого JavaScript, поэтому он не зависит от работы в браузере, но мне нужно знать, как он выполняет в браузере. NodeJS предоставит мне только метрики для Chrome, предположительно

Fred Kleuver 01.05.2018 16:00

@PeterCordes Выполнение тысяч итераций в цикле - это то, как я сейчас делаю свои тесты, но JIT-оптимизация здесь дает некоторую неопределенность. Кроме того, само зацикливание делает довольно утомительным выполнение тестов, так как мне приходится рефакторировать и изолировать код и эффективно делать его другим, просто чтобы иметь возможность измерить. В идеале я просто хочу отбросить некоторые точки измерения в существующем коде без необходимости все перепрограммировать,

Fred Kleuver 01.05.2018 16:03
Существуют ли в JavaScript какие-либо методы кодирования или библиотеки, обеспечивающие точность до микросекунд? Если бы это было так, это была бы уязвимость в текущем механизме защиты от Spectre. (AFAIK, вы не можете легко использовать Meltdown из JS, просто Spectre. И патчи ядра Meltdown полностью блокируют его, в то время как патчи ядра Spectre только защищают ядро и, возможно, процессы из других процессов; они по-прежнему оставляют виртуальные машины песочницы уязвимыми для гостевого кода в тот же процесс. Приведение в действие побочных каналов, основанных на времени, в основном необходимо для защиты внутренних компонентов браузера от гостевого кода JS.)
Peter Cordes 01.05.2018 16:04

Я думаю, что лучше всего поискать где-нибудь ручку, которая повторно включает высокоточную синхронизацию в современных браузерах; по-видимому, они сделали возможным использование таких сценариев, как ваш. Не знаю как, но думаю, это возможно. Однако вам, возможно, придется создать свой собственный Firefox и Chromium из исходного кода с отключенной этой опцией. В основном вы заботитесь об использовании этого в своем собственном браузере на одной или нескольких машинах, потому что это используется для тестирования.

Peter Cordes 01.05.2018 16:06

@PeterCordes Справочная точка. Но скажем, например, если бы у вас был бесконечный цикл, работающий в веб-воркере, просто подсчитывающий (и полностью используя ядро ​​ЦП в процессе), и вы бы спросили его текущее количество в двух разных моментах, это потенциально все равно будет полезной информацией. . Я просто не знаю точно, как это сделать (и возможно ли это с самого начала - не уверен в накладных расходах postMessage и т. д.)

Fred Kleuver 01.05.2018 16:08

@PeterCordes Да, я как бы ищу ручку, просто говорю, что я был бы открыт для других вариантов, если бы они существовали. Создание firefox и хрома из исходников - на самом деле довольно умная идея, я разберусь с ней.

Fred Kleuver 01.05.2018 16:09

Интересно; этот взлом цикла, надеюсь, даст вам точность лучше, чем мс, но, вероятно, с множеством микросекунд накладных расходов. Наверное, недостаточно для надежных атак Spectre. Есть ли в JS общие атомарные переменные, которые вы можете читать из одного потока, в то время как другой атомарно их увеличивает? Или зацикленный поток должен получить сообщение и ответить на него? (Я действительно не знаю javascript, просто процессоры / низкоуровневые вещи; я здесь для тегов [benchmarking] и [performance]: P)

Peter Cordes 01.05.2018 16:12

Это также немного похоже на оптимизацию неправильной вещи: синхронизация отдельных фрагментов кода, когда полный кодовый путь уже установлен, или менее 2 мс предполагает, что у вас уже есть код кода, в котором оптимизация будет -эффективно-нерелевантной. Чтобы оптимизировать эти фрагменты кода, вы действительно больше обращаете внимание на анализ сложности, а не на анализ времени выполнения, так что ввод увеличенной длины не увеличивается с 2 мс до 2 секунд до 2000 лет на два порядка. И если вы используете имеют для их оптимизации, у вас есть код, для которого подойдет Node.js.

Mike 'Pomax' Kamermans 01.05.2018 16:13

@PeterCordes JavaScript не выполняет потоки, все концептуально происходит в цикле сообщений в одном потоке - в то время как у вас есть подобные сервис-воркеры (которые можно рассматривать как потоки), вы должны публиковать сообщения между ними - нет гарантия того, как быстро это сообщение будет видно

James Thorpe 01.05.2018 16:14

@ Mike'Pomax'Kamermans: 2 мс - это около 8 миллионов тактовых циклов ЦП на ЦП с тактовой частотой 4 ГГц. Это во много тысяч раз дольше, чем окно не по порядку (224 мопса) на Skylake, даже при выполнении кода с небольшим количеством инструкций за такт. Это также достаточно долго, чтобы вы, вероятно, могли с пользой провести микробенчмаркинг чего-то, что вы могли бы использовать как часть цикла, без того, чтобы что-то полностью искажалось из-за того, что оно настолько мало, что обычно складывается в часть более крупной операции. (т.е. мы не говорим здесь о ++ x vs. x ++ или о других бессмысленных вещах, не глядя на вывод JIT asm.)

Peter Cordes 01.05.2018 16:18

@ Mike'Pomax'Kamermans Один из примеров того, что я хочу протестировать, - это конкретные горячие пути в парсере. Проверка того, что оператор switch со 100 вариантами может быть на несколько% быстрее, чем оператор switch с только 20 вариантами и 2-3 if-elses. Оптимизация сильно отличается в Chrome, FireFox и Edge. Иногда в Chrome его нельзя оптимизировать дальше, но в FireFox это возможно. Помимо этого низкоуровневого примера, я ищу также более высокоуровневые тесты, которые все еще находятся в пределах доли миллисекунды.

Fred Kleuver 01.05.2018 16:18

@FredKleuver: Да, как гуру asm, это звучит разумно, чтобы попытаться профилировать / микробенчмаркинг таким образом, за исключением того, что ветвь микробенчмаркинга, которая может ошибочно предсказывать, - это жесткий. Современные предикторы ветвления легко обучаются, и создать такую ​​же непредсказуемость, как и в вашем реальном сценарии использования, сложно. (И простое включение его в цикл искажает это.) Может быть, немного на малой стороне, особенно если он превращается в таблицу переходов. (Эээ, если случаи не являются целыми числами, как они должны быть в C, а вместо этого совпадают строки или что-то в этом роде, это немного больше и даже лучше подходит для микростендинга.)

Peter Cordes 01.05.2018 16:23

@PeterCordes Интересно. Имея это в виду, результаты также будут отличаться от процессора к процессору, и я могу только зайти так далеко. Всегда будут ошибки, и это вполне приемлемо. Несомненно, микросекундная и миллисекундная точность отсчета времени - вот слабый плод здесь, поэтому я сосредоточен именно на этом.

Fred Kleuver 01.05.2018 16:28

Для одного оператора switch вам может потребоваться наносекундная точность, чтобы получить полезные результаты. Некоторая беспорядочная многопоточность, которую вы взламываете, не будет стоить хлопот по сравнению с реализацией JS, чтобы напрямую дать вам время высокого разрешения. Для браузеров с открытым исходным кодом это возможно определенно, это может потребовать немного работы. Относительная производительность одного и того же ассемблера x86 может быть разной на разном оборудовании, поэтому вам не нужен метод синхронизации, который также зависит от процессора, что делает невозможным определение того, что к чему. (Но остерегайтесь вариаций частоты процессора в режиме турбо / энергосбережения в любом случае)

Peter Cordes 01.05.2018 16:34

@PeterCordes Спасибо за полезную информацию. Я сейчас смотрю, как собрать Chromium :) Если я как-то разберусь с этим для Chrome и FF, я отправлю это как ответ. Надеюсь, что кто-то еще придет тем временем с существующим решением, но это, вероятно, принятие желаемого за действительное, ха

Fred Kleuver 01.05.2018 16:38

Я бы поискал коммиты, которые внесли это изменение, и посмотрел, есть ли в нем возможность отключить его в настройках конфигурации Firefox, у которых нет пункта меню графического интерфейса пользователя, просто похороненного где-то в about: settings. Или на самом деле я бы просто сначала посмотрел эти настройки и, возможно, погуглил, как отключить ограничение точности. Вероятно, вы не первый, кто этого захочет.

Peter Cordes 01.05.2018 16:45

Да, вы бы так сказали, но я повсюду искал в Google, чтобы отключить ограничение точности - полное отсутствие результатов - вот что заставило меня задать этот вопрос. Я думаю, что нашел фиксацию в хроме: github.com/chromium/chromium/commit/… - мне кажется, что это довольно жестко запрограммировано.

Fred Kleuver 01.05.2018 17:12

@FredKleuver: Да, нет никаких признаков того, что API времени обходит TimeClamper::ClampTimeResolution. Но если есть, вы не обязательно увидите это в том патче, потому что до этого уже было ограничение в 5 мкс. Во всяком случае, в источнике выглядит легко отключить. Замена static constexpr double kResolutionSeconds = 0.0001 на 1.0 будет работать, или для снижения накладных расходов, сделав TimeClamper::ClampTimeResolution бездействующим или исключив вызовы к нему. (И, кстати, floor(time_seconds / kResolutionSeconds) был бы более эффективным, чем floor(time_seconds * (1.0/kResolutionSeconds)), чтобы избежать разделения времени выполнения.

Peter Cordes 02.05.2018 02:18

У вас действительно есть ботаник снайпер, да :) Спасибо большое, очень полезная информация! Я еще не смог собрать хром (ни один крупный продукт ОС не удалось собрать с первого раза в Windows), так что хороших новостей с моей стороны пока нет.

Fred Kleuver 02.05.2018 12:52
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
10
23
1 791
2

Ответы 2

В Firefox есть параметр конфигурации privacy.reduceTimerPrecision, который отключает защиту от Spectre. Вы можете переключить его на false, используя страницу Firefox about: config (введите about:config в адресную строку).

Разобрался с подсказкой на MDN.

Я читал об этой опции в FireFox еще тогда, когда отправлял этот вопрос, но в то время он, похоже, еще не реализован. Хотя он сейчас там. Я должен отметить, что переключение, которое на самом деле не отключает полностью подавление Spectre, оно просто ограничивает его до 20 мкс, что и было смягчением в v59. Все еще намного лучше, чем ограничение по умолчанию на 1-2 мс, но не совсем те 5 мкс, которые были раньше.

Fred Kleuver 01.07.2018 00:27

Начиная с Firefox 79, вы можете использовать таймеры с высоким разрешением, если заставите ваш сервер отправлять два заголовка с вашей страницей:

Starting with Firefox 79, high resolution timers can be used if you cross-origin isolate your document using the Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

These headers ensure a top-level document does not share a browsing context group with cross-origin documents. COOP process-isolates your document and potential attackers can't access to your global object if they were opening it in a popup, preventing a set of cross-origin attacks dubbed XS-Leaks.

Ref: https://developer.mozilla.org/en-US/docs/Web/API/Performance/now

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

(() => {
  const start = performance.now();
  let diff;
  while ((diff = performance.now() - start) === 0);
  return diff;
})();

Это возвращает 0,02 или значение, очень близкое к 0,02 с этими заголовками, и 1 без них.

Другие вопросы по теме