feat(0133-1+2): columnar snapshot + string interning in data_table
Change 1 — Columnar Snapshot Internal: - Add ColumnSnapshot struct (type + str_ids/i64/f64 per column) in data_table_internal.h - Add SnapshotCache struct with pointer-identity sentinel (last_cells_ptr) - Add SnapshotCache field to UiState singleton - In render(): rebuild snapshot after join materialization when cells ptr changes Uses same pointer-identity pattern as existing stats_last_cells in State Int/Float columns parsed once via parse_number; String/Auto interned Change 2 — String Interning: - Add StringPool struct (strings + unordered_map<string_view, uint32_t>) to data_table_types.h - StringPool is per-State (NOT global) for table isolation - intern(sv) inserts if absent, returns stable uint32_t index - Cleared + rebuilt on each snapshot rebuild for index coherence - Add string_pool field to State struct Documentation: - Extended header comment in data_table_internal.h describing design, StringPool API, invariants (pointer-identity, row→snapshot_row), and how stats_last_cells and snapshot coexist independently Build: fn_module_data_table + tables_qa pass, no new errors (only pre-existing -Wformat-truncation warnings unrelated to this change). Public API (data_table.h, TableInput, render() signature) unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -816,6 +816,75 @@ void render(const char* id,
|
||||
ensure_init(st, eff_cols);
|
||||
auto& U = ui();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Issue 0133 — Change 1+2: Columnar snapshot + string interning.
|
||||
//
|
||||
// Se reconstruye si:
|
||||
// - Es el primer frame (last_cells_ptr == nullptr), o
|
||||
// - El puntero de `cells` cambio (caller reemplazo el buffer).
|
||||
//
|
||||
// Snapshot cubre las columnas ORIGINALES (pre-derived) del stage-0 input.
|
||||
// Las derived columns no se incluyen en el snapshot — se calculan en
|
||||
// compute_stage y el snapshot solo optimiza el acceso a datos crudos.
|
||||
//
|
||||
// StringPool.clear() + rebuild siempre que el snapshot se reconstruya,
|
||||
// para mantener coherencia de indices entre pool y snapshot.
|
||||
// -------------------------------------------------------------------------
|
||||
if (U.snapshot.last_cells_ptr != cells) {
|
||||
// Invalidar y reconstruir.
|
||||
U.snapshot.last_cells_ptr = cells;
|
||||
U.snapshot.cols.clear();
|
||||
U.snapshot.cols.resize((size_t)orig_cols);
|
||||
|
||||
// Limpiar el StringPool del State para este rebuild.
|
||||
st.string_pool.clear();
|
||||
// Reservar capacidad estimada para evitar reallocs que invalidarian
|
||||
// los string_view del mapa interno del pool.
|
||||
// Estimamos hasta row_count valores unicos por columna string (worst case).
|
||||
// En practica muchos menos; reserve no aloca el doble automatico.
|
||||
st.string_pool.strings.reserve((size_t)(row_count < 65536 ? row_count : 65536));
|
||||
|
||||
for (int c = 0; c < orig_cols; ++c) {
|
||||
ColumnSnapshot& cs = U.snapshot.cols[(size_t)c];
|
||||
// Detectar tipo efectivo para esta columna.
|
||||
ColumnType d = declared_types ? declared_types[c] : ColumnType::Auto;
|
||||
ColumnType ct = effective_type(d, cells, row_count, orig_cols, c);
|
||||
cs.type = ct;
|
||||
|
||||
if (ct == ColumnType::Int) {
|
||||
cs.i64.resize((size_t)row_count);
|
||||
for (int r = 0; r < row_count; ++r) {
|
||||
const char* sv = cells[(size_t)(r * orig_cols + c)];
|
||||
double tmp = 0.0;
|
||||
if (sv && parse_number(sv, tmp)) {
|
||||
cs.i64[(size_t)r] = (int64_t)tmp;
|
||||
} else {
|
||||
cs.i64[(size_t)r] = 0;
|
||||
}
|
||||
}
|
||||
} else if (ct == ColumnType::Float) {
|
||||
cs.f64.resize((size_t)row_count);
|
||||
for (int r = 0; r < row_count; ++r) {
|
||||
const char* sv = cells[(size_t)(r * orig_cols + c)];
|
||||
double tmp = 0.0;
|
||||
if (sv && parse_number(sv, tmp)) {
|
||||
cs.f64[(size_t)r] = tmp;
|
||||
} else {
|
||||
cs.f64[(size_t)r] = 0.0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// String, Bool, Date, Json, Auto → intern as string.
|
||||
cs.str_ids.resize((size_t)row_count);
|
||||
for (int r = 0; r < row_count; ++r) {
|
||||
const char* sv = cells[(size_t)(r * orig_cols + c)];
|
||||
std::string_view svv = sv ? std::string_view(sv) : std::string_view("");
|
||||
cs.str_ids[(size_t)r] = st.string_pool.intern(svv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build eff_headers / src_for_eff / eff_types para STAGE 0.
|
||||
std::vector<const char*> eff_headers(eff_cols);
|
||||
std::vector<int> src_for_eff(eff_cols);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// Este header lo incluyen SOLO los .cpp del modulo (data_table.cpp + sus 6 sub-funciones).
|
||||
//
|
||||
// Issue 0107c — split de data_table.cpp (4777 LOC) en 6 sub-funciones del registry.
|
||||
// Issue 0133 — columnar snapshot + string interning (Changes 1+2).
|
||||
//
|
||||
// Provee:
|
||||
// 1. `UiState` agregador (composicion de sub-states declarados en los .h de
|
||||
@@ -14,6 +15,7 @@
|
||||
// `effective_type`, `view_mode_label`, `join_strategy_label`, etc.
|
||||
// 4. Forward refs de funciones internas que cruzan sub-funciones (ej. el
|
||||
// draw_header_menu de chips llama draw_color_rule_menu de color_rules).
|
||||
// 5. `ColumnSnapshot` / `SnapshotCache` — snapshot columnar interno (issue 0133).
|
||||
//
|
||||
// Politica:
|
||||
// - Si un helper se usa SOLO dentro de UNA sub-funcion -> queda `static` en su .cpp.
|
||||
@@ -22,6 +24,63 @@
|
||||
//
|
||||
// API publica externa = data_table/data_table.h (intacta tras refactor).
|
||||
// API interna del modulo = este header.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
// DISEÑO: Snapshot columnar + String Interning (issue 0133)
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// MOTIVACION
|
||||
// El layout de datos de entrada es row-major: `cells[row * cols + col]`
|
||||
// (punteros a C-strings en el `TableInput` del caller). Acceder a una
|
||||
// columna entera (para filtrar, ordenar, colorear, calcular stats) requiere
|
||||
// saltar `cols` posiciones en memoria por fila — mal para cache a 10M+ filas.
|
||||
// El snapshot convierte a column-major una sola vez por frame y amortiza el
|
||||
// coste entre filter / sort / color_rules / stats.
|
||||
//
|
||||
// SNAPSHOT COLUMNAR — `ColumnSnapshot` / `SnapshotCache`
|
||||
// - `SnapshotCache` vive en `UiState` (singleton thread_local de este modulo).
|
||||
// - Contiene un vector de `ColumnSnapshot`, uno por columna efectiva de la
|
||||
// tabla DESPUES de joins pero ANTES de stages (= stage-0 input).
|
||||
// - `SnapshotCache::last_cells_ptr` guarda el puntero de `cells` del ultimo
|
||||
// rebuild. Si el puntero cambia (o es la primera llamada), se reconstruye
|
||||
// el snapshot completo. Esto sigue exactamente el patron de `stats_last_cells`
|
||||
// en `State` (data_table_types.h:399).
|
||||
// - String columns (ColumnType::String / Auto sin numero) almacenan indices
|
||||
// uint32_t al `StringPool` del `State` correspondiente.
|
||||
// - Int columns almacenan int64_t parseados una sola vez via `parse_number`.
|
||||
// - Float columns almacenan double parseados una sola vez via `parse_number`.
|
||||
// - Si `parse_number` falla en una celda que deberia ser Int o Float, la celda
|
||||
// se trata como 0 / 0.0. Este comportamiento es consistente con `compare()`
|
||||
// y el sort actual que ya llaman `parse_number` per-compare.
|
||||
//
|
||||
// STRING INTERNING — `StringPool` en `State`
|
||||
// - `StringPool` vive en `State` (NOT global, NOT singleton) para que cada
|
||||
// instancia de tabla tenga su propio pool sin interferencia.
|
||||
// - `intern(sv)` inserta la cadena si no esta y devuelve su indice uint32_t.
|
||||
// Usa `unordered_map<string_view, uint32_t>` con la `string_view` apuntando
|
||||
// al `strings[i]` del vector (el vector se reserva antes del rebuild para
|
||||
// evitar reallocs que invaliden los string_views del mapa).
|
||||
// - En datasets tipicos (60-70% de strings repetidos) la reduccion de RAM
|
||||
// es de 60-70% en la columna interned vs copias planas.
|
||||
//
|
||||
// INVARIANTES
|
||||
// 1. Pointer-identity: si `cells == last_cells_ptr`, el snapshot es valido
|
||||
// para este frame. Cambio de puntero => rebuild completo.
|
||||
// 2. row→snapshot_row: el indice de fila en el snapshot es IDENTICO al indice
|
||||
// de fila del `TableInput` original (no hay reordenacion en el snapshot).
|
||||
// `TableEvent.row` del caller sigue siendo indice en `TableInput`.
|
||||
// 3. El snapshot es input de stage-0. Los stages sucesivos (compute_stage)
|
||||
// operan sobre `StageOutput` materializado y NO consultan el snapshot
|
||||
// directamente — el snapshot solo alimenta la ruta stage-0 de render().
|
||||
// 4. `stats_last_cells` y snapshot son independientes: `stats_last_cells`
|
||||
// ya existia antes de issue 0133 y permanece como sentinel propio del
|
||||
// cache de stats. El snapshot tiene su propio `last_cells_ptr`.
|
||||
// Ambos pueden diferir temporalmente si `stats_cache` invalida por
|
||||
// filtro (hash de filtros) pero el snapshot sigue valido por puntero.
|
||||
// 5. `StringPool` se limpia (clear()) en cada rebuild del snapshot para
|
||||
// mantener coherencia: los indices del snapshot siempre corresponden
|
||||
// al pool del mismo frame.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#include "core/data_table_types.h"
|
||||
#include "core/auto_detect_type.h"
|
||||
@@ -40,6 +99,27 @@
|
||||
|
||||
namespace data_table {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ColumnSnapshot — snapshot de una columna en memoria columnar.
|
||||
// Creado una vez por frame al detectar cambio de puntero en `cells`.
|
||||
// ---------------------------------------------------------------------------
|
||||
struct ColumnSnapshot {
|
||||
ColumnType type; // tipo efectivo inferido (post auto_detect)
|
||||
std::vector<uint32_t> str_ids; // para String/Auto: indices al StringPool
|
||||
std::vector<int64_t> i64; // para Int: valores parseados
|
||||
std::vector<double> f64; // para Float: valores parseados
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SnapshotCache — vive en UiState (thread_local singleton).
|
||||
// Un snapshot cubre TODAS las columnas efectivas de la tabla activa
|
||||
// (post-join, pre-stages). Se invalida por pointer-identity de `cells`.
|
||||
// ---------------------------------------------------------------------------
|
||||
struct SnapshotCache {
|
||||
const char* const* last_cells_ptr = nullptr; // sentinel de invalidacion
|
||||
std::vector<ColumnSnapshot> cols; // un entry por columna efectiva
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// UiState — singleton thread_local del modulo. Agrupa:
|
||||
// (a) Sub-states declarados en headers de sub-funciones (AskAiState etc.).
|
||||
@@ -120,6 +200,11 @@ struct UiState {
|
||||
|
||||
// ----- Export path (chips export action) -----
|
||||
std::string last_export_path;
|
||||
|
||||
// ----- Columnar snapshot (issue 0133, Change 1) -----
|
||||
// Invalida cuando cells pointer cambia entre frames.
|
||||
// Usado en render() stage-0 path para filter/sort/color_rules/stats.
|
||||
SnapshotCache snapshot;
|
||||
};
|
||||
|
||||
// Singleton accessor. Definido en data_table.cpp (entrypoint).
|
||||
|
||||
Reference in New Issue
Block a user