- Опыт выявления и устранения ненужных операций привязки данных посредством измерения времени выполнения -
1. Проблемная ситуация
В проекте мы реализуем бизнес-экран Excel на основе React и TypeScript, используя SpreadJS (v17.0.1), чтобы файлы Excel можно было просматривать и редактировать в веб-браузере. Структура сохраняет загруженный пользователем файл Excel в качестве шаблона, а на экране сведений подключает фактические данные к этому шаблону и отображает их.
Проблема заключалась в чрезмерно длительном времени первоначальной загрузки при переходе на экран сведений Excel. При открытии экрана Excel отображался не сразу. Сначала появлялся пустой экран, затем экран загрузки, и только спустя некоторое время отображался экран Excel. Измерения в среде разработки показали, что общее время загрузки составляло примерно 25–30 секунд. Используемый файл Excel содержал 28 листов и имел относительно сложную структуру, включая ссылки между листами и формулы.
Я хотел бы поделиться процессом поиска и устранения узкого места: шаг за шагом измеряя, какая именно часть этого сложного процесса загрузки занимала время.
2. Проверка файла Excel и диапазонов строк/столбцов
Сначала я проверил размер файла Excel, а также диапазоны строк и столбцов на его листах. Ранее я уже устранял проблему производительности в SpreadJS, при которой неоправданно большие диапазоны строк и столбцов, выходящие за пределы фактических данных, влияли на производительность загрузки, поэтому сначала проверил, не была ли и на этот раз задействована та же причина.
(※ Предыдущий пример улучшения производительности, связанный с диапазонами строк и столбцов, можно найти https://www.nextree.io/spreadjs-egsel-roding-seongneung-coejeoghwa/ здесь.)
В предыдущем случае фактические данные занимали всего около 100 строк, но диапазон строк внутреннего листа Excel был установлен на максимальное значение — 1 048 576 строк. Поскольку независимо от фактических данных требовалось обработать большой диапазон, время загрузки увеличивалось. Производительность удалось улучшить за счёт уменьшения неоправданно большого настроенного диапазона.
На этот раз после проверки диапазонов строк и столбцов Excel я также обнаружил, что диапазоны некоторых листов были значительно больше фактических данных. Поскольку проект был настроен на использование входных диапазонов Excel до 1 000 строк и 1 000 столбцов, я провёл тест, ограничив диапазоны этими значениями и сохранив файл. В результате размер данных SJS уменьшился следующим образом.
- До изменения: примерно 5,35 МБ (5 357 864 байта)
- После изменения: примерно 4,67 МБ (4 675 857 байт)
Однако, хотя размер файла уменьшился, время загрузки существенно не сократилось. Поэтому я пришёл к выводу, что основной причиной этой проблемы были не просто размер файла Excel или диапазоны строк и столбцов. Ранее производительность удалось улучшить за счёт уменьшения ненужных диапазонов непосредственно в Excel. На этот раз требовалось изучить операции, выполняемые на экране после загрузки Excel.
3. Измерение времени выполнения каждого этапа загрузки
Если причиной не являлся сам файл, необходимо было определить, какая именно операция занимала время. Поэтому я разделил процесс отображения Excel на экране на несколько этапов и измерил время их выполнения. Основные результаты измерений были следующими.
|
Операция |
Затраченное время |
|---|---|
|
workbook.open |
Примерно 2,3 секунды |
|
Формирование информации BindingPath |
Примерно 25 мс |
|
Обработка данных BindingPath |
Примерно 1 мс |
|
Создание CellBindingSource |
Примерно 2 мс |
|
setDataSource |
Примерно 18 секунд |
Вопреки ожиданиям, `workbook.open()` — операция, которая фактически считывает файл Excel, — занимала примерно 2,3 секунды, тогда как проверка BindingPath или последующая обработка данных занимали в основном не более нескольких десятков миллисекунд. В то же время выполнение `setDataSource()` занимало примерно 18 секунд. Это подтвердило, что основной причиной задержки загрузки было не считывание файла Excel, а подключение данных к листам после считывания Excel.
4. Проверка обработки привязки данных для всех листов
Чтобы более подробно изучить причину, я проверил существующий код. В существующей реализации `setDataSource()` выполнялся для всех листов.
const dataSource = new GC.Spread.Sheets.Bindings.CellBindingSource(data);
for (let i = 0; i < workbook.getSheetCount(); i++) {
const sheet = workbook.getSheet(i);
sheet.setDataSource(dataSource);
}
Этот файл Excel содержал 28 листов, но фактически привязка данных требовалась только для некоторых из них.
|
[Существующая структура] Перебор всех 28 листов -> Выполнение setDataSource() для каждого листа |
|---|
Затем я измерил время выполнения для каждого листа.
- Лист 1: примерно 1 021 мс
- Лист 2: примерно 836 мс
- Лист 3: примерно 805 мс
- Лист 4: примерно 812 мс
- Лист 5: примерно 783 мс
- Лист 6: примерно 771 мс
Обработка одного листа занимала примерно 0,7–0,9 секунды. Что ещё важнее, вызов `setDataSource()` занимал значительное время даже для листов, не имевших BindingPath для привязки. Поэтому я пришёл к выводу, что основной причиной проблемы было многократное выполнение одной и той же операции для листов, которым привязка не требовалась.
Однако почему сам вызов setDataSource() занимает время даже на листах без пути привязки, включая внутреннее поведение SpreadJS, я планирую отдельно исследовать позднее.
5. Выбор листов для привязки данных
Ранее `setDataSource()` выполнялся для каждого листа, но я изменил структуру так, чтобы выбирать и обрабатывать только те листы, которым действительно требуется привязка данных.
const dataSource = new GC.Spread.Sheets.Bindings.CellBindingSource(data);
for (let i = 0; i < workbook.getSheetCount(); i++) {
const sheet = workbook.getSheet(i);
if (!sheet.visible()) {
continue;
}
if (!isBindingTargetSheet(sheet)) {
continue;
}
sheet.setDataSource(dataSource);
}
- Скрытые листы, используемые другими листами только для формул и ссылок, также исключались из привязки данных, чтобы предотвратить ненужные вычисления.
- `isBindingTargetSheet()` проверяет наличие BindingPath на листе. Если найден хотя бы один BindingPath, лист считается целевым для привязки данных.
Обновлённая структура выглядит следующим образом.
|
[Улучшенная структура] Перебор всех листов -> Проверка необходимости привязки данных -> Выполнение setDataSource() только для необходимых листов |
|---|
6. Результаты улучшения времени загрузки
В существующей реализации общее время загрузки экрана сведений в тестовой среде составляло примерно 25–30 секунд. После изменения реализации таким образом, чтобы обрабатывать только листы, которым требуется привязка данных, время было сокращено примерно до 15–20 секунд.
|
[До улучшения] Примерно 25–30 секунд ↓ [После улучшения] Примерно 15–20 секунд (сокращение примерно на 40–50%) |
|---|
В тестовой среде мы подтвердили сокращение времени загрузки примерно на 40–50%. (※ Результаты могут отличаться в зависимости от характеристик ПК пользователя, среды браузера и структуры Excel.)
Хотя одно лишь уменьшение размера файла не привело к значительному сокращению задержки загрузки, нам удалось существенно улучшить ситуацию, уменьшив область задач, которые после загрузки выполнялись без необходимости.
7. Заключение
Это улучшение производительности стало возможным благодаря тому, что мы выявили узкое место, пошагово измерив фактический процесс загрузки, а не предположив, что причиной были размер файла или структура Excel.
Сначала, основываясь на предыдущем опыте, мы предположили, что причиной являются диапазоны строк и столбцов в Excel, однако уменьшение размера файла не привело к значительному сокращению времени загрузки. Затем мы измерили время выполнения каждого этапа процесса загрузки и обнаружили, что `setDataSource()` занимает значительно больше времени, чем `workbook.open()`. Измерив время для каждого листа, мы выяснили, что эта операция также выполнялась на листах, которым не требовалась привязка данных.
Благодаря этому улучшению нам удалось сократить начальное время загрузки в тестовой среде примерно с 25–30 до 15–20 секунд. Однако, поскольку мы определили, что итоговое время загрузки всё ещё можно улучшить, мы планируем расширить область оптимизации и включить в неё другие задачи, выполняемые после загрузки Excel.
При исследовании проблем с производительностью сложных экранов Excel мы подтвердили, что важно учитывать не только размер файла, но и измерять, какие задачи и сколько времени занимают в процессе фактической загрузки, а также сужать область выполнения так, чтобы выполнялись только необходимые задачи.
Справочные материалы
[Официальная документация по привязке данных SpreadJS]
https://developer.mescius.com/spreadjs/docs/v19/features/binding
JY