Files
hakmem/core/box/tiny_page_box.h

352 lines
12 KiB
C
Raw Normal View History

// 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 <stdint.h>
#include <stdlib.h>
#include <string.h>
#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 counterRelease 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 classper-thread Box
extern __thread TinyPageBoxContext g_tiny_page_box[TINY_NUM_CLASSES];
// One-shot init guardper-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 (C5C7)
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 statetiny_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-classENV: 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 SlabTinyTLSSlab上の
// 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