## Summary - ChatGPT により bench_profile.h の setenv segfault を修正(RTLD_NEXT 経由に切り替え) - core/box/pool_zero_mode_box.h 新設:ENV キャッシュ経由で ZERO_MODE を統一管理 - core/hakmem_pool.c で zero mode に応じた memset 制御(FULL/header/off) - A/B テスト結果:ZERO_MODE=header で +15.34% improvement(1M iterations, C6-heavy) ## Files Modified - core/box/pool_api.inc.h: pool_zero_mode_box.h include - core/bench_profile.h: glibc setenv → malloc+putenv(segfault 回避) - core/hakmem_pool.c: zero mode 参照・制御ロジック - core/box/pool_zero_mode_box.h (新設): enum/getter - CURRENT_TASK.md: Phase ML1 結果記載 ## Test Results | Iterations | ZERO_MODE=full | ZERO_MODE=header | Improvement | |-----------|----------------|-----------------|------------| | 10K | 3.06 M ops/s | 3.17 M ops/s | +3.65% | | 1M | 23.71 M ops/s | 27.34 M ops/s | **+15.34%** | 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
352 lines
12 KiB
C
352 lines
12 KiB
C
// 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 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
|