
Всем привет! Меня зовут Илья Кара́псин, я работаю над производительностью Яндекс Браузера. Задачи моей команды включают не только работу над самим браузером, но и прямое улучшение используемых в нём опенсорс‑решений, например Chromium и применяемых в нём проектов (Blink, V8, Catapult), в том числе и компиляторов (LLVM Clang). Другими словами, мы вносим вклад в сообщество. При этом поиск и создание улучшений для сторонних опенсорс‑проектов может стать прямой рабочей задачей даже в ходе стажировки.
К слову, моя работа в Браузере как раз с неё и началась. При отборе положительно сказалось наличие у меня pet‑проектов: отдельного внимания заслужил проект, использующий озвучку и SFX из Heroes of Might and Magic V. Так удивительно совпало, что мой будущий руководитель ранее работал в Nival Interactive как раз над этой игрой.
А в этой статье я расскажу о том, как обычное сокращение полных файловых путей в логах до имени файла (например, path/to/filename → filename) может повлиять на размер исполняемых файлов и производительность Браузера, а также внести вклад в проект LLVM Clang.
Влияние размера файлов на производительность
При запуске приложения — будь то Браузер или что‑то ещё — в ОЗУ загружаются разные файлы, необходимые для работы. Загрузка каждого занимает время, которое зависит от скорости чтения и размера самого файла. Поэтому чем больше места занимают необходимые файлы, тем дольше будет идти старт приложения.
Это влияет на всех пользователей, но на пользователей компьютеров без SSD больше всего. По результатам наших исследований у 27% всех пользователей Яндекс Браузера нет SSD, а это значительная доля аудитории.
Также от размера файлов зависит размер обновлений, а они могут приходить весьма часто, поэтому негативные эффекты (лишние трафик, чтение, запись и прочее) от избыточного размера файлов умножаются во много раз.
Следовательно, важно не увеличивать ключевые файлы без необходимости.
В случае Яндекс Браузера один из ключевых файлов при загрузке — browser.dll. Он достаточно крупный — занимает ≈270 MiB — и грузится при каждом запуске Браузера. Поэтому в команде Браузера и в Chromium стараются уменьшать dll, если от этого не будет ухудшений.
Текст в dll
В dll содержится много различной информации, необходимой для работы программы. Например, текст в интерфейсе программы или логах приложения как раз может формироваться из строк текста, которые находятся в dll.
Незадолго до начала моей работы в команде Браузера обнаружили, что в dll содержится заметная часть строк текста, из которых формируются сообщения браузерных логов. А они используются даже в релизной версии у обычных пользователей.
Для создания сообщений логов используются соответствующие им строковые литералы, заранее записанные в dll: например, соответствующий логу путь до файла с кодом или другой константный текст, которым зачастую является сообщение о том, что при работе программы произошла ошибка.
Для единообразия дальнейшего текста в файловых путях будет написан разделитель пути /
, но замечу, что в Windows для разделения пути по умолчанию используется \
, поэтому в dll фактически будет содержаться вариант с \
в путях.
Пример влияния логирования в абстрактном файле path/to/file.cc на содержимое browser.dll
path/to/file.cc:
// В Chromium для логирования используются макросы LOG и FROM_HERE, которые
// автоматически подставляют в лог строку пути до файла, в котором применили макрос.
// Подробнее об этих макросах будет рассказано далее в статье.
LOG(ERROR) << “MyLog”; // Используются строки path/to/file.cc (от макроса LOG) и MyLog.
PostTask(..., FROM_HERE); // Используется строка path/to/file.cc (от макроса FROM_HERE).
Текст от path/to/file.cc в browser.dll:
…path/to/file.cc…MyLog…
Как увидеть влияние логирования в реальных файлах на содержимое dll?
Найти и скопировать имя файла из кодовой базы Chromium, использующего
LOG(ERROR)
илиFROM_HERE
. Например,FROM_HERE
применяется в ui/views/widget/widget.cc.Открыть browser.dll (у Яндекс Браузера) или chrome.dll (у Chromium или Chrome), например, с помощью стандартного Windows‑Блокнота, и сделать поиск по имени найденного ранее файла. Если файл не найдётся в dll, то, скорее всего, он не используется в вашей версии браузера, надо поискать другой файл.
На момент написания статьи в chrome.dll будет находиться полный файловый путь, а в browser.dll только имя файла.
Так влияет логирование в ui/views/widget/widget.cc на dll:

Можно заметить, что из‑за логов в dll пишутся полные файловые пути. Отсюда возникает вопрос: а зачем нам вообще нужен полный путь, если можно обойтись только именем?
Сокращение полного пути до имени
Я решил изучить поставленный вопрос и если возможно, то сократить полные пути до имён везде, где это допустимо. Предполагалось, что это будет простой задачей, но за простым изменением может таиться заметное улучшение, как, например, было после переименования Яндекс Браузера в Chrome.
Полные файловые пути в Chromium используются в макросах логирования — LOG
и FROM_HERE
. Изучим вопрос сокращения путей для каждого макроса отдельно. Дальнейшие рассуждения будут касаться компилятора Clang, но в основном они будут верны и для других компиляторов.
LOG
В Chromium и браузерах на его основе для создания логов применяется макрос LOG, внутри которого используется полный путь до файла. Однако по факту в коде из полного пути нужно только имя файла — в полном пути нет необходимости.
По коду для получения файлового пути используется предопределённый макрос __FILE__
. Чтобы получить только имя файла, достаточно использовать макрос __FILE_NAME__
.
К несчастью, такая простая и быстрая замена увеличила dll, но не изменила скорость старта. Dll увеличилась, потому что из‑за одновременного использования обоих макросов начали записываться две разных строки — имя файла для LOG
и полный путь файла для FROM_HERE
. Так суммарный размер используемых строк стал больше — отсюда и увеличение dll.
Пример влияния логирования в абстрактном файле path/to/file.cc на содержимое browser.dll
path/to/file.cc:
LOG(ERROR) << “MyLog”; // Используются строки file.cc (от макроса LOG) и MyLog.
PostTask(..., FROM_HERE); // Используется строка path/to/file.cc (от макроса FROM_HERE).
Текст от path/to/file.cc в browser.dll:
…file.cc…path/to/file.cc…MyLog…
Поэтому для получения выигрыша необходимо сократить путь до имени в оставшемся макросе — FROM_HERE
.
FROM_HERE
Макрос FROM_HERE
— это аналог std::source_location::current()
. Он обычно используется для логирования мест в коде, из которых публикуют асинхронный вызов. Полный путь в макросе формально нужен для того, чтобы однозначно определить файл, из которого шёл вызов. Однако в команде Браузера ни у кого не возникало сложностей с точным определением места вызова, потому что его можно однозначно найти по строке и имени функции, которые тоже записаны в FROM_HERE
.
Поэтому я со спокойной душой приступил к сокращению полного пути до имени файла.
В макросе FROM_HERE
для получения полного пути в точке вызова используется макрос __builtin_FILE()
.
Разница между __FILE__ и __builtin_FILE()
__FILE__
подставляет путь до файла, в котором написали этот макрос. Например, если этот макрос будет написан параметром по умолчанию в функции по пути base/logging.h
, то этот параметр будет всегда равен по умолчанию “base/logging.h”
:
base/logging.h:
const char* GetSimpleFILE(const char* simple_file = __FILE__) {
return simple_file;
}
// По умолчанию simple_file всегда будет равен “base/logging.h”,
// даже если вызвать GetSimpleFILE() в совершенно другом месте,
// например в base/example.h или prog.cc.
std::cout << GetSimpleFILE(); // “base/logging.h”
base/example.h:
std::cout << GetSimpleFILE(); // “base/logging.h”
prog.cc:
std::cout << GetSimpleFILE(); // “base/logging.h”
__builtin_FILE()
подставляет путь до файла в точке вызова макроса, а не в точке его написания. Например, если этот макрос будет написан параметром по умолчанию в функции (не важно, по какому пути), то его значение будет зависеть от файла, в котором позовут эту функцию. То есть при вызове в base/example.h
значение макроса будет “base/example.h”
, в prog.cc
— “prog.cc”
и так далее
base/logging.h:
const char* GetBuiltinFILE(const char* builtin_file = __builtin_FILE()) {
return builtin_file;
}
std::cout << GetBuiltinFILE(); // “base/logging.h”
base/example.h:
std::cout << GetBuiltinFILE(); // “base/example.h”
prog.cc:
std::cout << GetBuiltinFILE(); // “prog.cc”
Наглядный пример: код и его результат.
Ниже — таблица значений аргумента по умолчанию для функции, написанной в base/logging.h
, в зависимости от макроса, который используется для значения по умолчанию, и точки (файла) вызова этой функции.

Казалось бы, для использования только имени вместо полного пути можно просто заменить __builtin_FILE()
макросом __builtin_FILE_NAME()
(по аналогии с заменой __FILE__
на __FILE_NAME__
) — и задача будет решена. Я тоже так думал и сделал замену, запустил компиляцию… и вот что увидел:
error: use of undeclared identifier '__builtin_FILE_NAME'
__builtin_FILE_NAME();
^
1 error generated.
Нужного макроса на тот момент (а это было в марте 2023) просто не существовало в Clang.
Кому‑то может показаться, что здесь уже не получится использовать только имя файла, так как компилятор не даёт инструментов для этого. А начинающему разработчику будет не под силу изменить компилятор, который, похоже, создают гораздо более продвинутые люди. Задачу уже собирались забрать у меня и передать ведущим разработчикам Яндекса, но…
…я обратил внимание, что Clang — это опенсорс‑проект. А значит, у любого человека есть доступ к исходному коду проекта, и туда можно предлагать свои изменения. Поэтому я вызвался полностью довести дело до конца. Пока рано сдаваться, ещё не всё потеряно — просто пришло время коммитить в Clang!
Коммит в Clang
Вначале мне казалось, что раз Clang компилирует C/C++, то этот компилятор будет написан на другом языке — скорее всего, на Assembler. К нему мне прикасаться совершенно не хотелось, потому что мой опыт взаимодействия с ним был болью и мучениями. С опаской я открыл исходный код Clang и увидел… код на C/C++! Это значительно облегчило мне задачу.
В ходе изучения реализации похожих макросов (__FILE__
, __FILE_NAME__
, __builtin_FILE()
) стало ясно, что для создания __builtin_FILE_NAME()
принципиально новый код писать не придётся, потому что:
Ближайший ориентир по реализации —
__FILE_NAME__
— получается сокращением результата__FILE__
до имени. То есть сокращение полного пути до имени уже написано в реализации макроса.Следовательно, для создания макроса
__builtin_FILE_NAME()
достаточно скопировать реализацию__builtin_FILE()
и с̶к̶о̶п̶и̶п̶а̶с̶т̶и̶т̶ь̶ добавить туда уже написанное ранее сокращение до имени.
Мой Pull Request с новым макросом был отправлен ревьюерам Clang на рассмотрение. Они предложили некоторые корректировки, которые я учёл и внёс. После этого мою реализацию одобрили и влили в main‑ветку. И начиная с релиза Clang 17.0.1 в C/C++ стало возможно использовать __builtin_FILE_NAME()
, а макрос появился в документации Clang.
К слову, в GCC даже завели feature‑request на добавление __builtin_FILE_NAME()
, чтобы не отставать от Clang. А в феврале 2025 кто‑то повторно попросил добавить __builtin_FILE_NAME()
в GCC. Похоже, потребность в новом макросе возникла и в других проектах.
Когда нужная версия Clang дошла до Браузера, то сокращение макроса FROM_HERE
с помощью __builtin_FILE_NAME()
успешно состоялось.
Подведём итоги
Такое небольшое изменение смогло заметно уменьшить dll без негативных эффектов, а такими маленькими шагами возможно достичь большой цели. А если ещё несколько неравнодушных людей найдут и применят свой простой способ уменьшения dll, то его размер станет гораздо меньше.
Ещё благодаря поставленной задаче мне удалось сделать свой вклад в улучшение Clang — востребованный компилятор С/C++ и крупный опенсорс‑проект. И благодаря этому появилась возможность использовать новый полезный макрос __builtin_FILE_NAME()
.
Что касается меня, то внесённый в опенсорс вклад не остался без внимания. Описанная в статье работа была сделана в ходе стажировки, что оказало крайне положительное влияние на её итоги: мне предложили работу в штате сразу на уровне middle‑разработчика, что бывает редко (в большинстве случаев стажёров берут на junior‑позиции). Считаю это хорошим знаком, поэтому продолжу искать возможности для улучшения не только продуктов компании, но и сторонних проектов с открытым кодом.
Эта история показывает, что даже простая задача может стать отправной точкой для внесения значимого вклада в крупные проекты. Речь не всегда идёт о масштабных изменениях: иногда именно тщательные, продуманные оптимизации в сумме дают значительные улучшения — не сразу, но с течением времени.
В статье и её оформлении используются отрывки из кода Chromium // Copyright 2012 The Chromium Authors
S-trace
Как-то тема производительности вообще не раскрыта, хотя в заголовке упомянута.
Да и тема размера тоже, то что dll уменьшится от такого было и так нетрудно догадаться, интереснее было бы конкретные показатели увидеть.
А время сборки как-то изменилось?