7.9 KiB
Попытки оптимизации
В этом файле фиксируются попытки оптимизации, которые не дали результата, чтобы не повторять их в будущем. Либо дали, но спор не достаточный.
1. Time-based yield при сканировании
В performCacheScanAsync добавлен yield по времени: каждые N итераций проверять performance.now() - lastYieldTime и если > 15ms — отдавать управление через setTimeout.
Результат: Не устранило violation 'setTimeout' handler took Nms. Накладные расходы на performance.now() и setTimeout замедлили сканирование с ~3.3с до ~4.2с.
2. Yield по итерациям (50, 100, 200)
В performCacheScanAsync добавлен yield каждые N итераций (пробовали 50, 100, 200, 500).
Результат: Violation оставался (первые итерации до первого yield выполняются синхронно). Overhead от частых yield замедлял сканирование.
3. Кеширование parametersToJson в computeCardState
prepareCommonArgs вызывается для каждой карточки. parametersToJson(settings.parameters) сериализует одни и те же параметры 5000 раз. Добавлены опциональные parametersJson и nowStr в TS-функции, кеширование в performCacheScanAsync.
Результат: Выигрыш < 10ms на 5000 вызовов. parametersToJson и now.toISOString() настолько быстрые, что оверхед от условной логики перевешивает профит. Откачено.
4. Батчевый computeCardsState (первая версия)
Добавлена Rust-функция compute_cards_state, которая принимает JSON-массив карточек и возвращает JSON-массив состояний. На TS — computeCardsState, в performCacheScanAsync — один вызов на пачку.
Первая версия Rust: парсила массив в Vec<serde_json::Value>, для каждого элемента вызывала compute_current_state (которая парсит JSON заново). Двойная сериализация.
Результат: Сканирование не ускорилось (3.39 → 3.63 с) из-за двойной сериализации в Rust. Загрузка таблицы ускорилась (2.16 → 0.87 с), но это случайная вариативность — рендеринг таблицы не использует computeCardsState.
5. Батчевый computeCardsState (вторая версия, без двойной сериализации)
Та же идея, но в Rust выделена внутренняя функция compute_card_state_inner, работающая с уже распарсенными CardData и FsrsParameters. compute_cards_state парсит массив один раз и вызывает inner для каждой карточки без промежуточной сериализации.
Результат: Сканирование 3.30-3.32 с — без изменений относительно baseline (3.3 с). Bottleneck не в FFI/сериализации, а в 105k итерациях цикла по файлам (проверка metadataCache).
6. Чтение metadataCache из внутреннего Map вместо getFileCache
Вместо 105k вызовов app.metadataCache.getFileCache(file) — читать из (app.metadataCache as any).metadataCache (внутренний Map<string, CachedMetadata>).
Результат: Первая попытка — каст в Map (сломало).
Вторая — каст в Record, доступ по [file.path] (105k без frontmatter).
Третья — Object.entries, фильтр .md, итерация по entries (0 карточек).
Внутренняя структура metadataCache не соответствует публичному API getFileCache. Откачено.
Вывод: Оптимизация через обход внутренней структуры metadataCache ненадёжна — формат ключей/значений может отличаться в разных версиях Obsidian.
7. Сохранение кэша карточек между запусками (cache.json)
Идея: после первого полного сканирования сохранять кэш (cache.json, ~8.5 МБ) и при следующих запусках загружать из него, избегая повторного обхода 105k файлов.
Реализация:
- При первом запросе таблицы — полное сканирование + сохранение
cache.json - При повторном запуске — загрузка из кэша (0.83 с вместо 3.30 с)
params_hash— при изменении параметров FSRS кэш сбрасываетсяmtime— для отслеживания устаревших записей- Ленивая загрузка: кэш читается только при открытии fsrs-table
- Debounce-сохранение (2 с) при изменении файлов
Результат: Таблица доступна мгновенно при повторных запусках. Минификация JSON могла бы снизить размер до ~1.5–2 МБ.
Почему отклонено:
- Создаёт сложность и неопределённость (устаревание, инвалидация, гонки)
- Вероятны сбои при частых обновлениях плагина (структура кэша может меняться)
- При умеренном количестве карточек (< 7000) накладные расходы на запись/чтение ощутимы, а выигрыш незначителен
- Эффект проявляется только на очень больших хранилищах
Вывод: Метод не однозначный. Может иметь смысл только при значительном (> 7000) количестве карточек. А может и не иметь. ОТКЛОНЕНО.
8. Unix-таймстемп вместо ISO 8601
Замена строковых дат ("2026-01-15T10:30:00Z") на целые числа (1736935800) в reviews[].date и due. Убирает parse_datetime_flexible (парсинг 6 форматов), заменяет на DateTime::from_timestamp. Сокращает длину JSON-строк.
Результат: Сканирование 3.30 → 3.37 с (в пределах погрешности). Парсинг дат не был узким местом — основные затраты в обходе metadataCache и setTimeout между пачками. НЕ ПРИНЯТО.
9. opt-level = 3 (скорость) против opt-level = "s" (размер)
Сравнение после замены regex на regex-lite, с wasm-opt -Oz, на 100 карточках с WHERE ~, ORDER BY, LIMIT.
| opt-level | WASM | query (ms) | queryCount (ms) |
|---|---|---|---|
"s" (размер) |
612 KB | 0.696 | 0.632 |
3 (скорость) |
784 KB | 0.688 | 0.631 |
| Δ | +28% | -1.2% | -0.2% |
Результат: Разница в скорости в пределах погрешности (~1%), а размер +172 KB. Оставлен opt-level = "s".