Назад в Центр знаний
Отпечатки

Как hardwareConcurrency раскрывает реальные ядра вашего CPU

Как браузеры сообщают число ядер CPU через navigator.hardwareConcurrency и проверки с Worker, и что может раскрыть этот сигнал.

BotBrowser Team

Документация

Нужна поддерживаемая продуктовая документация?

У этой статьи есть соответствующая страница в центре документации. Используйте docs для каноничного сценария настройки, актуальных флагов и долгосрочной справки.

navigator.hardwareConcurrency это всего одно число, но оно относится к тем аппаратным подсказкам, которые любой скрипт может прочитать без запроса разрешения. Оно сообщает, сколько логических процессоров может использовать браузер, и доступно в основном потоке и во всех типах воркеров. Сайт, который хочет оценить согласованность описания устройства, может сравнить это число с другими аппаратными значениями и с тем, как на практике ведёт себя параллельная работа. Разделы ниже рассматривают, что означает значение, почему оно различается на разных машинах, как сильно помогает идентификации и каким требованиям должно отвечать согласованное число ядер, взятое из профиля. BotBrowser может сообщать одно и то же число ядер профиля согласованно во всех контекстах контролируемого теста, но не меняет, сколько ядер на самом деле у хост-машины и как сайт использует это значение.

Схема: число ядер профиля сообщается основному потоку, Worker, SharedWorker и ServiceWorker, а параллельное масштабирование согласовано в Linux и Windows

Смотреть видео

Это видео на английском проверяет четыре утверждения о сообщаемом числе ядер на контролируемой лабораторной странице: чтение в консоли, контексты Worker, измеренный тест масштабирования на одном хосте Ubuntu и то, что статья говорит о профиле. Оно показывает две машины, а не выборку пользователей.

Краткое резюме

  • navigator.hardwareConcurrency сообщает логические процессоры, а не гарантированную производительность.
  • Виртуальные машины, контейнеры, политика браузера и гибридные CPU могут изменить сообщаемое значение.
  • Сверяйте значение в основном потоке и воркерах с платформой и памятью профиля.
  • BotBrowser сохраняет согласованным значение из профиля; Linux и Windows также могут выровнять привязку Worker к CPU.

Содержание руководства

Что сообщает navigator.hardwareConcurrency

navigator.hardwareConcurrency может отражать гостевую систему, ограниченную 2 vCPU, а не число процессоров её хоста.

Свойство возвращает целое число логических процессоров, доступных для запуска потоков. Логические процессоры это физические ядра, умноженные на число аппаратных потоков на ядро, поэтому четырёхъядерный процессор с одновременной многопоточностью сообщает 8. MDN описывает свойство как число логических процессоров, доступных браузеру, а спецификация HTML от WHATWG добавляет, что пользовательскому агенту следует склоняться к раскрытию реального числа и использовать меньшее значение только там, где есть ограничение или где цель состоит в снижении фингерпринтинга.

// Основной поток
console.log(navigator.hardwareConcurrency);
// Внутри выделенного воркера, общего воркера или сервис-воркера
console.log(self.navigator.hardwareConcurrency);

Это число служит подсказкой для планирования, а не измерением скорости. Оно не говорит, насколько быстро работает ядро, являются ли ядра энергоэффективными или производительными, насколько загружена машина в данный момент и сколько потоков операционная система реально запланирует для страницы. Две машины, которые сообщают 8, могут вести себя совершенно по-разному при одной и той же параллельной нагрузке.

Приложения читают это значение по разумным причинам, например чтобы выбрать размер пула воркеров, поэтому браузеры не могут просто убрать его, не сломав работающий код. Кроме того, оно не требует разрешения и доступно всем типам воркеров. Это сочетание объясняет, почему число остаётся видимым и почему практической целью становится согласованность между контекстами, а не удаление.

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

Почему число различается на разных машинах

В Linux контейнер может предоставить браузеру выделение cpuset.cpus в 2 vCPU вместо общего числа 16-core у хоста.

Несколько факторов определяют, что сообщает браузер.

  • Модель процессора. Число ядер и потоков определяется CPU. Многие распространённые настольные процессоры сообщают 8, 12 или 16 логических процессоров, а старые и недорогие машины могут сообщать 2 или 4. Apple silicon и другие гибридные конструкции сочетают энергоэффективные и производительные ядра, а итог зависит от чипа.
  • Виртуальные машины. Виртуальная машина сообщает виртуальные процессоры, выделенные гипервизором. Хост с 16 ядрами, который отдаёт гостю 2 виртуальных процессора, показывает внутри гостя 2, независимо от оборудования под ним.
  • Контейнеры и ограничения операционной системы. Среды контейнеров и настройки планировщика могут ограничивать число процессоров, которые разрешено использовать процессу. Увидит ли браузер такое ограничение, зависит от того, как оно применено.
  • Политика браузера. Спецификация позволяет пользовательскому агенту сообщать меньшее значение, когда он вводит ограничения или хочет снизить фингерпринтинг, и некоторые браузеры экспериментировали с ограничением сверху или группировкой числа. Chromium сейчас сообщает число, которое показывает операционная система.

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

Типичные значения следуют за рынком оборудования. На настольных компьютерах чаще всего встречаются 4 и 8, а 6, 12 и 16 появляются у отдельных семейств процессоров. Выше 16 это в основном рабочие станции и серверы. Телефоны обычно сообщают 4, 6 или 8, в зависимости от конструкции чипа. Нечётные числа вроде 7 или 13 редки среди выпускаемых процессоров, и это становится важным, когда кто-то думает выбрать число вручную.

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

Как сигнал помогает идентификации

Исследования фингерпринтинга измеряют, насколько каждый атрибут сужает популяцию. Данные AmIUnique оценивают navigator.hardwareConcurrency примерно в 4,5 бита энтропии, что эквивалентно разделению примерно на 23 равновероятные группы. Реальное распределение далеко от равномерного: несколько частых значений охватывают большинство браузеров, а остальные редки, поэтому практическое разделение грубее, чем подсказывает эта цифра.

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

Редкое значение сужает ещё сильнее. Сообщение о 24, 32 или 64 ядрах наводит на мысль о рабочей станции или сервере, что нечасто встречается среди обычных пользователей браузера. Число ничего не говорит о человеке, но может сделать сессию заметнее среди других, которые сообщают 4 или 8.

Мобильные устройства добавляют ещё один слой. Некоторые семейства чипов связывают конкретное число с другими свойствами, например со строкой платформы и экраном. Телефон, сообщающий 8 вместе с определённым шаблоном пользовательского агента, сужает устройство до короткого списка моделей, а это больше информации, чем даёт каждое из значений по отдельности. Та же логика действует для настольных компьютеров: число, частое само по себе, становится более заметным в сочетании с необычным значением памяти или платформой.

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

Эти числа повод для осторожности, а не приговор. Браузер, сообщающий частое число, не защищён от других сигналов, а браузер с необычным числом не отмечается автоматически. Число это один вход среди многих, и его вес зависит от того, что ещё видит страница.

Как число ядер перепроверяется и почему ручные изменения не срабатывают

Сравнение начинается с navigator.hardwareConcurrency и может включать значение, возвращённое Worker, созданным на той же странице.

Помимо самого числа, сайт может спросить, подходит ли оно. Когда сообщается число ядер, важны три вида согласованности.

  1. Правдоподобие по отношению к другим значениям. Число ядер должно иметь смысл вместе с платформой, пользовательским агентом и подсказкой об объёме памяти устройства. Два ядра при 8 ГБ памяти описывают обычный небольшой ноутбук или виртуальную машину. Сообщение о 64 ядрах рядом с пользовательским агентом телефона описывает оборудование, которого не существует.
  2. Согласованность между контекстами. Основной поток, выделенные воркеры, общие воркеры и сервис-воркеры должны сообщать одно значение. Один скрипт страницы может прочитать их все, поэтому расхождение между контекстами легко заметить.
  3. Согласованность с параллельным поведением. Скрипт мог бы запустить одну и ту же задачу на растущем числе Worker и посмотреть, где время выполнения перестаёт улучшаться. Машина с 4 логическими процессорами выходит на плато около четырёх Worker, а машина с 16 продолжает улучшаться дольше.

Пары, которые стоит проверять, вполне конкретны. Пользовательский агент настольной Windows с 8 или 16 логическими процессорами и 8 ГБ сообщаемой памяти встречается часто. Платформа Linux x86_64 с 4 ядрами указывает на обычное настольное или серверное оборудование, а строка платформы ARM с 8 ядрами наводит на телефон или конкретный сервер ARM. Ни одна из этих пар сама по себе ничего не доказывает; каждая лишь не должна противоречить остальному описанию.

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

Третья проверка скорее возможность, чем рутинное измерение. Браузеры снижают точность таймеров, а инструменты измерения с разделяемой памятью, такие как SharedArrayBuffer, требуют страницы, изолированной между источниками, с заголовками COOP и COEP. Эти требования ограничивают точность, с которой страница может измерять масштабирование, поэтому проверку стоит считать ещё одной причиной держать сообщаемое число и реальное поведение согласованными, а не гарантированным результатом.

Как конкретный сайт взвешивает эти проверки, снаружи наблюдать нельзя. Это цели согласованности, а не предсказания того, к какому выводу придёт определённый сайт.

Когда число ядер неверно, расхождение появляется в фиксированном порядке. Сначала смотрят на основной поток, потому что внедрённый скрипт страницы обычно меняет только эту область. Выделенный воркер, созданный из URL Blob, идёт вторым: он занимает три строки и выявляет переопределение, которое не дошло до областей воркеров. Общий воркер и сервис-воркер следуют дальше, потому что требуют больше подготовки, но тоже читают свойство из собственной глобальной области. Память устройства и строка платформы идут последними как проверки правдоподобия числа, на котором сошлись четыре контекста.

Проверяющий может пройти этот порядок как четыре чтения. Запишите navigator.hardwareConcurrency на странице, затем self.navigator.hardwareConcurrency в выделенном воркере, общем воркере и сервис-воркере и сравните четыре числа. Прочитайте подсказку об объёме памяти устройства и строку платформы в той же сессии, чтобы судить число рядом с остальным описанием. Запуск масштабирования, описанный ниже, это пятый проход, и для него нужен хост Linux или Windows.

Совпадающий результат скучен: все четыре контекста возвращают 4, память устройства показывает 8, строка платформы совпадает с профилем, а бенчмарк перестаёт улучшаться около четырёх Worker. Несовпадение читается так же легко. Страница, которая возвращает 4, тогда как выделенный воркер возвращает 32, указывает на переопределение уровня страницы, а не на оборудование. Четыре совпадающих контекста с числом, противоречащим платформе или подсказке о памяти, указывают на профиль, изменённый изолированно, а совпадающие контексты, которые всё равно масштабируются дальше заявленного числа, указывают на отсутствие привязки к ядрам CPU в macOS.

Перезапись Navigator.prototype.hardwareConcurrency на странице не обновляет WorkerNavigator.hardwareConcurrency в отдельной глобальной области воркера.

Обычный способ изменить значение состоит в перезаписи свойства из расширения или внедрённого скрипта. Это меняет число, которое возвращает основной поток, и оставляет три пробела.

  • Несоответствие масштабирования. Если свойство говорит 4, а у машины 16 ядер, параллельная работа завершается быстрее, чем позволила бы машина с 4 ядрами. Утверждение и поведение расходятся, как только кто-то измеряет.
  • Пробелы между контекстами. Переопределение, применённое только к объекту navigator страницы, не доходит до областей воркеров, если его не внедрить и туда, поэтому воркер всё ещё может вернуть реальное значение.
  • Неполное описание устройства. Новое число, не согласованное с пользовательским агентом, платформой и подсказкой о памяти, может им противоречить.

Случайные значения усугубляют третий пробел. Число 7 или 13 редко совпадает с выпускаемым оборудованием. Даже фиксированное частое значение вроде 8 противоречит пользовательскому агенту, который называет телефон с 4-ядерным чипом. Свойство, изменённое изолированно, настолько надёжно, насколько надёжно самое слабое из окружающих его значений.

Пробел между контекстами проще всего найти из трёх. Тестовая страница может создать выделенный воркер из URL Blob, отправить значение обратно и сравнить его со значением основного потока в несколько строк. Общий воркер и сервис-воркер требуют чуть больше подготовки, но сравнение то же, и любое различие это дефект переопределения, а не свойство оборудования.

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

Как проверить число ядер, взятое из профиля

Первая проверка записывает navigator.hardwareConcurrency в основном потоке и в каждом Worker.

Настройка на основе профиля берёт число из полного профиля устройства, а не из переопределения, заданного вручную. Отдельной опции для числа ядер нет, потому что значение берётся из профиля. Четыре проверки показывают, согласуется ли результат.

  • Значение. Прочитайте свойство и убедитесь, что оно равно числу ядер профиля, а не хоста.
  • Контексты. Прочитайте его внутри выделенного воркера, общего воркера и сервис-воркера и убедитесь, что каждый совпадает с основным потоком.
  • Правдоподобие. Убедитесь, что число реалистично для платформы, пользовательского агента и памяти устройства профиля.
  • Стабильность. Прочитайте его в нескольких сессиях с одним и тем же профилем и убедитесь, что оно никогда не меняется.
ПроверкаГде читатьОжидаемый результат с профилем на 4 ядра
Значение в основном потокеnavigator.hardwareConcurrency на странице4, а не 32 хоста
Значение в воркереself.navigator.hardwareConcurrency в выделенном воркере4, совпадает с основным потоком
Общий воркер и сервис-воркерто же свойство в каждой глобальной области4 в обоих, без пробела между контекстами
Правдоподобиечисло рядом с подсказками о платформе и памяти4 подходит мобильному профилю, 32 нет
const source = 'postMessage(self.navigator.hardwareConcurrency)';
const blob = new Blob([source], { type: 'text/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
worker.onmessage = event => console.log('worker:', event.data, 'main:', navigator.hardwareConcurrency);

Если проверка не проходит, обычных причин немного.

  • Значение показывает число ядер хоста. Профиль не загружен или не содержит ожидаемого числа ядер. Значение берётся из профиля, поэтому сначала проверьте файл профиля.
  • Параллельная задача по-прежнему быстрая в macOS. macOS не предоставляет интерфейс привязки к ядрам CPU, который используют Linux и Windows, поэтому Worker работают на всех ядрах. Сообщаемое значение по-прежнему берётся из профиля, но параллельное время может с ним не совпадать. Используйте Linux или Windows, когда важно поведение масштабирования.
  • Worker кажутся медленными. Это ожидаемо, когда профиль заявляет меньше ядер, чем есть у хоста, потому что Worker намеренно ограничиваются заявленным числом.

Масштабирование это пятая проверка, и она зависит от платформы. На хосте с большим числом ядер, чем заявляет профиль, параллельная задача, распределённая на большее число Worker, чем заявленное число, должна перестать ускоряться около этого числа. Когда несколько экземпляров браузера делят один хост, каждый экземпляр ограничивается независимо своим профилем, поэтому профиль с 4 ядрами и профиль с 8 ядрами на одном сервере показывают разные кривые масштабирования, соответствующие их заявлениям.

Практический пример показывает эффект. Сборочный сервер с 32 ядрами запускает мобильный профиль, который заявляет 4 ядра. Профиль сообщает 4 во всех контекстах, а Worker ограничены четырьмя физическими ядрами, поэтому параллельный бенчмарк с 8 Worker перестаёт ускоряться около четырёх Worker и показывает кривую масштабирования 4-ядерной машины, а не ту, которую мог бы дать хост. Абсолютная скорость по-прежнему зависит от производительности ядра хоста, которая обычно выше, чем у телефона.

Тот же хост с 32 ядрами может одновременно обслуживать профиль на 4 ядра в нескольких контекстах браузера. Откройте три или четыре страницы с одним профилем, прочитайте navigator.hardwareConcurrency на каждой странице и откройте воркер с каждой страницы, чтобы прочитать там self.navigator.hardwareConcurrency. Каждая страница и каждый воркер должны возвращать 4, тогда как хост, прочитанный вне контролируемого браузера, возвращает 32. Если какой-то контекст возвращает 32, этот контекст создан без профиля, а не профилем, поэтому сравните, как запускался каждый контекст, прежде чем винить профиль.

Хорошие привычки делают результат предсказуемым. Используйте полный профиль вместо перезаписи одного свойства, выбирайте профиль, число ядер которого подходит его платформе и памяти, всегда проверяйте контексты воркеров и не подгоняйте число вручную. Если сообщаемое число нужно изменить, меняйте профиль, потому что остальные значения устройства относятся к тому же описанию. Подробности есть в руководстве о защите свойств navigator, в руководстве по API памяти устройства, в руководстве по планированию воркеров и в обзоре фингерпринтинга браузера.

Храните короткую письменную запись каждой проверки, чтобы позднее изменение можно было сравнить с ней: использованный профиль, платформа хоста, значение, прочитанное в основном потоке и в каждом типе воркера, и результат масштабирования в Linux или Windows. Если запись называет также версию браузера и версию профиля, легко понять, откуда взялось позднейшее различие: из профиля, из браузера или с хоста.

BotBrowser может сохранять согласованным значение navigator.hardwareConcurrency из профиля в основном потоке и в воркерах, а в Linux и Windows применяет привязку потоков Worker к ядрам CPU, чтобы масштабирование параллельных бенчмарков соответствовало заявленному числу ядер. Это помогает, когда более мощный хост запускает профиль с меньшим числом ядер, потому что сообщаемое число и масштабирование параллельной работы остаются в согласии. BotBrowser не может добавить реальные ядра CPU или изменить оборудование хоста, не ограничивает Worker привязкой к ядрам CPU в macOS, где параллельное время может не совпадать с профилем, и не гарантирует, как тот или иной сайт истолкует сигнал.

Практическое заключение

Считайте сообщаемое число ограниченным входом для планирования. Читайте его в каждом контексте воркера, сравнивайте с платформой профиля и используйте Linux или Windows, когда в тест входит привязка к CPU. Меняйте профиль, а не исправляйте одно свойство вручную.

Открытые источники

#Cpu#fingerprinting#hardwareConcurrency#приватность

Эта статья входит в руководство: Что такое отпечаток браузера?

Переведите BotBrowser из исследований в продакшн

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