Битрикс: получить путь файла

В битриксе так устроено, что каждый загружаемый файл (картинка, документ или что-угодно ещё) сохраняется и на диске, и в базе данных (основные параметры файла). В базе данных параметры файлов хранятся в таблице 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 с осторожностью - всегда проверять, нет ли более быстрого варианта.

Комментарии

Популярные сообщения из этого блога

Пропорциональное распределение суммы

Битрикс: своя геолокация

Битрикс: два способа отправить файл