Битрикс: получить путь файла
В битриксе так устроено, что каждый загружаемый файл (картинка, документ или что-угодно ещё) сохраняется и на диске, и в базе данных (основные параметры файла). В базе данных параметры файлов хранятся в таблице b_file. Если в инфоблоке загрузить картинку, то у элемента инфоблока в поле картинки сохранится только идентификатор - отсылка на запись в таблице b_file. При выборке данных через CIBlockElement::GetList() и Fetch() от картинки будет получен только идентификатор. Чтобы идентификатор превратить в путь к файлу картинки, нужно, например, применить метод CFile::GetPath() и передать в него этот идентификатор файла.
Этот стандартный битриксовый метод внутри делает много всякого. В том числе, кеширование результата. Далеко не всегда это кеширование полезно, на мой взгляд. Очень часто файлы получаем внутри компонентов, которые сами имеют кеширование. Или при обработке большого числа файлов, выгодно один раз из кеша достать сразу всю пачку, чем каждый файл из отдельного маленького кеша. Кроме кеширования, там внутри ещё делаются другие малополезные вещи: выборка лишних данных из БД, обработка битриксовых событий.
Попробовал сделать свою реализацию этого метода. Но сначала сделал замеры быстродействия битриксового метода. Все замеры делались на PHP 8.2; перед каждым замером делал сброс кеша:
sudo systemctl restart memcached
Код такой: делаем выборку из БД 200 любых идентификаторов файлов, получаем путь каждого файла и выводим на экран путь. Замерялся такой код:
$arFiles = \Bitrix\Main\Application::getConnection()->query(
'SELECT ID FROM `b_file` WHERE MODULE_ID = "iblock" LIMIT 200'
);
foreach ($arFiles as $key => $arFile) {
$filePath = CFile::GetPath((int) $arFile['ID']);
echo '<pre>#' . $key . ' - ' . $filePath . '</pre>';
}
Вот такие показатели даёт Xhprof для стандартного битриксового метода:
Overall Summary | |
---|---|
Total Incl. Wall Time (microsec): | 101,412 microsecs |
Total Incl. CPU (microsecs): | 56,150 microsecs |
Total Incl. MemUse (bytes): | 375,080 bytes |
Total Incl. PeakMemUse (bytes): | 405,040 bytes |
Number of Function Calls: | 8,637 |
Далее была первая попытка реализации. С помощью битриксового ORM.
/**
* Аналог битриксового метода CFile::GetPath только оптимизировано быстродействие
* (не выбираются лишние данные из БД, не срабатывают событие OnGetFileSRC).
*
* @param int $fileId Идентификатор битриксового файла
*
* @return string|null Путь к файлу, если удалось найти
*/
public static function getPath(int $fileId): ?string
{
$result = null;
if ($fileId > 0) {
try {
$arFile = FileTable::getRow(
[
'select' => [
'ID',
new ExpressionField(
'PATH',
'CONCAT("/upload/", %s, "/", %s)',
['SUBDIR', 'FILE_NAME']
),
],
'filter' => ['=ID' => $fileId],
]
);
if (!empty($arFile['PATH'])) {
$result = $arFile['PATH'];
}
} catch (\Exception $exception) {
AddMessage2Log('Не найдены данные: ' . $exception->getMessage());
}
}
return $result;
}
Замеры быстродействия удивили.
Overall Summary | |
---|---|
Total Incl. Wall Time (microsec): | 260,449 microsecs |
Total Incl. CPU (microsecs): | 221,186 microsecs |
Total Incl. MemUse (bytes): | 686,176 bytes |
Total Incl. PeakMemUse (bytes): | 699,616 bytes |
Number of Function Calls: | 65,144 |
Количество вызванных функций многократно больше, чем при битриксовом методе! Как так?! Ведь в этой реализации метода в разы меньше всего делается. Картину портит битриксовый ORM... Это ещё один из полезных выводов статьи. "Облегчённый" метод стал работать в два раза дольше базового, и вызывать в 7,5 раз больше функций!
В следующем подходе к реализации альтернативного метода решено было не задействовать битриксовый ORM. Функция получилась такая:
/**
* Аналог битриксового метода CFile::GetPath только оптимизировано быстродействие
* (не выбираются лишние данные из БД, не срабатывают событие OnGetFileSRC).
*
* @param int $fileId Идентификатор битриксового файла
*
* @return string|null Путь к файлу, если удалось найти
*/
public static function getPath(int $fileId): ?string
{
$result = null;
if ($fileId > 0) {
try {
$result = Application::getConnection()->queryScalar(
'SELECT CONCAT("/upload/", `SUBDIR`, "/", `FILE_NAME`) FROM `b_file` WHERE `ID` = ' . $fileId
);
} catch (\Exception $exception) {
AddMessage2Log('Не найдены данные: ' . $exception->getMessage());
}
}
return $result;
}
На этот раз замеры быстродействия показали ожидаемый результат:
Overall Summary | |
---|---|
Total Incl. Wall Time (microsec): | 39,987 microsecs |
Total Incl. CPU (microsecs): | 16,476 microsecs |
Total Incl. MemUse (bytes): | 49,768 bytes |
Total Incl. PeakMemUse (bytes): | 52,112 bytes |
Number of Function Calls: | 3,831 |
Эта реализация в 2,3 раза меньше функций вызывает внутри себя. При этом быстрее в 2,5 раза, чем битриксовый метод. Важно заметить, что этот быстрый вариант получения пути к файлу - ничего не кеширует. Кеширование надо делать снаружи (если требуется). Битриксовый метод кеширует каждый раз и при последующем запуске битриксового замера, когда уже есть кеш, отрабатывает быстрее (что логично):
Overall Summary | |
---|---|
Total Incl. Wall Time (microsec): | 13,099 microsecs |
Total Incl. CPU (microsecs): | 12,823 microsecs |
Total Incl. MemUse (bytes): | 375,384 bytes |
Total Incl. PeakMemUse (bytes): | 378,928 bytes |
Number of Function Calls: | 3,692 |
Я для себя решил, что принудительное кеширование мне в этом методе приносит меньше пользы: в 90% случаев это вообще не требуется (просто получается лишнее кеширование внутри другого кеширования). Но хорошо, что теперь есть выбор между несколькими альтернативными реализациями одной и той же операции.
Второй важный вывод - работать с битриксовым ORM с осторожностью - всегда проверять, нет ли более быстрого варианта.
Комментарии
Отправить комментарий