Files
hakmem/core/box/tiny_page_box.h

353 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
int slab_idx = (int)tls->slab_idx;
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