Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions pjson_db_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ template <typename Visitor> inline void pjson_traverse_subtree( node_id id, Visi

vis.visit( id, v );

if ( v.is_array() )
// Единственный вызов tag() вместо отдельных is_array() + is_object() (Этап 10.3).
node_tag t = v.tag();
if ( t == node_tag::array )
{
uintptr_t sz = v.size();
for ( uintptr_t i = 0; i < sz; ++i )
Expand All @@ -121,7 +123,7 @@ template <typename Visitor> inline void pjson_traverse_subtree( node_id id, Visi
pjson_traverse_subtree( elem.id, vis );
}
}
else if ( v.is_object() )
else if ( t == node_tag::object )
{
uintptr_t sz = v.size();
for ( uintptr_t i = 0; i < sz; ++i )
Expand All @@ -144,7 +146,9 @@ template <typename Visitor> inline void pjson_traverse_subtree_postorder( node_i
if ( !v.valid() )
return;

if ( v.is_array() )
// Единственный вызов tag() вместо отдельных is_array() + is_object() (Этап 10.3).
node_tag t = v.tag();
if ( t == node_tag::array )
{
uintptr_t sz = v.size();
for ( uintptr_t i = 0; i < sz; ++i )
Expand All @@ -154,7 +158,7 @@ template <typename Visitor> inline void pjson_traverse_subtree_postorder( node_i
pjson_traverse_subtree_postorder( elem.id, vis );
}
}
else if ( v.is_object() )
else if ( t == node_tag::object )
{
uintptr_t sz = v.size();
for ( uintptr_t i = 0; i < sz; ++i )
Expand All @@ -177,10 +181,11 @@ template <typename Visitor> inline void pjson_traverse_subtree_postorder( node_i
/// были освобождены раньше родителей.
struct pjson_free_node_visitor
{
void visit( node_id id, const node_view& v )
void visit( node_id id, const node_view& /* v */ )
{
node* n = pmm_resolve<node>( id );
switch ( v.tag() )
// Используем n->tag напрямую вместо v.tag(), чтобы избежать повторного pmm_resolve (Этап 10.3).
switch ( n != nullptr ? n->tag : node_tag::null )
{
case node_tag::array:
if ( n != nullptr )
Expand Down Expand Up @@ -225,10 +230,14 @@ struct pjson_count_visitor
uint64_t& object_cnt;
uint64_t& binary_bytes;

void visit( node_id id, const node_view& v )
void visit( node_id id, const node_view& /* v */ )
{
++node_cnt;
switch ( v.tag() )
// Единственный pmm_resolve: читаем tag напрямую из узла (Этап 10.3).
const node* n = pmm_resolve<node>( id );
if ( n == nullptr )
return;
switch ( n->tag )
{
case node_tag::array:
++array_cnt;
Expand All @@ -240,12 +249,8 @@ struct pjson_count_visitor
++ref_cnt;
break;
case node_tag::binary:
{
const node* n = pmm_resolve<node>( id );
if ( n != nullptr )
binary_bytes += static_cast<uint64_t>( n->binary_val.size() );
binary_bytes += static_cast<uint64_t>( n->binary_val.size() );
break;
}
default:
break;
}
Expand Down
13 changes: 9 additions & 4 deletions pjson_db_pmm.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,9 @@ class pjson_db_pmm

node_view parent{ parent_id };

if ( parent.is_object() )
// Единственный вызов tag() вместо отдельных is_object() + is_array() (Этап 10.3).
node_tag parent_tag = parent.tag();
if ( parent_tag == node_tag::object )
{
node_view child = parent.at( last_seg.c_str() );
if ( !child.valid() )
Expand All @@ -299,7 +301,7 @@ class pjson_db_pmm
_update_metrics_after_mutation();
return ok;
}
else if ( parent.is_array() )
else if ( parent_tag == node_tag::array )
{
char* end_ptr = nullptr;
uintptr_t idx = static_cast<uintptr_t>( std::strtoull( last_seg.c_str(), &end_ptr, 10 ) );
Expand Down Expand Up @@ -646,8 +648,11 @@ class pjson_db_pmm

[[maybe_unused]] bool is_last = ( *p == '\0' );

// Единственный вызов tag() вместо отдельных is_object() + is_array() (Этап 10.3).
node_tag cur_tag = cur_v.tag();

// --- навигация по object ---
if ( cur_v.is_object() )
if ( cur_tag == node_tag::object )
{
node_view child = cur_v.at( seg.c_str() );
if ( child.valid() )
Expand All @@ -674,7 +679,7 @@ class pjson_db_pmm
}
}
// --- навигация по array ---
else if ( cur_v.is_array() )
else if ( cur_tag == node_tag::array )
{
char* end_ptr = nullptr;
uintptr_t idx = static_cast<uintptr_t>( std::strtoull( seg.c_str(), &end_ptr, 10 ) );
Expand Down
16 changes: 12 additions & 4 deletions pjson_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,12 +305,18 @@ struct node_view

/// Проверить, является ли узел JSON null-значением.
/// Возвращает true только для id==0 или узлов с tag==null (но не для ошибочных view).
bool is_null() const { return !is_error() && tag() == node_tag::null; }
bool is_null() const { return tag() == node_tag::null; }
bool is_boolean() const { return tag() == node_tag::boolean; }
bool is_integer() const { return tag() == node_tag::integer; }
bool is_uinteger() const { return tag() == node_tag::uinteger; }
bool is_real() const { return tag() == node_tag::real; }
bool is_number() const { return is_integer() || is_uinteger() || is_real(); }
/// Проверить, является ли узел числовым (integer, uinteger или real).
/// Выполняет единственный pmm_resolve вместо трёх отдельных вызовов tag().
bool is_number() const
{
node_tag t = tag();
return t == node_tag::integer || t == node_tag::uinteger || t == node_tag::real;
}
bool is_string() const { return tag() == node_tag::string; }
bool is_binary() const { return tag() == node_tag::binary; }
bool is_array() const { return tag() == node_tag::array; }
Expand Down Expand Up @@ -517,14 +523,16 @@ struct node_view
/// Если recursive == true, разыменовывает цепочку ref-узлов.
/// Защита от зависания: ограничение глубиной max_depth.
/// Если текущий узел не ref — возвращает себя.
/// Оптимизировано: единственный pmm_resolve за итерацию (вместо отдельных is_ref + ref_target).
node_view deref( bool recursive = true, uintptr_t max_depth = PJSON_MAX_REF_DEPTH ) const
{
node_view cur = *this;
for ( uintptr_t depth = 0; depth < max_depth; depth++ )
{
if ( !cur.is_ref() )
const node* n = cur._resolve();
if ( n == nullptr || n->tag != node_tag::ref )
return cur;
node_id target = cur.ref_target();
node_id target = n->ref_val.target;
if ( target == 0 )
return node_view{}; // не разрешён
node_view next{ target };
Expand Down
28 changes: 15 additions & 13 deletions plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
| 7. Унификация итераторов | CRTP-база pjson_iterator_base + шаблонный pjson_range; ~22 строки удалено | ✅ |

**Итого удалено:** ~5 файлов (~1900 строк), ~381 строка дублирования.
**Тесты:** 676 тестов, ~360 000 assertion.
**Тесты:** 690 тестов, ~360 000 assertion.

---

Expand Down Expand Up @@ -91,19 +91,20 @@

---

### Проблема 10: node_view::tag() вызывает _resolve() при каждой проверке is_*()
### ~~Проблема 10: node_view::tag() вызывает _resolve() при каждой проверке is_*()~~ ✅

**Файл:** `pjson_node.h`, строки 283–308
**Решено в Этапе 10.3:** Оптимизированы горячие пути для сокращения избыточных вызовов `pmm_resolve<node>()`:

Каждый вызов `is_string()`, `is_array()`, `is_number()` и т.д. вызывает `tag()`, который выполняет `pmm_resolve<node>(id)`. При цепочке проверок (`is_null() || is_boolean() || is_integer() || ...`) один и тот же узел разрешается многократно.
1. **`is_number()`** — переписан с единственным вызовом `tag()` вместо трёх (через `is_integer()`, `is_uinteger()`, `is_real()`).
2. **`is_null()`** — убрана избыточная проверка `is_error()` (уже обрабатывается внутри `tag()`).
3. **`deref()`** — единственный `pmm_resolve` за итерацию вместо отдельных `is_ref()` + `ref_target()`.
4. **`pjson_traverse_subtree` / `pjson_traverse_subtree_postorder`** — единственный `tag()` вместо цепочки `is_array()` + `is_object()`.
5. **`pjson_free_node_visitor`** — используется `n->tag` напрямую вместо повторного `v.tag()`.
6. **`pjson_count_visitor`** — единственный `pmm_resolve` для получения тега и данных.
7. **`_walk_path()`** — единственный `tag()` вместо цепочки `is_object()` + `is_array()`.
8. **`erase()`** — единственный `tag()` вместо цепочки `is_object()` + `is_array()`.

Метод `is_number()` вызывает `tag()` трижды:

```cpp
bool is_number() const { return is_integer() || is_uinteger() || is_real(); }
```

**Решение (низкий приоритет):** Кэширование тега в node_view не подходит (node_view — легковесный accessor). Можно рассмотреть метод `tag_checked()`, который одновременно проверяет тег и возвращает указатель, для горячих путей.
Добавлены 14 тестов.

---

Expand Down Expand Up @@ -182,7 +183,7 @@ pvector был бы предпочтительнее **только** при ч
|---|----------|------|-----------|---------|
| ~~3~~ | ~~Глобальное состояние PMM (Этап A)~~ | ~~pam_pmm.h~~ | ~~Высокая~~ | ✅ |
| ~~7~~ | ~~Нет escaping '/' в путях~~ | ~~pjson_db_pmm.h~~ | ~~Средняя~~ | ✅ |
| 10 | Многократный resolve в is_*() | pjson_node.h | Средняя | Производительность |
| ~~10~~ | ~~Многократный resolve в is_*()~~ | ~~pjson_node.h~~ | ~~Средняя~~ | |
| 11 | const-корректность _walk_path | pjson_db_pmm.h | Средняя | Корректность |

---
Expand All @@ -207,7 +208,7 @@ pvector был бы предпочтительнее **только** при ч
Этап 10: Приоритет 3 — архитектурные улучшения
10.1 ✅ Инкапсуляция глобального состояния pam_pmm (Этап A: структура pam_pmm_state + синглтон)
10.2 ✅ Поддержка RFC 6901 (JSON Pointer) для путей
10.3 Оптимизация tag-проверок на горячих путях
10.3 Оптимизация tag-проверок на горячих путях
10.4 const-корректность с явной передачей состояния
```

Expand All @@ -217,6 +218,7 @@ pvector был бы предпочтительнее **только** при ч

| Дата | Изменение |
|------|-----------|
| 2026-03-22 | Этап 10.3: оптимизация tag-проверок на горячих путях — сокращение избыточных pmm_resolve (Issue #207) |
| 2026-03-22 | Этап 10.2: поддержка RFC 6901 (JSON Pointer) для путей — escaping ~/slash в ключах (Issue #206) |
| 2026-03-22 | Этап 10.1: инкапсуляция глобального состояния pam_pmm в структуру pam_pmm_state (Issue #205) |
| 2026-03-22 | Этап 9.4: parse_object() в один проход без двойного парсинга (Issue #192) |
Expand Down
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ int main() {
| `pjson_db_pmm.h` | D | Менеджер персистной JSON-БД: path-адресация, `put`/`get`/`erase`, `$ref`, метрики, поиск, клонирование |
| `deps/pmm/pmm.h` | A | [PersistMemoryManager](https://github.com/netkeep80/PersistMemoryManager) — бэкенд ПАП |
| `main.cpp` | — | Демонстрационная программа |
| `tests/` | — | Тесты на Catch2 (676 тестов, ~360 000 assertion) |
| `tests/` | — | Тесты на Catch2 (690 тестов, ~360 000 assertion) |
| `CMakeLists.txt` | — | Система сборки (CMake 3.16+, C++20) |

---
Expand Down Expand Up @@ -454,6 +454,7 @@ db.put("/copy/name", "Bob");
- **Глобальное состояние PMM** — в одном процессе может быть открыта только одна БД (см. [plan.md](plan.md), Проблема 3); состояние инкапсулировано в `pam_pmm_state`, передача как параметра — в будущих версиях
- ~~**Нет escaping `/` в путях**~~ — **Исправлено** в Этапе 10.2: поддержка RFC 6901 (JSON Pointer) — `~1` для `/`, `~0` для `~` в сегментах путей
- ~~**Утечка временных узлов метрик**~~ — **Исправлено** в Этапе 8.4: один pre-allocated узел переиспользуется для всех вызовов метрик
- ~~**Многократный resolve в is_*() проверках**~~ — **Исправлено** в Этапе 10.3: `is_number()`, `deref()`, traversal и walk_path оптимизированы для единственного `pmm_resolve` вместо повторных вызовов
- **Не потокобезопасно** — CacheManagerConfig (по умолчанию) использует NoLock; для многопоточности нужен PersistentDataConfig
- **Строки не освобождаются** — словарь `pstringview_pmm` только растёт

Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ set(TEST_SOURCES
test_compat_save_load.cpp
test_pjson_ref_stability.cpp
test_pjson_rfc6901.cpp
test_pjson_tag_opt.cpp
)

add_executable(tests ${TEST_SOURCES})
Expand Down
Loading
Loading