Files
hakmem/docs/archive/PHASE_6.11_DYNAMIC_THRESHOLDS.md
Moe Charm (CI) 52386401b3 Debug Counters Implementation - Clean History
Major Features:
- Debug counter infrastructure for Refill Stage tracking
- Free Pipeline counters (ss_local, ss_remote, tls_sll)
- Diagnostic counters for early return analysis
- Unified larson.sh benchmark runner with profiles
- Phase 6-3 regression analysis documentation

Bug Fixes:
- Fix SuperSlab disabled by default (HAKMEM_TINY_USE_SUPERSLAB)
- Fix profile variable naming consistency
- Add .gitignore patterns for large files

Performance:
- Phase 6-3: 4.79 M ops/s (has OOM risk)
- With SuperSlab: 3.13 M ops/s (+19% improvement)

This is a clean repository without large log files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-05 12:31:14 +09:00

15 KiB
Raw Blame History

Phase 6.11: 動的閾値学習 - ELO による閾値最適化

Date: 2025-10-21 Status: 設計完了(実装待機) 発案者: tomoakiユーザー 詳細設計: ChatGPT Pro


🎯 コンセプト

tomoaki の発想:

「ELO なんだから、キャッシュの固定値その物を学習して動かせばいいんじゃにゃい?」

ChatGPT の詳細化:

  • 全部を常時動かすのはNG
  • 3〜5個の効きが大きい閾値だけを対象
  • CANARY 窓で小さく試してから FROZEN に焼く

📊 学習対象の閾値(効果順)

★★★ 最優先(効果大/副作用小)

1. W_MAX切り上げ許容率

// 上位クラス流用の許容度
// 例: 20KB 要求 → 32KB クラスを使う32/20 = 1.6
W_MAX  {1.25, 1.5, 1.75, 2.0}

役割:
  - 20KB  32KB のような上位クラス流用の"どこまで許すか"

効きどころ:
  - 中粒(232KiB)のプールヒット率 
  - mir/mixed  p99 改善

具体例:

W_MAX = 1.25(厳しめ):
  - 20KB 要求 → 25KB 以下のクラスのみ使用
  - ヒット率: 低い
  - メモリ効率: 高い(無駄少ない)

W_MAX = 1.75(緩め):
  - 20KB 要求 → 35KB 以下のクラスまで使用
  - ヒット率: 高い
  - メモリ効率: やや低い(無駄増える)

→ ELO が最適値を学習

2. Hot/Warm/Cold の境界ms

// 再利用間隔に基づく Free policy 切り替え
hot_ms  {50, 100, 200}       // Hot: < 100ms → KEEP
warm_ms  {500, 800, 1200}     // Warm: 100-800ms → FREE (MADV_FREE)
                               // Cold: >= 800ms → DONTNEED (batch)

効きどころ:
  - minor-faults  RSS のバランス最適化
  - 頻繁な再利用を KEEP で高速化

具体例:

hot_ms = 50厳しめ:
  - 50ms 以内の再利用のみ KEEP
  - minor-faults: 多い
  - RSS: 少ない

hot_ms = 200緩め:
  - 200ms 以内の再利用を KEEP
  - minor-faults: 少ない
  - RSS: 多い

→ ELO がワークロードに最適な境界を学習

3. BATCH_THRESHOLD の基準レンジ(下限/上限)

// madvise バッチ処理の対象範囲
BATCH_RANGE  {(64KiB, 2MiB), (128KiB, 4MiB), (256KiB, 8MiB)}

効きどころ:
  - madvise 呼びすぎ/返さなさすぎの両極端を回避
  - syscall 頻度と RSS のバランス

★★ 次優先(環境依存)

4. THP gateHugePage 適用最小サイズ)

THP_THRESHOLD  {1MB, 2MB, 4MB}

効きどころ:
  - vm(大塊)の TLB/fault 最適化
  - 環境依存なので慎重に

5. mmap_cutovermmap に切替えるサイズ)

MMAP_CUTOVER  {512KB, 768KB, 1MB, 2MB}

効きどころ:
  - 巨大側の syscall 頻度・断片化最適化

🔧 学習方法(軽量バンディット)

基本方針

FROZEN:
  - 閾値は固定(読み取りのみ、オーバーヘッドゼロ)

CANARY 窓30〜60秒:
  - 候補閾値を試す
  - 報酬を集計
  - 勝者を採択

FROZEN採用後:
  - 新しい閾値で凍結
  - 一定時間dwell timeは変更禁止

候補数の最適解: 3候補ChatGPT Pro 推奨)

理由:

  1. 3候補: 探索空間を抑えつつ十分なバリエーション
    • 2候補では選択肢が少なすぎる
    • 4候補以上では収束が遅いCANARY 窓数が増える)
  2. ε-greedy (ε=0.1): 10%探索、90%最良選択で効率的
  3. CANARY window: 30秒で新候補テスト可能
  4. Hysteresis: ±1 step のみ変更で安定性確保

初期値の選び方:

  • 中間値を初期値とする(例: W_MAX は 1.5
  • 理由: 両方向に探索可能(上下に候補あり)

1. 候補集合の離散化

// W_MAX の候補
const double W_MAX_CANDIDATES[] = {1.25, 1.5, 1.75};
const int W_MAX_NUM_CANDIDATES = 3;

// hot_ms の候補
const int HOT_MS_CANDIDATES[] = {50, 100, 200};
const int HOT_MS_NUM_CANDIDATES = 3;

// warm_ms の候補(順序制約: hot_ms < warm_ms
const int WARM_MS_CANDIDATES[] = {500, 800, 1200};
const int WARM_MS_NUM_CANDIDATES = 3;

// BATCH_RANGE の候補
typedef struct {
    size_t min;
    size_t max;
} BatchRange;

const BatchRange BATCH_RANGE_CANDIDATES[] = {
    {64 * 1024, 2 * 1024 * 1024},
    {128 * 1024, 4 * 1024 * 1024},
    {256 * 1024, 8 * 1024 * 1024}
};
const int BATCH_RANGE_NUM_CANDIDATES = 3;

2. 報酬rewardの定義

// 窓ごとに報酬を計算
typedef struct {
    double p99;            // レイテンシ P99
    uint64_t minor_faults; // minor page faults
    uint64_t syscalls;     // syscall 回数
    uint64_t rss_kb;       // RSS (KB)
} Metrics;

// 改善率の計算
static inline double improv(double current, double baseline) {
    if (baseline == 0) return 0.0;
    return (baseline - current) / baseline;
}

// RSS ペナルティ(増えすぎたらマイナス)
static inline double rss_penalty(uint64_t rss, uint64_t baseline_rss) {
    if (rss <= baseline_rss * 1.1) return 0.0;  // 10% までは許容
    return (double)(rss - baseline_rss * 1.1) / (double)baseline_rss;
}

// 総合報酬(重み付き和)
double calculate_reward(Metrics m, Metrics baseline) {
    const double wL = 0.5;  // latency 重み
    const double wF = 0.3;  // faults 重み
    const double wS = 0.1;  // syscalls 重み
    const double wR = 0.1;  // RSS penalty 重み

    return wL * improv(m.p99, baseline.p99)
         + wF * improv(m.minor_faults, baseline.minor_faults)
         + wS * improv(m.syscalls, baseline.syscalls)
         - wR * rss_penalty(m.rss_kb, baseline.rss_kb);
}

3. 探索(ε-greedy

// Arm候補の状態
typedef struct {
    double avg_reward;  // 平均報酬
    int count;          // 試行回数
} Arm;

// W_MAX の Arm3候補
static Arm g_arms_wmax[3] = {{0.0, 0}, {0.0, 0}, {0.0, 0}};
static int g_current_wmax_idx = 1;  // 初期値: 1.5

// ε-greedy 選択
#define EPSILON 0.1  // 10% の確率でランダム探索

static int select_arm_epsilon_greedy(Arm* arms, int num_arms, int current_idx) {
    // 10% の確率でランダム
    if ((rand() % 100) < (EPSILON * 100)) {
        return rand() % num_arms;
    }

    // 90% の確率で最良 Arm
    int best_idx = 0;
    double best_avg = arms[0].avg_reward;

    for (int i = 1; i < num_arms; i++) {
        if (arms[i].avg_reward > best_avg) {
            best_avg = arms[i].avg_reward;
            best_idx = i;
        }
    }

    return best_idx;
}

4. 採択ゲート(勝者判定)

// 採択条件
#define MIN_SAMPLES 10      // 最低試行回数
#define CONFIDENCE_LEVEL 1.96  // 95% 信頼区間

// 95% 信頼区間の上限
static double upper_confidence_bound(Arm* arm) {
    if (arm->count == 0) return 0.0;

    // 標準誤差(簡易版:分散を 1 と仮定)
    double se = 1.0 / sqrt(arm->count);
    return arm->avg_reward + CONFIDENCE_LEVEL * se;
}

// 勝者判定(現在の設定より有意に良いか?)
static int is_improvement_significant(Arm* candidate, Arm* current) {
    if (candidate->count < MIN_SAMPLES) return 0;
    if (current->count < MIN_SAMPLES) return 1;  // 現在が未学習なら即採用

    // 候補の下限 > 現在の上限 → 有意な改善
    double candidate_lower = candidate->avg_reward - CONFIDENCE_LEVEL / sqrt(candidate->count);
    double current_upper = current->avg_reward + CONFIDENCE_LEVEL / sqrt(current->count);

    return candidate_lower > current_upper;
}

5. ヒステリシス(揺れ防止)

// 1回の採用で ±1 ステップまで
#define MAX_STEP 1

// Dwell time採用後の変更禁止期間
#define DWELL_TIME_SEC 600  // 10分

static time_t g_last_adoption_time = 0;

static int clamp_step(int current_idx, int new_idx, int max_step) {
    int diff = new_idx - current_idx;

    if (diff > max_step) return current_idx + max_step;
    if (diff < -max_step) return current_idx - max_step;

    return new_idx;
}

static int can_adopt(void) {
    time_t now = time(NULL);
    return (now - g_last_adoption_time) >= DWELL_TIME_SEC;
}

6. CANARY 窓終端での更新

// CANARY 窓終端30〜60秒ごとに呼ばれる
void canary_window_end(Metrics m, Metrics baseline) {
    if (!can_adopt()) return;  // Dwell time 中は何もしない

    // 今回試した候補の報酬を記録
    double reward = calculate_reward(m, baseline);

    int cur_arm = g_current_wmax_idx;  // 今回使った Arm
    g_arms_wmax[cur_arm].avg_reward =
        (g_arms_wmax[cur_arm].avg_reward * g_arms_wmax[cur_arm].count + reward)
        / (g_arms_wmax[cur_arm].count + 1);
    g_arms_wmax[cur_arm].count++;

    // 十分なサンプルが溜まったら勝者を判定
    if (g_arms_wmax[cur_arm].count >= MIN_SAMPLES) {
        int best = select_arm_epsilon_greedy(g_arms_wmax, W_MAX_NUM_CANDIDATES, cur_arm);

        if (is_improvement_significant(&g_arms_wmax[best], &g_arms_wmax[cur_arm])) {
            // ヒステリシス±1 ステップまで)
            int new_idx = clamp_step(g_current_wmax_idx, best, MAX_STEP);

            if (new_idx != g_current_wmax_idx) {
                // 採用!
                g_current_wmax_idx = new_idx;
                g_frozen_policy.W_MAX = W_MAX_CANDIDATES[new_idx];

                printf("[ELO] W_MAX adopted: %.2f (arm %d)\n",
                       g_frozen_policy.W_MAX, new_idx);

                g_last_adoption_time = time(NULL);

                // Arm をリセット(次の CANARY まで)
                for (int i = 0; i < W_MAX_NUM_CANDIDATES; i++) {
                    g_arms_wmax[i].avg_reward *= 0.9;  // 減衰
                    g_arms_wmax[i].count = (int)(g_arms_wmax[i].count * 0.9);
                }
            }
        }
    }
}

🎯 Site Rules との併用

優先順位

[入力] (size, pc/site)

  ┌─ Step 1: Site Rules ルックアップO(1)
  │   hit? → route/shard を即適用
  │   miss? → Step 2 へ
  │
  └─ Step 2: FrozenPolicy閾値学習の結果を使用
      - W_MAX で上位クラス流用判定
      - hot_ms/warm_ms で Free policy 決定
      - BATCH_RANGE で batch 対象判定

ポイント:

  • Site Rule で route 固定している (site, class) には閾値学習は影響しない
  • 閾値学習は "既定"側を更新Site Rule がない場合のフォールバック)
  • 局所は Site Rule、全体は閾値学習できれいに分業

📈 期待される効果

W_MAX 学習

現状(固定 W_MAX = 1.5:
  - 20KB 要求 → 32KB クラス1.6 > 1.5 なので不可)
  - Pool ミス → malloc へフォールバック

学習後W_MAX = 1.75:
  - 20KB 要求 → 32KB クラス1.6 < 1.75 なので可)
  - Pool ヒット!

効果:
  - L2 Pool ヒット率: +15〜35%
  - mir/mixed の p99: -10〜25%

Hot/Warm/Cold 境界学習

現状(固定 hot_ms = 100, warm_ms = 800:
  - 150ms で再利用 → Warm 扱いMADV_FREE
  - minor-faults 発生

学習後hot_ms = 200, warm_ms = 1200:
  - 150ms で再利用 → Hot 扱いKEEP
  - minor-faults 削減

効果:
  - minor-faults/sec: -30〜70%Warm 厚めが効く)

BATCH_RANGE 学習

現状(固定 64KiB〜2MiB:
  - 3MiB 要求 → batch 対象外(即時 munmap
  - syscall 多い

学習後128KiB〜4MiB:
  - 3MiB 要求 → batch 対象(遅延 munmap
  - syscall 削減

効果:
  - madvise/sec: -20〜40%
  - RSS 変動のスパイク抑制

⚠️ ガードレール(必須制約)

1. 順序制約

// hot_ms < warm_ms を常に保証
if (new_hot_ms >= current_warm_ms) {
    return;  // 不正な組み合わせは拒否
}

// BATCH_MIN ≤ BATCH_MAX を保証
if (new_batch_min > new_batch_max) {
    return;  // 不正な組み合わせは拒否
}

// W_MAX ≥ 1.0 を保証
if (new_wmax < 1.0) {
    return;  // 不正な値は拒否
}

2. Dwell time変更禁止期間

// 採用直後は 10 分間変更禁止
#define DWELL_TIME_SEC 600

// 揺れ防止
if (!can_adopt()) {
    return;  // まだ変更できない
}

3. ±1 ステップ制限

// 1回の採用で ±1 ステップまで
// (大ジャンプ禁止)
int new_idx = clamp_step(current_idx, best_idx, 1);

4. 安全弁固定(頻繁に動かさない)

// これらは月次/週次レベルでのみ調整
// - mmap_cutover影響が大きい
// - THP gate環境依存

🚀 最小セット(すぐ始めるなら)

Phase 6.11.1: W_MAX 学習のみ

// 候補
W_MAX  {1.25, 1.5, 1.75}

// CANARY 窓
3060

// 報酬
wL=0.5 (p99), wF=0.3 (faults), wS=0.1 (syscalls), wR=0.1 (RSS)

// 探索
ε-greedy (ε=0.1)

// 採択
±1 ステップ、dwell time 10

期待効果:

  • L2 Pool ヒット率: +20%
  • mir/mixed の p99: -15%

Phase 6.11.2: Hot/Warm/Cold 境界追加

// 候補(格子探索)
hot_ms  {50, 100, 200}
warm_ms  {600, 900, 1200}

// 順序制約
hot_ms < warm_ms を常に保証

期待効果:

  • minor-faults/sec: -40%

Phase 6.11.3: BATCH_RANGE 追加

// 候補
BATCH_RANGE  {(64K, 2M), (128K, 4M), (256K, 8M)}

期待効果:

  • madvise/sec: -30%
  • RSS スパイク抑制

📊 オーバーヘッド

FROZEN 時(通常)

オーバーヘッド: ゼロ
  - 閾値は固定値を読み取るだけ
  - per-op コストなし

CANARY 窓30〜60秒ごと

オーバーヘッド: O(1) 集計のみ
  - 窓終端での報酬計算
  - Arm 更新
  - 勝者判定

所要時間: < 1ms無視できる

🎯 まとめ

ユーザーの洞察は完璧

「ELO なんだから、キャッシュの固定値その物を学習して動かせばいい」

その通り!閾値自体を ELO で最適化すべき

🚀 実装プラン

Phase 6.11.1: W_MAX 学習

  • 候補: {1.25, 1.5, 1.75}
  • ε-greedy 探索
  • CANARY 窓で採択

Phase 6.11.2: Hot/Warm/Cold 境界

  • 候補: hot_ms ∈ {50, 100, 200}, warm_ms ∈ {600, 900, 1200}
  • 格子探索

Phase 6.11.3: BATCH_RANGE

  • 候補: {(64K, 2M), (128K, 4M), (256K, 8M)}

期待効果:

  • L2 Pool ヒット率: +20%
  • minor-faults/sec: -40%
  • madvise/sec: -30%
  • オーバーヘッド: ゼロFROZEN 時)

ChatGPT の助言:

  • 対象を 3〜5 個に絞る(全部動かさない)
  • CANARY 窓で小さく試す30〜60秒
  • ±1 ステップ制限(大ジャンプ禁止)
  • Dwell time 10分(揺れ防止)

これで mimalloc の "adaptive size classes" を ELO で自動化😺


Generated: 2025-10-21 発案者: tomoakiユーザー 詳細設計: ChatGPT Pro 実装: Phase 6.11.1 から段階的に開始予定 システム名: SACS (Site-Aware Adaptive Cache System) の一部