// tiny_page_box.h - Tiny Page Box (Tiny-Plus layer for mid-size classes) // // Purpose: // - Provide a per-class "page-level" freelist box that sits between // Unified Cache and the Superslab/Warm Pool backend. // - First target: class C7 (≈1KB) to reduce Shared Pool pressure. // // Box Contract: // - API is generic over class_idx (0-7), but enabled-classes are controlled // by ENV so that we can start with C7 only and later extend to C5/C6. // - When enabled for a class: // tiny_page_box_refill(class_idx, tls, out, max) will try to supply up to // `max` BASE pointers using per-page freelist before falling back. // - When disabled for a class: the box returns 0 and caller uses legacy path. // // - TLS Bind Responsibility: // Page Box selects the appropriate (SuperSlab, slab_idx) pair for the // current request (prioritizing EMPTY or HOT slabs). // It then delegates the binding operation to ss_tls_bind_one() (TLS Bind Box). // This separates "Resource Selection" (Page Box) from "Context Binding" // (TLS Bind Box), clarifying the boundary with Superslab Backend. // // ENV: // HAKMEM_TINY_PAGE_BOX_CLASSES (optional) // - Comma-separated class indices, e.g. "7" or "5,6,7" // - Default: only class 7 is enabled ("7") #ifndef TINY_PAGE_BOX_H #define TINY_PAGE_BOX_H #include #include #include #include "../hakmem_tiny_config.h" #include "../tiny_box_geometry.h" #include "../tiny_tls.h" #include "../superslab/superslab_types.h" // For TinySlabMeta, SuperSlab #include "../box/tiny_next_ptr_box.h" // For tiny_next_read() #include "../hakmem_tiny_superslab.h" // For tiny_stride_for_class(), base helpers, superslab_ref_inc/dec #include "../box/tiny_mem_stats_box.h" // For coarse memory accounting // Superslab active counter(Release Guard Box と整合性を取るためのカウンタ更新) extern void ss_active_add(SuperSlab* ss, uint32_t n); // 最大保持ページ数(1クラスあたり) // C7 専用実験では 1〜2 枚あれば十分だが、将来 C5/C6 にも拡張することを考え 4 枚まで許容する。 #ifndef TINY_PAGE_BOX_MAX_PAGES #define TINY_PAGE_BOX_MAX_PAGES 12 #endif // 1 ページ分のメタデータ typedef struct TinyPageDesc { SuperSlab* ss; TinySlabMeta* meta; uint8_t* base; uint8_t slab_idx; uint8_t _pad[3]; } TinyPageDesc; // Internal per-class page box state. // Phase 2 では: // - enabled: このクラスで Page Box を使うかどうか // - num_pages: 現在保持しているページ数(0〜TINY_PAGE_BOX_MAX_PAGES) // - pages[]: TLS が掴んだ C7/C5/C6 ページの ring(小さなバッファ) typedef struct TinyPageBoxContext { uint8_t enabled; // 1=Page Box enabled for this class, 0=disabled uint8_t num_pages; // 有効な pages[] エントリ数 uint8_t _pad[2]; TinyPageDesc pages[TINY_PAGE_BOX_MAX_PAGES]; } TinyPageBoxContext; // TLS/state: one TinyPageBoxContext per class(per-thread Box) extern __thread TinyPageBoxContext g_tiny_page_box[TINY_NUM_CLASSES]; // One-shot init guard(per-thread) extern __thread int g_tiny_page_box_init_done; static inline int tiny_page_box_log_enabled(void) { static int g_page_box_log = -1; if (__builtin_expect(g_page_box_log == -1, 0)) { const char* e = getenv("HAKMEM_TINY_PAGEBOX_LOG"); g_page_box_log = (e && *e && *e != '0') ? 1 : 0; } return g_page_box_log; } // Helper: parse class list from ENV and set enabled flags. // Default behaviour (ENV unset/empty) is to enable class 7 only. static inline void tiny_page_box_init_once(void) { if (__builtin_expect(g_tiny_page_box_init_done, 1)) { return; } // Clear all state memset(g_tiny_page_box, 0, sizeof(g_tiny_page_box)); tiny_mem_stats_add_pagebox((ssize_t)sizeof(g_tiny_page_box)); const char* env = getenv("HAKMEM_TINY_PAGE_BOX_CLASSES"); if (!env || !*env) { // Default: enable mid-size classes (C5–C7) for (int c = 5; c <= 7 && c < TINY_NUM_CLASSES; c++) { g_tiny_page_box[c].enabled = 1; } } else { // Parse simple comma-separated list of integers: "5,6,7" // We deliberately keep parsing logic simple and robust. const char* p = env; while (*p) { // Skip non-digit characters (commas/spaces) while (*p && (*p < '0' || *p > '9')) { p++; } if (!*p) break; int val = 0; while (*p >= '0' && *p <= '9') { val = val * 10 + (*p - '0'); p++; } if (val >= 0 && val < TINY_NUM_CLASSES) { g_tiny_page_box[val].enabled = 1; } } } g_tiny_page_box_init_done = 1; } // Query: is Page Box enabled for this class? static inline int tiny_page_box_is_enabled(int class_idx) { if (__builtin_expect(!g_tiny_page_box_init_done, 0)) { tiny_page_box_init_once(); } if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES) { return 0; } return g_tiny_page_box[class_idx].enabled != 0; } // Forward declaration for TLS slab state(tiny_tls.h から参照) extern __thread TinyTLSSlab g_tls_slabs[TINY_NUM_CLASSES]; // 新しい TLS Slab が bind されたタイミングで呼び出されるフック。 // ここで Page Box が利用可能なページとして登録しておくことで、 // 後続の unified_cache_refill() から Superslab/Warm Pool に落ちる前に // 「既に TLS が掴んでいるページ」を優先的に使えるようにする。 static inline void tiny_page_box_on_new_slab(int class_idx, TinyTLSSlab* tls) { if (!tls) { return; } if (__builtin_expect(!g_tiny_page_box_init_done, 0)) { tiny_page_box_init_once(); } if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES) { return; } SuperSlab* ss = tls->ss; TinySlabMeta* meta = tls->meta; uint8_t* base = tls->slab_base; if (!ss || !meta || !base) { return; } if (meta->class_idx != (uint8_t)class_idx) { return; } TinyPageBoxContext* st = &g_tiny_page_box[class_idx]; if (!st->enabled) { return; } // 既に登録済みのページであれば更新だけ行う(refcount は増やさない) for (uint8_t i = 0; i < st->num_pages; i++) { TinyPageDesc* d = &st->pages[i]; if (d->ss == ss && d->slab_idx == tls->slab_idx) { d->meta = meta; d->base = base; return; } } // 新規登録: スロットに空きがあれば末尾に追加、なければ先頭を追い出す uint8_t slot; if (st->num_pages < TINY_PAGE_BOX_MAX_PAGES) { slot = st->num_pages++; } else { // 先頭を追い出してシフト(最大 4 エントリなのでコストは無視できる) TinyPageDesc* evict = &st->pages[0]; if (evict->ss) { superslab_ref_dec(evict->ss); } if (TINY_PAGE_BOX_MAX_PAGES > 1) { memmove(&st->pages[0], &st->pages[1], (TINY_PAGE_BOX_MAX_PAGES - 1) * sizeof(TinyPageDesc)); } slot = (uint8_t)(st->num_pages - 1); // 末尾スロット } TinyPageDesc* d = &st->pages[slot]; d->ss = ss; d->meta = meta; d->base = base; d->slab_idx = tls->slab_idx; // Page Box で追跡している間は Superslab を pin しておく superslab_ref_inc(ss); #if !HAKMEM_BUILD_RELEASE // Debug: Track Page Box stats per-class(ENV: HAKMEM_TINY_PAGEBOX_LOG=0 で抑制) if (tiny_page_box_log_enabled()) { fprintf(stderr, "[PAGE_BOX_REG] class=%d num_pages=%u capacity=%u carved=%u\n", class_idx, st->num_pages, meta->capacity, meta->carved); } #endif } // Phase 1 implementation strategy: // - C7(デフォルト有効クラス)については、既存の TLS Slab(TinyTLSSlab)上の // freelist/carve ロジックをここに集約し、Superslab から新しい slab を取ることなく // 「いま TLS が指している slab からだけ」ブロックを切り出す。 // - TLS に有効な slab がない場合(tls->ss==NULL)は 0 を返し、呼び出し側が // 既存の Warm Pool / Superslab 経路で slab を確保する。 // // これにより: // - Box 境界として「Page Box」が成立し、 // - Hot 側では Page Box → Warm Pool → Shared Pool の順序を保ちながら、 // - Superslab/Shared Pool 呼び出し頻度を徐々に観測・調整できる。 static inline int tiny_page_box_refill(int class_idx, TinyTLSSlab* tls, void** out, int max_out) { (void)tls; // reserved for future per-TLS hints if (!tiny_page_box_is_enabled(class_idx)) { return 0; } if (class_idx < 0 || class_idx >= TINY_NUM_CLASSES) { return 0; } if (max_out <= 0) { return 0; } TinyPageBoxContext* st = &g_tiny_page_box[class_idx]; if (st->num_pages == 0) { return 0; } size_t stride = tiny_stride_for_class(class_idx); if (stride == 0) { return 0; } int produced = 0; // 保持しているページを順に走査し、freelist → carve の順にブロックを供給する。 // Page Box では Superslab や Tier/Guard を触らず、「既に TLS が掴んでいるページ」 // だけを対象にする。 for (uint8_t idx = 0; idx < st->num_pages && produced < max_out; /* idx はループ内で更新 */) { TinyPageDesc* d = &st->pages[idx]; SuperSlab* ss = d->ss; TinySlabMeta* m = d->meta; uint8_t* base = d->base; // 無効化されたエントリはその場で削除 if (!ss || !m || !base || (int)m->class_idx != class_idx) { if (ss) { superslab_ref_dec(ss); } uint8_t last = (uint8_t)(st->num_pages - 1); if (idx < last) { st->pages[idx] = st->pages[last]; } st->pages[last].ss = NULL; st->pages[last].meta = NULL; st->pages[last].base = NULL; st->pages[last].slab_idx = 0; st->num_pages--; continue; // 同じ idx に入れ替えられたエントリを再評価 } int local_produced = 0; // まず freelist から pop while (produced < max_out && m->freelist) { void* p = m->freelist; void* next_node = tiny_next_read(class_idx, p); // ヘッダ書き込み(TLS SLL と同じ規約) #if HAKMEM_TINY_HEADER_CLASSIDX *(uint8_t*)p = (uint8_t)(0xa0 | (class_idx & 0x0f)); // freelist 更新と out[] への公開の間で再順序が起きないようフェンス __atomic_thread_fence(__ATOMIC_RELEASE); #endif m->freelist = next_node; m->used++; out[produced++] = p; local_produced++; } // freelist が尽きたら、同じ slab から線形 carve while (produced < max_out && m->carved < m->capacity) { void* p = (void*)(base + ((size_t)m->carved * stride)); #if HAKMEM_TINY_HEADER_CLASSIDX *(uint8_t*)p = (uint8_t)(0xa0 | (class_idx & 0x0f)); #endif m->carved++; m->used++; out[produced++] = p; local_produced++; } if (local_produced > 0) { // Superslab のアクティブカウンタを進める(Release Guard との整合性保持) ss_active_add(ss, (uint32_t)local_produced); } // このページが完全に枯渇した場合は ring から削除 if (!m->freelist && m->carved >= m->capacity) { superslab_ref_dec(ss); uint8_t last = (uint8_t)(st->num_pages - 1); if (idx < last) { st->pages[idx] = st->pages[last]; } st->pages[last].ss = NULL; st->pages[last].meta = NULL; st->pages[last].base = NULL; st->pages[last].slab_idx = 0; st->num_pages--; continue; // 同じ idx に入れ替えられたエントリを再評価 } idx++; } return produced; } #endif // TINY_PAGE_BOX_H