1350 lines
43 KiB
Markdown
1350 lines
43 KiB
Markdown
|
|
# TLS SLL アーキテクチャ改善と今後の開発方針
|
|||
|
|
|
|||
|
|
**調査日**: 2025-11-28
|
|||
|
|
**対象**: Larson ベンチマーク double-free バグの根本原因と包括的なアーキテクチャ改善
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## エグゼクティブサマリー
|
|||
|
|
|
|||
|
|
### 根本原因の特定
|
|||
|
|
|
|||
|
|
Larson ベンチマークでの double-free バグの根本原因は、**TLS SLL と `meta->used` の同期不整合**です:
|
|||
|
|
|
|||
|
|
1. **TLS SLL 操作時に `meta->used` が更新されない**
|
|||
|
|
- `tls_sll_push()`: ポインタを TLS SLL に追加するが `meta->used` は変更なし
|
|||
|
|
- `tls_sll_pop()`: ポインタを TLS SLL から取り出すが `meta->used` は変更なし
|
|||
|
|
- Drain 時のみ: `tiny_free_local_box()` 経由で `meta->used--` される
|
|||
|
|
|
|||
|
|
2. **Slab 再利用時の TLS SLL クリア漏れ**
|
|||
|
|
- Slab が `meta->used == 0` で empty と判定される
|
|||
|
|
- しかし TLS SLL には古いポインタが残っている
|
|||
|
|
- Slab が再初期化されて別のアドレス範囲で使われる
|
|||
|
|
- 古いポインタが再度 allocate されて重複検出
|
|||
|
|
|
|||
|
|
3. **既知のバグ(既に修正済み)**
|
|||
|
|
- TLS Drain pushback バグ: `tls_sll_drain_box.h:148-162` (commit c2f104618 で修正)
|
|||
|
|
- ポインタが TLS SLL の 2 箇所に存在する状態を作り出していた
|
|||
|
|
|
|||
|
|
### 影響範囲
|
|||
|
|
|
|||
|
|
- **確実に影響**: Larson ベンチマーク(マルチスレッド、高頻度 alloc/free)
|
|||
|
|
- **潜在的影響**: 本番環境での長時間稼働時のメモリリーク・重複 free
|
|||
|
|
- **性能への影響**: 現在の periodic drain (2048 frees ごと) では不十分な可能性
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Part 1: 現状分析
|
|||
|
|
|
|||
|
|
### 1.1 TLS SLL の全体構造
|
|||
|
|
|
|||
|
|
#### 主要ファイル
|
|||
|
|
|
|||
|
|
| ファイル | 役割 | 行数 |
|
|||
|
|
|---------|------|------|
|
|||
|
|
| `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_box.h` | TLS SLL のコア実装 (push/pop/splice) | 753行 |
|
|||
|
|
| `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h` | Periodic drain (Option B) | 270行 |
|
|||
|
|
| `/mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h` | Fast free path (TLS SLL への push) | 370行 |
|
|||
|
|
| `/mnt/workdisk/public_share/hakmem/core/box/free_local_box.c` | Slow free path (`meta->used--`) | 210行 |
|
|||
|
|
|
|||
|
|
#### TLS SLL の設計
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// TLS SLL 構造体 (hakmem_tiny.c で定義)
|
|||
|
|
typedef struct {
|
|||
|
|
void* head; // リストの先頭ポインタ (BASE pointer)
|
|||
|
|
uint32_t count; // リスト内の要素数
|
|||
|
|
} TinyTLSSLL;
|
|||
|
|
|
|||
|
|
extern __thread TinyTLSSLL g_tls_sll[TINY_NUM_CLASSES]; // クラスごとに1つ
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**重要な設計原則**:
|
|||
|
|
- すべてのポインタは **BASE ポインタ** (user pointer - 1 byte)
|
|||
|
|
- クラス 0 と 7 は `next_offset = 0` (header を上書きしない)
|
|||
|
|
- クラス 1-6 は `next_offset = 1` (header を書き戻す)
|
|||
|
|
|
|||
|
|
### 1.2 `meta->used` の更新箇所
|
|||
|
|
|
|||
|
|
#### Increment 箇所 (alloc 時)
|
|||
|
|
|
|||
|
|
| ファイル | 行番号 | 条件 |
|
|||
|
|
|---------|-------|------|
|
|||
|
|
| `tiny_superslab_alloc.inc.h` | 30, 102, 146, 323, 363, 387 | 線形 carve / freelist pop |
|
|||
|
|
| `hakmem_tiny_refill_p0.inc.h` | 126, 131, 200 | バッチ refill |
|
|||
|
|
| `hakmem_tiny_tls_ops.h` | 93, 136 | TLS slab refill |
|
|||
|
|
| `slab_handle.h` | 313 | SlabHandle 経由の操作 |
|
|||
|
|
|
|||
|
|
#### Decrement 箇所 (free 時)
|
|||
|
|
|
|||
|
|
| ファイル | 行番号 | 条件 |
|
|||
|
|
|---------|-------|------|
|
|||
|
|
| `free_local_box.c` | 180 | **唯一の decrement 箇所** |
|
|||
|
|
| `hakmem_tiny_bg_spill.c` | 72, 81 | バックグラウンド spill |
|
|||
|
|
| `hakmem_tiny_tls_ops.h` | 198 | TLS spill |
|
|||
|
|
| `slab_handle.h` | 276 | SlabHandle 経由の free |
|
|||
|
|
|
|||
|
|
**重要な発見**: `meta->used` の decrement は **`tiny_free_local_box()` を経由した場合のみ** 実行される。
|
|||
|
|
|
|||
|
|
### 1.3 ポインタのライフサイクル
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[状態遷移図]
|
|||
|
|
|
|||
|
|
CARVED (meta->used++)
|
|||
|
|
|
|
|||
|
|
v
|
|||
|
|
ALLOCATED (ユーザーが使用中)
|
|||
|
|
|
|
|||
|
|
v (free 呼び出し)
|
|||
|
|
|
|
|||
|
|
+---> [FAST PATH] TLS SLL に push (meta->used は変更なし) ----+
|
|||
|
|
| |
|
|||
|
|
| v
|
|||
|
|
| (periodic drain)
|
|||
|
|
| |
|
|||
|
|
+---> [SLOW PATH] tiny_free_local_box() --------------------+
|
|||
|
|
|
|
|||
|
|
v
|
|||
|
|
FREELIST (meta->used--, freelist に追加)
|
|||
|
|
|
|
|||
|
|
v
|
|||
|
|
(再利用時)
|
|||
|
|
|
|
|||
|
|
v
|
|||
|
|
ALLOCATED
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**問題点**:
|
|||
|
|
- Fast path (99% のケース) では `meta->used` が更新されない
|
|||
|
|
- Periodic drain (2048 frees ごと) まで `meta->used` は高いまま
|
|||
|
|
- この間、Slab は "full" に見える → 再利用されない → メモリリーク
|
|||
|
|
|
|||
|
|
### 1.4 Slab 再利用のコードパス
|
|||
|
|
|
|||
|
|
#### Empty 判定条件
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// ss_hot_cold_box.h:37-39
|
|||
|
|
static inline bool ss_is_slab_empty(const TinySlabMeta* meta) {
|
|||
|
|
return (meta->capacity > 0 && meta->used == 0);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Empty マーク
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// free_local_box.c:183-202
|
|||
|
|
if (meta->used == 0) {
|
|||
|
|
// Slab became EMPTY → mark for highest-priority reuse
|
|||
|
|
ss_mark_slab_empty(ss, slab_idx);
|
|||
|
|
|
|||
|
|
fprintf(stderr, "[FREE_LOCAL_BOX] EMPTY detected: cls=%u ss=%p slab=%d\n",
|
|||
|
|
cls, (void*)ss, slab_idx);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Slab 再利用フロー
|
|||
|
|
|
|||
|
|
1. `shared_pool_acquire_slab()` (hakmem_shared_pool.c:700-1300)
|
|||
|
|
- **Stage 1**: EMPTY slots を優先的に再利用 (empty_mask をスキャン)
|
|||
|
|
- **Stage 2**: UNUSED slots を探す
|
|||
|
|
- **Stage 3**: 新しい SuperSlab を割り当て
|
|||
|
|
|
|||
|
|
2. `superslab_init_slab()` (初期化時)
|
|||
|
|
- `meta->used = 0`
|
|||
|
|
- `meta->freelist = NULL`
|
|||
|
|
- `meta->class_idx = class_idx` を設定
|
|||
|
|
|
|||
|
|
**重要な問題**: Slab が EMPTY と判定されて再初期化される時、**TLS SLL はクリアされない**。
|
|||
|
|
|
|||
|
|
### 1.5 既存の診断機能
|
|||
|
|
|
|||
|
|
#### 環境変数ゲート
|
|||
|
|
|
|||
|
|
| 環境変数 | 機能 | ファイル |
|
|||
|
|
|---------|------|---------|
|
|||
|
|
| `HAKMEM_TINY_SLL_DIAG` | TLS SLL の詳細診断 | tls_sll_box.h:118 |
|
|||
|
|
| `HAKMEM_TINY_SLL_DRAIN_ENABLE` | Periodic drain の有効/無効 | tls_sll_drain_box.h:40 |
|
|||
|
|
| `HAKMEM_TINY_SLL_DRAIN_INTERVAL` | Drain 間隔 (デフォルト 2048) | tls_sll_drain_box.h:54 |
|
|||
|
|
| `HAKMEM_TINY_SLL_DRAIN_DEBUG` | Drain の詳細ログ | tls_sll_drain_box.h:121 |
|
|||
|
|
| `HAKMEM_TINY_SLL_SAFEHEADER` | Header 上書き防止モード | tls_sll_box.h:336 |
|
|||
|
|
| `HAKMEM_TINY_LARSON_FIX` | クロススレッド free 検出 | tiny_free_fast_v2.inc.h:189 |
|
|||
|
|
|
|||
|
|
#### 重複チェック
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// tls_sll_box.h:372-402 (debug ビルドのみ)
|
|||
|
|
#if !HAKMEM_BUILD_RELEASE
|
|||
|
|
void* scan = g_tls_sll[class_idx].head;
|
|||
|
|
uint32_t scanned = 0;
|
|||
|
|
const uint32_t limit = 256; // 最大 256 要素をスキャン
|
|||
|
|
|
|||
|
|
while (scan && scanned < limit) {
|
|||
|
|
if (scan == ptr) {
|
|||
|
|
fprintf(stderr, "[TLS_SLL_PUSH_DUP] cls=%d ptr=%p ...\n");
|
|||
|
|
abort(); // 重複検出時は即座に abort
|
|||
|
|
}
|
|||
|
|
// ...
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Part 2: 修正案の設計
|
|||
|
|
|
|||
|
|
### 2.1 即座のバグ修正(最小変更)
|
|||
|
|
|
|||
|
|
#### 修正 A: TLS SLL クリア on Slab Reuse
|
|||
|
|
|
|||
|
|
**ファイル**: `/mnt/workdisk/public_share/hakmem/core/tiny_superslab_alloc.inc.h`
|
|||
|
|
**場所**: `superslab_refill()` 関数内 (行 192-265)
|
|||
|
|
|
|||
|
|
**問題**: Slab が再初期化される時、TLS SLL に古いポインタが残る。
|
|||
|
|
|
|||
|
|
**修正案**:
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// superslab_refill() 内 (行 243 の後に追加)
|
|||
|
|
|
|||
|
|
// CRITICAL FIX: Clear TLS SLL for this class on slab reuse
|
|||
|
|
// Problem: When slab is recycled (EMPTY → reinitialized), old pointers
|
|||
|
|
// remain in TLS SLL, causing double-free on next allocation
|
|||
|
|
// Solution: Drain TLS SLL to ensure no stale pointers remain
|
|||
|
|
#if !HAKMEM_BUILD_RELEASE
|
|||
|
|
{
|
|||
|
|
extern __thread TinyTLSSLL g_tls_sll[TINY_NUM_CLASSES];
|
|||
|
|
uint32_t old_count = g_tls_sll[class_idx].count;
|
|||
|
|
if (old_count > 0) {
|
|||
|
|
// Drain ALL blocks from TLS SLL to freelist (slow but safe)
|
|||
|
|
extern uint32_t tiny_tls_sll_drain(int class_idx, uint32_t batch_size);
|
|||
|
|
uint32_t drained = tiny_tls_sll_drain(class_idx, 0); // 0 = drain all
|
|||
|
|
|
|||
|
|
fprintf(stderr, "[SUPERSLAB_REFILL_TLS_DRAIN] cls=%d drained=%u from TLS SLL\n",
|
|||
|
|
class_idx, drained);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**影響**:
|
|||
|
|
- **本番ビルド**: 変更なし (debug only)
|
|||
|
|
- **開発ビルド**: Slab refill 時に TLS SLL を強制的に drain
|
|||
|
|
- **性能**: 無視できる (refill は低頻度、O(count) の drain)
|
|||
|
|
|
|||
|
|
**副作用**:
|
|||
|
|
- なし(防御的コード、性能への影響は最小)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
#### 修正 B: Drain 間隔の動的調整
|
|||
|
|
|
|||
|
|
**ファイル**: `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h`
|
|||
|
|
**場所**: `tls_sll_drain_get_interval()` (行 54-73)
|
|||
|
|
|
|||
|
|
**問題**: 現在の固定間隔 (2048 frees) では、ワークロードによっては不十分。
|
|||
|
|
|
|||
|
|
**修正案**:
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Dynamic drain interval based on workload
|
|||
|
|
static inline uint32_t tls_sll_drain_get_interval_dynamic(int class_idx) {
|
|||
|
|
static uint32_t g_drain_interval_base = 2048; // デフォルト
|
|||
|
|
static int g_adaptive_drain = -1;
|
|||
|
|
|
|||
|
|
if (__builtin_expect(g_adaptive_drain == -1, 0)) {
|
|||
|
|
const char* env = getenv("HAKMEM_TINY_SLL_DRAIN_ADAPTIVE");
|
|||
|
|
g_adaptive_drain = (env && *env && *env != '0') ? 1 : 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!g_adaptive_drain) {
|
|||
|
|
return g_drain_interval_base; // 固定間隔モード
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Adaptive: Hot classes (0-3) drain more frequently
|
|||
|
|
switch (class_idx) {
|
|||
|
|
case 0: return 512; // 8B - 最頻使用
|
|||
|
|
case 1: return 768; // 16B
|
|||
|
|
case 2: return 768; // 32B
|
|||
|
|
case 3: return 1024; // 64B
|
|||
|
|
case 4: return 1536; // 128B
|
|||
|
|
case 5: return 2048; // 256B
|
|||
|
|
case 6: return 2048; // 512B
|
|||
|
|
case 7: return 2048; // 2048B
|
|||
|
|
default: return g_drain_interval_base;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**使い方**:
|
|||
|
|
```bash
|
|||
|
|
# Adaptive drain を有効化
|
|||
|
|
export HAKMEM_TINY_SLL_DRAIN_ADAPTIVE=1
|
|||
|
|
|
|||
|
|
# または特定クラスの間隔を手動調整
|
|||
|
|
export HAKMEM_TINY_SLL_DRAIN_INTERVAL=512
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**影響**:
|
|||
|
|
- **性能**: Hot classes の drain 頻度 ↑ → `meta->used` の正確性 ↑ → empty detection ↑
|
|||
|
|
- **メモリ**: TLS SLL の最大サイズ ↓ → footprint ↓
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2.2 同期マクロの導入
|
|||
|
|
|
|||
|
|
#### 設計: `TLS_SLL_PUSH_TRACKED` / `TLS_SLL_POP_TRACKED`
|
|||
|
|
|
|||
|
|
**目的**: TLS SLL 操作時に `meta->used` を追跡する(将来的な同期のための基盤)
|
|||
|
|
|
|||
|
|
**ファイル**: 新規作成 `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_tracked_box.h`
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
#pragma once
|
|||
|
|
|
|||
|
|
#include "tls_sll_box.h"
|
|||
|
|
#include "hakmem_tiny_superslab.h"
|
|||
|
|
#include "superslab/superslab_inline.h"
|
|||
|
|
|
|||
|
|
// TLS_SLL_PUSH_TRACKED: Push to TLS SLL with optional meta->used tracking
|
|||
|
|
//
|
|||
|
|
// Args:
|
|||
|
|
// cls: Size class index
|
|||
|
|
// ptr: BASE pointer to push
|
|||
|
|
// cap: Capacity limit
|
|||
|
|
// ss: SuperSlab pointer (for meta->used tracking, can be NULL)
|
|||
|
|
// slab_idx: Slab index (for meta->used tracking, can be -1)
|
|||
|
|
//
|
|||
|
|
// Returns: true on success, false on failure
|
|||
|
|
//
|
|||
|
|
// Behavior:
|
|||
|
|
// - Always pushes to TLS SLL (normal path)
|
|||
|
|
// - If ss != NULL && slab_idx >= 0: optionally track in meta->used (env-gated)
|
|||
|
|
//
|
|||
|
|
static inline bool TLS_SLL_PUSH_TRACKED(int cls, void* ptr, uint32_t cap,
|
|||
|
|
SuperSlab* ss, int slab_idx) {
|
|||
|
|
// Standard TLS SLL push
|
|||
|
|
if (!tls_sll_push(cls, ptr, cap)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#if !HAKMEM_BUILD_RELEASE
|
|||
|
|
// Optional: Track TLS SLL inventory in meta->used_tls_sll (future work)
|
|||
|
|
// ENV: HAKMEM_TINY_TLS_SLL_TRACK_USED=1
|
|||
|
|
static int g_track_used = -1;
|
|||
|
|
if (__builtin_expect(g_track_used == -1, 0)) {
|
|||
|
|
const char* env = getenv("HAKMEM_TINY_TLS_SLL_TRACK_USED");
|
|||
|
|
g_track_used = (env && *env && *env != '0') ? 1 : 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (g_track_used && ss && slab_idx >= 0 && slab_idx < ss_slabs_capacity(ss)) {
|
|||
|
|
// Future: Increment ss->slabs[slab_idx].used_tls_sll
|
|||
|
|
// Note: Requires adding new field to TinySlabMeta
|
|||
|
|
// For now, just log (diagnostic mode)
|
|||
|
|
static _Atomic uint64_t g_track_log = 0;
|
|||
|
|
uint64_t n = atomic_fetch_add(&g_track_log, 1);
|
|||
|
|
if (n < 10) {
|
|||
|
|
fprintf(stderr, "[TLS_SLL_TRACK] PUSH cls=%d ptr=%p ss=%p slab=%d\n",
|
|||
|
|
cls, ptr, (void*)ss, slab_idx);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#else
|
|||
|
|
(void)ss;
|
|||
|
|
(void)slab_idx;
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// TLS_SLL_POP_TRACKED: Pop from TLS SLL with optional meta->used tracking
|
|||
|
|
static inline bool TLS_SLL_POP_TRACKED(int cls, void** out,
|
|||
|
|
SuperSlab** ss_out, int* slab_idx_out) {
|
|||
|
|
// Standard TLS SLL pop
|
|||
|
|
if (!tls_sll_pop(cls, out)) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Resolve SuperSlab/slab for caller (optional metadata)
|
|||
|
|
if (ss_out && slab_idx_out) {
|
|||
|
|
SuperSlab* ss = hak_super_lookup(*out);
|
|||
|
|
if (ss && ss->magic == SUPERSLAB_MAGIC) {
|
|||
|
|
int sidx = slab_index_for(ss, *out);
|
|||
|
|
if (sidx >= 0 && sidx < ss_slabs_capacity(ss)) {
|
|||
|
|
*ss_out = ss;
|
|||
|
|
*slab_idx_out = sidx;
|
|||
|
|
|
|||
|
|
#if !HAKMEM_BUILD_RELEASE
|
|||
|
|
static int g_track_used = -1;
|
|||
|
|
if (__builtin_expect(g_track_used == -1, 0)) {
|
|||
|
|
const char* env = getenv("HAKMEM_TINY_TLS_SLL_TRACK_USED");
|
|||
|
|
g_track_used = (env && *env && *env != '0') ? 1 : 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (g_track_used) {
|
|||
|
|
// Future: Decrement ss->slabs[sidx].used_tls_sll
|
|||
|
|
static _Atomic uint64_t g_track_log = 0;
|
|||
|
|
uint64_t n = atomic_fetch_add(&g_track_log, 1);
|
|||
|
|
if (n < 10) {
|
|||
|
|
fprintf(stderr, "[TLS_SLL_TRACK] POP cls=%d ptr=%p ss=%p slab=%d\n",
|
|||
|
|
cls, *out, (void*)ss, sidx);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**使い方**:
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Before (existing code)
|
|||
|
|
tls_sll_push(class_idx, base, UINT32_MAX);
|
|||
|
|
|
|||
|
|
// After (with tracking)
|
|||
|
|
TLS_SLL_PUSH_TRACKED(class_idx, base, UINT32_MAX, ss, slab_idx);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**段階的導入**:
|
|||
|
|
1. Phase 1: マクロ導入、ログのみ(診断用)
|
|||
|
|
2. Phase 2: `TinySlabMeta.used_tls_sll` フィールド追加
|
|||
|
|
3. Phase 3: `meta->used` の代わりに `meta->used + meta->used_tls_sll` で empty 判定
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 2.3 Slab 再利用ガードの設計
|
|||
|
|
|
|||
|
|
#### 設計: `tiny_slab_can_reuse()`
|
|||
|
|
|
|||
|
|
**目的**: Slab を再利用する前に、TLS SLL に古いポインタが残っていないことを確認
|
|||
|
|
|
|||
|
|
**ファイル**: 新規作成 `/mnt/workdisk/public_share/hakmem/core/box/slab_reuse_guard_box.h`
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
#pragma once
|
|||
|
|
|
|||
|
|
#include "hakmem_tiny_superslab.h"
|
|||
|
|
#include "hakmem_tiny_config.h"
|
|||
|
|
#include <stdint.h>
|
|||
|
|
#include <stdbool.h>
|
|||
|
|
|
|||
|
|
// ============================================================================
|
|||
|
|
// Slab Reuse Guard: Prevent reuse when TLS SLL contains stale pointers
|
|||
|
|
// ============================================================================
|
|||
|
|
//
|
|||
|
|
// Problem:
|
|||
|
|
// - Slab marked as EMPTY (meta->used == 0)
|
|||
|
|
// - But TLS SLL still contains pointers from this slab
|
|||
|
|
// - Slab gets reinitialized for different class
|
|||
|
|
// - Old pointers in TLS SLL get allocated → double-free
|
|||
|
|
//
|
|||
|
|
// Solution:
|
|||
|
|
// - Before reusing EMPTY slab, check if TLS SLL contains pointers from it
|
|||
|
|
// - If yes, force drain TLS SLL first (or skip reuse)
|
|||
|
|
//
|
|||
|
|
// Design:
|
|||
|
|
// - Development builds: strict check + force drain
|
|||
|
|
// - Production builds: best-effort check (optional, env-gated)
|
|||
|
|
|
|||
|
|
// Check if TLS SLL contains pointers from given slab
|
|||
|
|
// Returns: number of pointers found in TLS SLL from this slab
|
|||
|
|
static inline uint32_t tiny_slab_count_tls_sll_refs(SuperSlab* ss, int slab_idx, int class_idx) {
|
|||
|
|
if (!ss || slab_idx < 0 || slab_idx >= ss_slabs_capacity(ss)) {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
extern __thread TinyTLSSLL g_tls_sll[TINY_NUM_CLASSES];
|
|||
|
|
|
|||
|
|
// Calculate slab address range
|
|||
|
|
uint8_t* slab_base = tiny_slab_base_for(ss, slab_idx);
|
|||
|
|
uintptr_t slab_start = (uintptr_t)slab_base;
|
|||
|
|
uintptr_t slab_end = slab_start + SLAB_SIZE;
|
|||
|
|
|
|||
|
|
// Scan TLS SLL for this class
|
|||
|
|
uint32_t found = 0;
|
|||
|
|
void* cur = g_tls_sll[class_idx].head;
|
|||
|
|
uint32_t scanned = 0;
|
|||
|
|
const uint32_t limit = 1024; // Scan up to 1024 entries
|
|||
|
|
|
|||
|
|
while (cur && scanned < limit) {
|
|||
|
|
uintptr_t addr = (uintptr_t)cur;
|
|||
|
|
|
|||
|
|
// Check if pointer is in this slab's range
|
|||
|
|
if (addr >= slab_start && addr < slab_end) {
|
|||
|
|
found++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Get next pointer
|
|||
|
|
void* next = NULL;
|
|||
|
|
PTR_NEXT_READ("reuse_guard_scan", class_idx, cur, 0, next);
|
|||
|
|
cur = next;
|
|||
|
|
scanned++;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return found;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Guard: Check if slab can be safely reused
|
|||
|
|
// Returns: true if safe to reuse, false if TLS SLL drain needed
|
|||
|
|
static inline bool tiny_slab_can_reuse(SuperSlab* ss, int slab_idx, int class_idx) {
|
|||
|
|
#if !HAKMEM_BUILD_RELEASE
|
|||
|
|
// Development: Always check for safety
|
|||
|
|
uint32_t refs = tiny_slab_count_tls_sll_refs(ss, slab_idx, class_idx);
|
|||
|
|
|
|||
|
|
if (refs > 0) {
|
|||
|
|
fprintf(stderr,
|
|||
|
|
"[SLAB_REUSE_GUARD] BLOCKED: cls=%d ss=%p slab=%d has %u refs in TLS SLL\n",
|
|||
|
|
class_idx, (void*)ss, slab_idx, refs);
|
|||
|
|
|
|||
|
|
// Force drain TLS SLL to clear stale pointers
|
|||
|
|
extern uint32_t tiny_tls_sll_drain(int class_idx, uint32_t batch_size);
|
|||
|
|
uint32_t drained = tiny_tls_sll_drain(class_idx, 0); // Drain all
|
|||
|
|
|
|||
|
|
fprintf(stderr,
|
|||
|
|
"[SLAB_REUSE_GUARD] Forced drain: cls=%d drained=%u blocks\n",
|
|||
|
|
class_idx, drained);
|
|||
|
|
|
|||
|
|
// Re-check after drain
|
|||
|
|
refs = tiny_slab_count_tls_sll_refs(ss, slab_idx, class_idx);
|
|||
|
|
if (refs > 0) {
|
|||
|
|
fprintf(stderr,
|
|||
|
|
"[SLAB_REUSE_GUARD] CRITICAL: Still %u refs after drain! (memory corruption?)\n",
|
|||
|
|
refs);
|
|||
|
|
return false; // Unsafe to reuse
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true; // Safe to reuse
|
|||
|
|
#else
|
|||
|
|
// Production: Optional check (env-gated for minimal overhead)
|
|||
|
|
static int g_reuse_guard = -1;
|
|||
|
|
if (__builtin_expect(g_reuse_guard == -1, 0)) {
|
|||
|
|
const char* env = getenv("HAKMEM_TINY_SLAB_REUSE_GUARD");
|
|||
|
|
g_reuse_guard = (env && *env && *env != '0') ? 1 : 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!g_reuse_guard) {
|
|||
|
|
return true; // Guard disabled in production
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Same logic as development build
|
|||
|
|
uint32_t refs = tiny_slab_count_tls_sll_refs(ss, slab_idx, class_idx);
|
|||
|
|
if (refs > 0) {
|
|||
|
|
// Production: Just skip reuse (don't force drain to avoid latency spike)
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
#endif
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**統合場所**: `hakmem_shared_pool.c` の `shared_pool_acquire_slab()`
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// shared_pool_acquire_slab() 内 (Stage 1: EMPTY slot reuse の前)
|
|||
|
|
|
|||
|
|
// Before reusing EMPTY slot, check TLS SLL safety
|
|||
|
|
if (!tiny_slab_can_reuse(ss, slot_idx, class_idx)) {
|
|||
|
|
// Skip this EMPTY slot (TLS SLL drain needed first)
|
|||
|
|
fprintf(stderr, "[ACQUIRE_SLAB] Skipped EMPTY slot due to TLS SLL refs\n");
|
|||
|
|
continue; // Try next slot
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**影響**:
|
|||
|
|
- **開発ビルド**: 常に有効、TLS SLL の自動 drain 付き
|
|||
|
|
- **本番ビルド**: ENV で有効化可能、drain なし(スキップのみ)
|
|||
|
|
- **性能**: 開発ビルドで O(TLS_SLL_count) のスキャン、本番では無視できる
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Part 3: アーキテクチャ改善
|
|||
|
|
|
|||
|
|
### 3.1 ポインタライフサイクル管理
|
|||
|
|
|
|||
|
|
#### 状態遷移図(改善版)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌─────────────────────────────────────────────────────────────┐
|
|||
|
|
│ Pointer Lifecycle States │
|
|||
|
|
└─────────────────────────────────────────────────────────────┘
|
|||
|
|
|
|||
|
|
[UNCARVED] (Slab 初期化直後、meta->used < meta->capacity)
|
|||
|
|
|
|
|||
|
|
| superslab_alloc_from_slab() - Linear carve
|
|||
|
|
v
|
|||
|
|
[CARVED] (meta->used++, user に返す直前)
|
|||
|
|
|
|
|||
|
|
| return block to user
|
|||
|
|
v
|
|||
|
|
[ALLOCATED] (ユーザーコードが使用中)
|
|||
|
|
|
|
|||
|
|
| user calls free(ptr)
|
|||
|
|
v
|
|||
|
|
+---> [FAST FREE PATH 99%] ----+ +---> [SLOW FREE PATH 1%]
|
|||
|
|
| | |
|
|||
|
|
| hak_tiny_free_fast_v2() | | hak_tiny_free()
|
|||
|
|
| - Header から class_idx 読取 | | - SuperSlab lookup
|
|||
|
|
| - tls_sll_push(ptr) | | - Ownership 確認
|
|||
|
|
| (meta->used 不変!) | |
|
|||
|
|
| | |
|
|||
|
|
v | v
|
|||
|
|
[IN_TLS_SLL] | [FREELIST]
|
|||
|
|
| - TLS SLL に格納 | | - tiny_free_local_box()
|
|||
|
|
| - meta->used は高いまま | | - freelist に push
|
|||
|
|
| - periodic drain を待つ | | - meta->used-- ★
|
|||
|
|
| | |
|
|||
|
|
| (2048 frees 後) | |
|
|||
|
|
v | |
|
|||
|
|
[DRAINING] | |
|
|||
|
|
| tiny_tls_sll_drain() | |
|
|||
|
|
| - TLS SLL から pop +<----+
|
|||
|
|
| - tiny_free_local_box() 呼出
|
|||
|
|
| - meta->used-- ★
|
|||
|
|
v
|
|||
|
|
[FREELIST] (meta->freelist に連結、meta->used 正確)
|
|||
|
|
|
|
|||
|
|
| (meta->used == 0 なら)
|
|||
|
|
v
|
|||
|
|
[EMPTY] (ss_mark_slab_empty())
|
|||
|
|
|
|
|||
|
|
| shared_pool_acquire_slab() - Stage 1
|
|||
|
|
| → tiny_slab_can_reuse() ガード ★
|
|||
|
|
v
|
|||
|
|
[REUSABLE] (再初期化可能)
|
|||
|
|
|
|
|||
|
|
| superslab_init_slab()
|
|||
|
|
v
|
|||
|
|
[UNCARVED] (ループ)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**重要な Invariants**:
|
|||
|
|
|
|||
|
|
1. **Meta->used の正確性**
|
|||
|
|
```c
|
|||
|
|
// Invariant 1: meta->used reflects allocated + in_freelist
|
|||
|
|
// (TLS SLL 内のポインタは含まれない!)
|
|||
|
|
meta->used == allocated_count + freelist_count // 不正確
|
|||
|
|
|
|||
|
|
// Correct invariant (with TLS SLL):
|
|||
|
|
meta->used == allocated_count + freelist_count + tls_sll_count
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
2. **Empty 判定の条件**
|
|||
|
|
```c
|
|||
|
|
// Current (WRONG):
|
|||
|
|
is_empty = (meta->used == 0); // TLS SLL に残っている可能性
|
|||
|
|
|
|||
|
|
// Fixed (with guard):
|
|||
|
|
is_empty = (meta->used == 0) && !tiny_slab_has_tls_sll_refs(ss, slab_idx, cls);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
3. **TLS SLL のクリーン状態**
|
|||
|
|
```c
|
|||
|
|
// Invariant 3: TLS SLL must be drained before slab reuse
|
|||
|
|
// Enforced by: tiny_slab_can_reuse() guard
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 コード構造の改善
|
|||
|
|
|
|||
|
|
#### 責任の分離
|
|||
|
|
|
|||
|
|
| モジュール | 責任 | 主要 API |
|
|||
|
|
|-----------|------|---------|
|
|||
|
|
| **TLS SLL Box** | TLS 単方向リスト管理 | `tls_sll_push/pop/splice` |
|
|||
|
|
| **TLS SLL Drain Box** | Periodic drain、meta->used 同期 | `tiny_tls_sll_drain/try_drain` |
|
|||
|
|
| **Slab Meta Box** | Slab メタデータ管理 | `ss_slab_meta_*` |
|
|||
|
|
| **Slab Reuse Guard Box** | 再利用安全性チェック | `tiny_slab_can_reuse` |
|
|||
|
|
| **Free Local Box** | Freelist 管理、meta->used 更新 | `tiny_free_local_box` |
|
|||
|
|
| **Shared Pool** | SuperSlab 割り当て、slot 管理 | `shared_pool_acquire/release_slab` |
|
|||
|
|
|
|||
|
|
#### ファイル構成の提案
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
core/
|
|||
|
|
├── box/
|
|||
|
|
│ ├── tls_sll_box.h (既存) Push/Pop/Splice
|
|||
|
|
│ ├── tls_sll_drain_box.h (既存) Periodic drain
|
|||
|
|
│ ├── tls_sll_tracked_box.h (新規) Tracked push/pop
|
|||
|
|
│ ├── slab_reuse_guard_box.h (新規) Reuse safety guard
|
|||
|
|
│ ├── free_local_box.{h,c} (既存) Freelist + meta->used--
|
|||
|
|
│ ├── ss_slab_meta_box.h (既存) Meta accessors
|
|||
|
|
│ └── ss_hot_cold_box.h (既存) Hot/Cold/Empty marking
|
|||
|
|
├── hakmem_shared_pool.{h,c} (既存) Shared pool
|
|||
|
|
├── tiny_superslab_alloc.inc.h (既存) Alloc paths
|
|||
|
|
└── tiny_free_fast_v2.inc.h (既存) Fast free path
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.3 デバッグ・診断機能の強化
|
|||
|
|
|
|||
|
|
#### 新規追加すべき診断機能
|
|||
|
|
|
|||
|
|
##### A. TLS SLL 在庫トラッキング
|
|||
|
|
|
|||
|
|
**ENV**: `HAKMEM_TINY_TLS_SLL_INVENTORY=1`
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Per-class TLS SLL inventory statistics (TLS)
|
|||
|
|
static __thread struct {
|
|||
|
|
uint64_t total_push; // 累計 push 回数
|
|||
|
|
uint64_t total_pop; // 累計 pop 回数
|
|||
|
|
uint64_t total_drained; // 累計 drain 回数
|
|||
|
|
uint32_t peak_count; // 最大 count
|
|||
|
|
uint32_t current_count; // 現在の count
|
|||
|
|
} g_tls_sll_stats[TINY_NUM_CLASSES];
|
|||
|
|
|
|||
|
|
// Destructor で出力
|
|||
|
|
static void tls_sll_inventory_report(void) __attribute__((destructor));
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
##### B. Slab 再利用ヒストリー
|
|||
|
|
|
|||
|
|
**ENV**: `HAKMEM_TINY_SLAB_REUSE_HISTORY=1`
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Per-slab reuse history (circular buffer)
|
|||
|
|
#define REUSE_HISTORY_SIZE 16
|
|||
|
|
typedef struct {
|
|||
|
|
uint64_t timestamp_ns; // 再利用時刻
|
|||
|
|
uint8_t prev_class_idx; // 前のクラス
|
|||
|
|
uint8_t new_class_idx; // 新しいクラス
|
|||
|
|
uint16_t tls_sll_refs; // 再利用時の TLS SLL 参照数
|
|||
|
|
} SlabReuseEvent;
|
|||
|
|
|
|||
|
|
// Per SuperSlab
|
|||
|
|
SlabReuseEvent reuse_history[SLABS_PER_SUPERSLAB_MAX][REUSE_HISTORY_SIZE];
|
|||
|
|
uint32_t reuse_index[SLABS_PER_SUPERSLAB_MAX];
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
##### C. Meta->used 整合性チェック
|
|||
|
|
|
|||
|
|
**ENV**: `HAKMEM_TINY_META_USED_VERIFY=1`
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Periodic verification: count actual allocations vs meta->used
|
|||
|
|
// (Expensive, debug only)
|
|||
|
|
static void verify_meta_used_consistency(SuperSlab* ss, int slab_idx) {
|
|||
|
|
TinySlabMeta* meta = &ss->slabs[slab_idx];
|
|||
|
|
|
|||
|
|
// Count freelist entries
|
|||
|
|
uint32_t freelist_count = 0;
|
|||
|
|
void* cur = meta->freelist;
|
|||
|
|
while (cur && freelist_count < meta->capacity) {
|
|||
|
|
freelist_count++;
|
|||
|
|
cur = tiny_next_read(meta->class_idx, cur);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Expected: meta->used + freelist_count <= meta->capacity
|
|||
|
|
if (meta->used + freelist_count > meta->capacity) {
|
|||
|
|
fprintf(stderr, "[META_USED_VERIFY] CORRUPTION: used=%u freelist=%u cap=%u\n",
|
|||
|
|
meta->used, freelist_count, meta->capacity);
|
|||
|
|
abort();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 性能への影響を最小化
|
|||
|
|
|
|||
|
|
**戦略**:
|
|||
|
|
1. **本番ビルドでは完全無効化**: `#if !HAKMEM_BUILD_RELEASE` でガード
|
|||
|
|
2. **ENV ゲート**: デフォルト OFF、必要時のみ有効化
|
|||
|
|
3. **サンプリング**: 全操作ではなく N 回に 1 回だけチェック
|
|||
|
|
4. **TLS カウンタ**: Atomic 操作を避ける
|
|||
|
|
|
|||
|
|
### 3.4 ドキュメント化
|
|||
|
|
|
|||
|
|
#### MEMORY_LIFECYCLE.md の内容提案
|
|||
|
|
|
|||
|
|
```markdown
|
|||
|
|
# メモリライフサイクル設計書
|
|||
|
|
|
|||
|
|
## 概要
|
|||
|
|
|
|||
|
|
HAKMEM の Tiny Pool におけるポインタのライフサイクルと状態遷移を定義。
|
|||
|
|
|
|||
|
|
## 状態定義
|
|||
|
|
|
|||
|
|
### UNCARVED
|
|||
|
|
- **定義**: Slab 初期化直後、まだ carve されていない領域
|
|||
|
|
- **条件**: `meta->used < meta->capacity`
|
|||
|
|
- **次状態**: CARVED (alloc 時)
|
|||
|
|
|
|||
|
|
### CARVED
|
|||
|
|
- **定義**: Slab から carve され、ユーザーに返す直前
|
|||
|
|
- **操作**: `meta->used++`
|
|||
|
|
- **次状態**: ALLOCATED
|
|||
|
|
|
|||
|
|
### ALLOCATED
|
|||
|
|
- **定義**: ユーザーコードが使用中
|
|||
|
|
- **Invariant**: ポインタはユーザー空間にあり、内部リストには未登録
|
|||
|
|
- **次状態**: IN_TLS_SLL (fast free) or FREELIST (slow free)
|
|||
|
|
|
|||
|
|
### IN_TLS_SLL
|
|||
|
|
- **定義**: TLS SLL に push された、drain 待ち
|
|||
|
|
- **Invariant**:
|
|||
|
|
- `g_tls_sll[class_idx]` のリスト内に存在
|
|||
|
|
- `meta->used` は変更なし(高いまま)
|
|||
|
|
- Periodic drain まで freelist に戻らない
|
|||
|
|
- **次状態**: DRAINING (periodic drain)
|
|||
|
|
|
|||
|
|
### DRAINING
|
|||
|
|
- **定義**: TLS SLL から freelist に移動中
|
|||
|
|
- **操作**:
|
|||
|
|
1. `tls_sll_pop()`
|
|||
|
|
2. `tiny_free_local_box()` → `meta->used--`
|
|||
|
|
3. Freelist に push
|
|||
|
|
- **次状態**: FREELIST
|
|||
|
|
|
|||
|
|
### FREELIST
|
|||
|
|
- **定義**: Slab の freelist に連結
|
|||
|
|
- **Invariant**:
|
|||
|
|
- `meta->freelist` のリスト内に存在
|
|||
|
|
- `meta->used` は正確(decrement 済み)
|
|||
|
|
- **次状態**: ALLOCATED (再利用) or EMPTY (すべて free)
|
|||
|
|
|
|||
|
|
### EMPTY
|
|||
|
|
- **定義**: Slab 内のすべてのブロックが free
|
|||
|
|
- **条件**: `meta->used == 0`
|
|||
|
|
- **マーク**: `ss_mark_slab_empty(ss, slab_idx)`
|
|||
|
|
- **次状態**: REUSABLE (再利用準備)
|
|||
|
|
|
|||
|
|
### REUSABLE
|
|||
|
|
- **定義**: 別のクラスで再初期化可能
|
|||
|
|
- **ガード**: `tiny_slab_can_reuse()` でチェック
|
|||
|
|
- **操作**: `superslab_init_slab()`
|
|||
|
|
- **次状態**: UNCARVED
|
|||
|
|
|
|||
|
|
## 状態遷移の Invariants
|
|||
|
|
|
|||
|
|
### Invariant 1: Meta->used の意味
|
|||
|
|
```c
|
|||
|
|
// WRONG (current):
|
|||
|
|
meta->used == allocated_count + freelist_count
|
|||
|
|
|
|||
|
|
// CORRECT (with TLS SLL):
|
|||
|
|
meta->used == allocated_count + freelist_count + tls_sll_count
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**問題**: TLS SLL 内のポインタは `meta->used` に反映されない。
|
|||
|
|
|
|||
|
|
### Invariant 2: Empty 判定
|
|||
|
|
```c
|
|||
|
|
// WRONG:
|
|||
|
|
is_empty = (meta->used == 0);
|
|||
|
|
|
|||
|
|
// CORRECT:
|
|||
|
|
is_empty = (meta->used == 0) &&
|
|||
|
|
!tiny_slab_has_tls_sll_refs(ss, slab_idx, class_idx);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Invariant 3: Slab 再利用前の TLS SLL クリーン
|
|||
|
|
```c
|
|||
|
|
// Before reuse:
|
|||
|
|
tiny_slab_can_reuse(ss, slab_idx, class_idx) == true
|
|||
|
|
⇒ TLS SLL に当該 slab のポインタが存在しない
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## デバッグツール
|
|||
|
|
|
|||
|
|
- `HAKMEM_TINY_TLS_SLL_DIAG=1`: TLS SLL 診断
|
|||
|
|
- `HAKMEM_TINY_SLAB_REUSE_GUARD=1`: 再利用ガード(本番)
|
|||
|
|
- `HAKMEM_TINY_META_USED_VERIFY=1`: Meta->used 検証(開発)
|
|||
|
|
|
|||
|
|
## 参考文献
|
|||
|
|
|
|||
|
|
- `/mnt/workdisk/public_share/hakmem/docs/analysis/LARSON_DOUBLE_FREE_INVESTIGATION.md`
|
|||
|
|
- `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_box.h`
|
|||
|
|
- `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h`
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.5 テスト戦略
|
|||
|
|
|
|||
|
|
#### 単体テスト
|
|||
|
|
|
|||
|
|
**ファイル**: 新規作成 `tests/test_tls_sll_lifecycle.c`
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Test 1: TLS SLL push/pop/drain cycle
|
|||
|
|
void test_tls_sll_basic_cycle(void) {
|
|||
|
|
int class_idx = 1; // 16B
|
|||
|
|
void* ptr1 = allocate_test_block(class_idx);
|
|||
|
|
void* ptr2 = allocate_test_block(class_idx);
|
|||
|
|
|
|||
|
|
// Push to TLS SLL
|
|||
|
|
assert(tls_sll_push(class_idx, ptr1, 1000));
|
|||
|
|
assert(g_tls_sll[class_idx].count == 1);
|
|||
|
|
|
|||
|
|
// Pop from TLS SLL
|
|||
|
|
void* out;
|
|||
|
|
assert(tls_sll_pop(class_idx, &out));
|
|||
|
|
assert(out == ptr1);
|
|||
|
|
assert(g_tls_sll[class_idx].count == 0);
|
|||
|
|
|
|||
|
|
// Drain empty list (no-op)
|
|||
|
|
uint32_t drained = tiny_tls_sll_drain(class_idx, 0);
|
|||
|
|
assert(drained == 0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Test 2: Slab reuse guard
|
|||
|
|
void test_slab_reuse_guard(void) {
|
|||
|
|
int class_idx = 2; // 32B
|
|||
|
|
SuperSlab* ss = get_test_superslab();
|
|||
|
|
int slab_idx = 0;
|
|||
|
|
|
|||
|
|
// Allocate and free blocks (push to TLS SLL)
|
|||
|
|
for (int i = 0; i < 10; i++) {
|
|||
|
|
void* ptr = allocate_from_slab(ss, slab_idx);
|
|||
|
|
tls_sll_push(class_idx, ptr, 1000);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Manually mark slab as empty (simulate meta->used == 0)
|
|||
|
|
ss->slabs[slab_idx].used = 0;
|
|||
|
|
ss_mark_slab_empty(ss, slab_idx);
|
|||
|
|
|
|||
|
|
// Reuse guard should detect TLS SLL refs
|
|||
|
|
assert(!tiny_slab_can_reuse(ss, slab_idx, class_idx));
|
|||
|
|
|
|||
|
|
// Drain TLS SLL
|
|||
|
|
tiny_tls_sll_drain(class_idx, 0);
|
|||
|
|
|
|||
|
|
// Now reuse should be safe
|
|||
|
|
assert(tiny_slab_can_reuse(ss, slab_idx, class_idx));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Test 3: Meta->used consistency after drain
|
|||
|
|
void test_meta_used_consistency(void) {
|
|||
|
|
// TODO: Implement
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 統合テスト
|
|||
|
|
|
|||
|
|
**ベンチマーク**: Larson + 拡張
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
# 現在の Larson (MT stress test)
|
|||
|
|
./mimalloc-bench/out/bench/larson 10 5 200000
|
|||
|
|
|
|||
|
|
# 追加テスト: Slab 再利用を強制
|
|||
|
|
export HAKMEM_TINY_SLL_DRAIN_INTERVAL=100 # 頻繁に drain
|
|||
|
|
export HAKMEM_TINY_SLAB_REUSE_GUARD=1 # ガード有効
|
|||
|
|
./mimalloc-bench/out/bench/larson 10 5 500000
|
|||
|
|
|
|||
|
|
# 診断モード
|
|||
|
|
export HAKMEM_TINY_SLL_DIAG=1
|
|||
|
|
export HAKMEM_TINY_SLAB_REUSE_HISTORY=1
|
|||
|
|
./mimalloc-bench/out/bench/larson 10 5 100000 2>larson_diag.log
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Fuzzing
|
|||
|
|
|
|||
|
|
**ツール**: AFL++ or libFuzzer
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Fuzzing harness for TLS SLL operations
|
|||
|
|
int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
|
|||
|
|
if (size < 4) return 0;
|
|||
|
|
|
|||
|
|
int class_idx = data[0] % TINY_NUM_CLASSES;
|
|||
|
|
uint8_t op = data[1];
|
|||
|
|
|
|||
|
|
switch (op % 4) {
|
|||
|
|
case 0: { // Push
|
|||
|
|
void* ptr = allocate_test_block(class_idx);
|
|||
|
|
tls_sll_push(class_idx, ptr, 1000);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 1: { // Pop
|
|||
|
|
void* out;
|
|||
|
|
tls_sll_pop(class_idx, &out);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 2: { // Drain
|
|||
|
|
tiny_tls_sll_drain(class_idx, data[2]);
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
case 3: { // Slab reuse
|
|||
|
|
// Simulate slab reuse
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Part 4: リファクタリング計画
|
|||
|
|
|
|||
|
|
### 段階的な改善ステップ
|
|||
|
|
|
|||
|
|
#### Phase 1: 即座の修正(1-2 日)
|
|||
|
|
|
|||
|
|
**目標**: Larson double-free バグを修正
|
|||
|
|
|
|||
|
|
| ステップ | ファイル | 変更内容 | 工数 |
|
|||
|
|
|---------|---------|---------|------|
|
|||
|
|
| 1.1 | `tiny_superslab_alloc.inc.h` | Slab refill 時の TLS SLL drain | 小 |
|
|||
|
|
| 1.2 | `tls_sll_drain_box.h` | Adaptive drain interval | 小 |
|
|||
|
|
| 1.3 | テスト | Larson 100 runs → 0% crash | 中 |
|
|||
|
|
|
|||
|
|
**期待される結果**:
|
|||
|
|
- ✅ Larson ベンチマークで double-free が発生しない
|
|||
|
|
- ✅ 既存の性能を維持(変更は診断コードのみ)
|
|||
|
|
|
|||
|
|
#### Phase 2: ガード機構の導入(3-5 日)
|
|||
|
|
|
|||
|
|
**目標**: 再発防止のための予防的チェック
|
|||
|
|
|
|||
|
|
| ステップ | ファイル | 変更内容 | 工数 |
|
|||
|
|
|---------|---------|---------|------|
|
|||
|
|
| 2.1 | `box/slab_reuse_guard_box.h` | Reuse guard 実装 | 中 |
|
|||
|
|
| 2.2 | `hakmem_shared_pool.c` | Guard 統合 | 小 |
|
|||
|
|
| 2.3 | テスト | 単体テスト作成 | 中 |
|
|||
|
|
|
|||
|
|
**期待される結果**:
|
|||
|
|
- ✅ Slab 再利用時に TLS SLL の残留を検出
|
|||
|
|
- ✅ 開発ビルドで自動 drain、本番ビルドで ENV ゲート
|
|||
|
|
|
|||
|
|
#### Phase 3: 診断機能の強化(5-7 日)
|
|||
|
|
|
|||
|
|
**目標**: デバッグ効率の向上
|
|||
|
|
|
|||
|
|
| ステップ | ファイル | 変更内容 | 工数 |
|
|||
|
|
|---------|---------|---------|------|
|
|||
|
|
| 3.1 | `box/tls_sll_tracked_box.h` | Tracked push/pop マクロ | 中 |
|
|||
|
|
| 3.2 | 診断統計 | TLS SLL inventory tracking | 小 |
|
|||
|
|
| 3.3 | 診断統計 | Slab reuse history | 中 |
|
|||
|
|
| 3.4 | ドキュメント | `MEMORY_LIFECYCLE.md` 作成 | 中 |
|
|||
|
|
|
|||
|
|
**期待される結果**:
|
|||
|
|
- ✅ TLS SLL の挙動を可視化
|
|||
|
|
- ✅ Slab 再利用のヒストリーを追跡
|
|||
|
|
- ✅ 将来的な meta->used 同期の準備
|
|||
|
|
|
|||
|
|
#### Phase 4: アーキテクチャ改善(14-21 日)
|
|||
|
|
|
|||
|
|
**目標**: 根本的な解決(meta->used の正確性)
|
|||
|
|
|
|||
|
|
| ステップ | ファイル | 変更内容 | 工数 |
|
|||
|
|
|---------|---------|---------|------|
|
|||
|
|
| 4.1 | `superslab_types.h` | `TinySlabMeta.used_tls_sll` フィールド追加 | 小 |
|
|||
|
|
| 4.2 | `box/tls_sll_tracked_box.h` | `used_tls_sll` の increment/decrement | 中 |
|
|||
|
|
| 4.3 | `box/ss_hot_cold_box.h` | Empty 判定を `used + used_tls_sll` に変更 | 小 |
|
|||
|
|
| 4.4 | 全体統合 | Tracked マクロを全箇所に適用 | 大 |
|
|||
|
|
| 4.5 | テスト | 統合テスト、Larson 長時間実行 | 中 |
|
|||
|
|
|
|||
|
|
**期待される結果**:
|
|||
|
|
- ✅ `meta->used` が常に正確
|
|||
|
|
- ✅ TLS SLL 内のポインタも追跡
|
|||
|
|
- ✅ Empty 判定が正しく動作
|
|||
|
|
|
|||
|
|
### 優先順位の提案
|
|||
|
|
|
|||
|
|
| フェーズ | 緊急度 | 重要度 | 推奨順序 |
|
|||
|
|
|---------|-------|-------|---------|
|
|||
|
|
| Phase 1 | 🔴 高 | 🔴 高 | 1位(即座に実施) |
|
|||
|
|
| Phase 2 | 🟡 中 | 🔴 高 | 2位(Phase 1 の後) |
|
|||
|
|
| Phase 3 | 🟢 低 | 🟡 中 | 3位(並行可能) |
|
|||
|
|
| Phase 4 | 🟢 低 | 🔴 高 | 4位(長期計画) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## Part 5: 設計ドキュメント草案
|
|||
|
|
|
|||
|
|
### ポインタライフサイクル図
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
┌───────────────────────────────────────────────────────────────────┐
|
|||
|
|
│ HAKMEM Tiny Pool - Pointer Lifecycle │
|
|||
|
|
└───────────────────────────────────────────────────────────────────┘
|
|||
|
|
|
|||
|
|
States:
|
|||
|
|
UNCARVED → CARVED → ALLOCATED → IN_TLS_SLL → DRAINING → FREELIST → EMPTY → REUSABLE
|
|||
|
|
|
|||
|
|
Critical Paths:
|
|||
|
|
1. Alloc (Fast): UNCARVED → CARVED → ALLOCATED
|
|||
|
|
2. Free (Fast): ALLOCATED → IN_TLS_SLL
|
|||
|
|
3. Free (Slow): ALLOCATED → FREELIST
|
|||
|
|
4. Drain: IN_TLS_SLL → DRAINING → FREELIST
|
|||
|
|
5. Reuse: EMPTY → REUSABLE → UNCARVED
|
|||
|
|
|
|||
|
|
Invariants:
|
|||
|
|
I1: meta->used = allocated + freelist + tls_sll (total blocks not in UNCARVED)
|
|||
|
|
I2: is_empty ⇔ (meta->used == 0) ∧ (tls_sll_refs == 0)
|
|||
|
|
I3: reusable ⇔ is_empty ∧ tiny_slab_can_reuse() == true
|
|||
|
|
|
|||
|
|
Guards:
|
|||
|
|
G1: tiny_slab_can_reuse() - Prevents reuse when TLS SLL has refs
|
|||
|
|
G2: tls_sll_push() duplicate check - Prevents double-free in TLS SLL
|
|||
|
|
G3: tiny_free_local_box() - Only path that decrements meta->used
|
|||
|
|
|
|||
|
|
Synchronization Points:
|
|||
|
|
S1: Periodic drain (every N frees) - Syncs TLS SLL → freelist
|
|||
|
|
S2: Slab refill - Forces drain if TLS SLL has refs
|
|||
|
|
S3: Slab reuse - Blocks if TLS SLL has refs
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 不変条件リスト
|
|||
|
|
|
|||
|
|
#### I1: Meta->used の正確性
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Definition:
|
|||
|
|
// meta->used counts all blocks that have been carved but not returned to freelist
|
|||
|
|
// INCLUDING blocks in TLS SLL (not yet drained)
|
|||
|
|
|
|||
|
|
meta->used == SUM(state == ALLOCATED || state == IN_TLS_SLL || state == FREELIST)
|
|||
|
|
|
|||
|
|
// Corollary:
|
|||
|
|
// Empty detection requires checking BOTH meta->used AND TLS SLL
|
|||
|
|
is_empty(slab) ⟺ (meta->used == 0) ∧ (tls_sll_count(slab) == 0)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### I2: TLS SLL の一貫性
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Definition:
|
|||
|
|
// TLS SLL contains only pointers that:
|
|||
|
|
// 1. Were allocated from this allocator
|
|||
|
|
// 2. Have valid headers (magic + class_idx)
|
|||
|
|
// 3. Are still in ALLOCATED or IN_TLS_SLL state
|
|||
|
|
|
|||
|
|
∀ptr ∈ TLS_SLL[class]:
|
|||
|
|
- ptr is BASE pointer (user - 1)
|
|||
|
|
- header[ptr].magic == HEADER_MAGIC
|
|||
|
|
- header[ptr].class_idx == class
|
|||
|
|
- state[ptr] == IN_TLS_SLL
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### I3: Slab 再利用の安全性
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Definition:
|
|||
|
|
// Slab can be reused only if:
|
|||
|
|
// 1. meta->used == 0 (all blocks returned)
|
|||
|
|
// 2. No TLS SLL contains pointers from this slab
|
|||
|
|
// 3. Remote queues are drained
|
|||
|
|
|
|||
|
|
tiny_slab_can_reuse(ss, slab_idx, class) ⟺
|
|||
|
|
(meta->used == 0) ∧
|
|||
|
|
(tls_sll_count_refs(ss, slab_idx, class) == 0) ∧
|
|||
|
|
(remote_count[slab_idx] == 0)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### I4: Drain の冪等性
|
|||
|
|
|
|||
|
|
```c
|
|||
|
|
// Definition:
|
|||
|
|
// Draining an empty TLS SLL is a no-op
|
|||
|
|
|
|||
|
|
TLS_SLL[class].count == 0 ⟹ tiny_tls_sll_drain(class, N) == 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### API 契約の明文化
|
|||
|
|
|
|||
|
|
#### `tls_sll_push(class_idx, ptr, capacity)`
|
|||
|
|
|
|||
|
|
**事前条件**:
|
|||
|
|
- `0 <= class_idx < TINY_NUM_CLASSES`
|
|||
|
|
- `ptr` は BASE ポインタ(user - 1)
|
|||
|
|
- `ptr` は現在 TLS SLL に含まれていない(重複不可)
|
|||
|
|
- `capacity > 0`
|
|||
|
|
|
|||
|
|
**事後条件**:
|
|||
|
|
- 成功時: `TLS_SLL[class].count++`, `ptr` がリストの先頭
|
|||
|
|
- 失敗時: 状態変更なし
|
|||
|
|
- **重要**: `meta->used` は変更されない
|
|||
|
|
|
|||
|
|
**副作用**:
|
|||
|
|
- Header 書き戻し(クラス 1-6 のみ)
|
|||
|
|
- 重複チェック(debug ビルドのみ、先頭 256 要素)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
#### `tls_sll_pop(class_idx, *out)`
|
|||
|
|
|
|||
|
|
**事前条件**:
|
|||
|
|
- `0 <= class_idx < TINY_NUM_CLASSES`
|
|||
|
|
- `out != NULL`
|
|||
|
|
|
|||
|
|
**事後条件**:
|
|||
|
|
- 成功時: `TLS_SLL[class].count--`, `*out` に BASE ポインタ
|
|||
|
|
- 失敗時: `return false`, `*out` は未定義
|
|||
|
|
- **重要**: `meta->used` は変更されない
|
|||
|
|
|
|||
|
|
**副作用**:
|
|||
|
|
- Next ポインタのクリア(リスト切り離し)
|
|||
|
|
- Header 検証(debug ビルド)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
#### `tiny_tls_sll_drain(class_idx, batch_size)`
|
|||
|
|
|
|||
|
|
**事前条件**:
|
|||
|
|
- `0 <= class_idx < TINY_NUM_CLASSES`
|
|||
|
|
- `batch_size == 0` → すべて drain
|
|||
|
|
|
|||
|
|
**事後条件**:
|
|||
|
|
- `N = min(batch_size, TLS_SLL[class].count)` 個の要素を drain
|
|||
|
|
- 各要素に対して `tiny_free_local_box()` 呼び出し
|
|||
|
|
- **重要**: `meta->used -= N`(freelist 経由)
|
|||
|
|
|
|||
|
|
**副作用**:
|
|||
|
|
- Freelist への追加
|
|||
|
|
- `meta->used` の decrement
|
|||
|
|
- Empty マーク(`meta->used == 0` の場合)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
#### `tiny_slab_can_reuse(ss, slab_idx, class_idx)`
|
|||
|
|
|
|||
|
|
**事前条件**:
|
|||
|
|
- `ss` は有効な SuperSlab
|
|||
|
|
- `0 <= slab_idx < ss_slabs_capacity(ss)`
|
|||
|
|
- `meta->used == 0`(caller が確認済み)
|
|||
|
|
|
|||
|
|
**事後条件**:
|
|||
|
|
- 成功時: `return true`, TLS SLL に refs なし
|
|||
|
|
- 失敗時: `return false`, TLS SLL に refs あり(drain 必要)
|
|||
|
|
|
|||
|
|
**副作用** (debug ビルドのみ):
|
|||
|
|
- TLS SLL の自動 drain(refs が見つかった場合)
|
|||
|
|
- ログ出力
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 付録 A: 主要ファイルのサマリー
|
|||
|
|
|
|||
|
|
### `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_box.h`
|
|||
|
|
|
|||
|
|
**役割**: TLS SLL のコア実装(push/pop/splice)
|
|||
|
|
|
|||
|
|
**主要関数**:
|
|||
|
|
- `tls_sll_push_impl()`: 要素を TLS SLL に追加(重複チェック付き)
|
|||
|
|
- `tls_sll_pop_impl()`: 要素を TLS SLL から取り出し
|
|||
|
|
- `tls_sll_splice()`: チェーンを一括で TLS SLL に統合
|
|||
|
|
|
|||
|
|
**重要な設計**:
|
|||
|
|
- すべて BASE ポインタで動作
|
|||
|
|
- Header の読み書き(クラス 1-6)
|
|||
|
|
- 重複検出(debug ビルド、先頭 256 要素)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### `/mnt/workdisk/public_share/hakmem/core/box/tls_sll_drain_box.h`
|
|||
|
|
|
|||
|
|
**役割**: Periodic drain(Option B)の実装
|
|||
|
|
|
|||
|
|
**主要関数**:
|
|||
|
|
- `tiny_tls_sll_drain()`: TLS SLL から freelist に移動
|
|||
|
|
- `tiny_tls_sll_try_drain()`: Counter ベースの自動 drain
|
|||
|
|
|
|||
|
|
**設計**:
|
|||
|
|
- デフォルト間隔: 2048 frees
|
|||
|
|
- ENV: `HAKMEM_TINY_SLL_DRAIN_INTERVAL` で調整可能
|
|||
|
|
- **重要**: `tiny_free_local_box()` 経由で `meta->used--`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### `/mnt/workdisk/public_share/hakmem/core/box/free_local_box.c`
|
|||
|
|
|
|||
|
|
**役割**: Freelist への追加 + `meta->used` の decrement
|
|||
|
|
|
|||
|
|
**主要関数**:
|
|||
|
|
- `tiny_free_local_box()`: **唯一の `meta->used--` 実行箇所**
|
|||
|
|
|
|||
|
|
**フロー**:
|
|||
|
|
1. BASE ポインタ計算(user - 1)
|
|||
|
|
2. Freelist に push
|
|||
|
|
3. **`meta->used--`** ← ここが重要
|
|||
|
|
4. `meta->used == 0` なら `ss_mark_slab_empty()`
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### `/mnt/workdisk/public_share/hakmem/core/tiny_free_fast_v2.inc.h`
|
|||
|
|
|
|||
|
|
**役割**: Fast free path(Header ベース)
|
|||
|
|
|
|||
|
|
**主要関数**:
|
|||
|
|
- `hak_tiny_free_fast_v2()`: Header から class 読み取り → TLS SLL に push
|
|||
|
|
|
|||
|
|
**フロー**:
|
|||
|
|
1. Header から `class_idx` 読み取り(2-3 cycles)
|
|||
|
|
2. `tls_sll_push(class_idx, base, UINT32_MAX)`
|
|||
|
|
3. Periodic drain トリガー(2048 frees ごと)
|
|||
|
|
|
|||
|
|
**重要**: `meta->used` は変更しない(drain 時に初めて更新)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### `/mnt/workdisk/public_share/hakmem/core/hakmem_shared_pool.c`
|
|||
|
|
|
|||
|
|
**役割**: SuperSlab の割り当てと slot 管理
|
|||
|
|
|
|||
|
|
**主要関数**:
|
|||
|
|
- `shared_pool_acquire_slab()`: 3-stage allocation
|
|||
|
|
- Stage 1: EMPTY slots(最優先)
|
|||
|
|
- Stage 2: UNUSED slots
|
|||
|
|
- Stage 3: 新規 SuperSlab
|
|||
|
|
|
|||
|
|
**問題箇所**: Stage 1 で EMPTY slot を再利用する時、TLS SLL のチェックがない
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 付録 B: 環境変数リファレンス
|
|||
|
|
|
|||
|
|
| 環境変数 | デフォルト | 説明 | ファイル |
|
|||
|
|
|---------|-----------|------|---------|
|
|||
|
|
| `HAKMEM_TINY_SLL_DIAG` | 0 | TLS SLL 詳細診断 | tls_sll_box.h:118 |
|
|||
|
|
| `HAKMEM_TINY_SLL_DRAIN_ENABLE` | 1 | Periodic drain 有効化 | tls_sll_drain_box.h:40 |
|
|||
|
|
| `HAKMEM_TINY_SLL_DRAIN_INTERVAL` | 2048 | Drain 間隔(frees 単位) | tls_sll_drain_box.h:54 |
|
|||
|
|
| `HAKMEM_TINY_SLL_DRAIN_DEBUG` | 0 | Drain 詳細ログ | tls_sll_drain_box.h:121 |
|
|||
|
|
| `HAKMEM_TINY_SLL_DRAIN_ADAPTIVE` | 0 | Class ごとの adaptive 間隔 | (新規提案) |
|
|||
|
|
| `HAKMEM_TINY_SLL_SAFEHEADER` | 0 | Header 上書き防止 | tls_sll_box.h:336 |
|
|||
|
|
| `HAKMEM_TINY_LARSON_FIX` | 0 | Cross-thread free 検出 | tiny_free_fast_v2.inc.h:189 |
|
|||
|
|
| `HAKMEM_TINY_SLAB_REUSE_GUARD` | 0 (本番) | Slab 再利用ガード | (新規提案) |
|
|||
|
|
| `HAKMEM_TINY_TLS_SLL_TRACK_USED` | 0 | TLS SLL inventory 追跡 | (新規提案) |
|
|||
|
|
| `HAKMEM_TINY_META_USED_VERIFY` | 0 | Meta->used 整合性検証 | (新規提案) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## まとめ
|
|||
|
|
|
|||
|
|
### 即座に実施すべき修正
|
|||
|
|
|
|||
|
|
1. **Slab refill 時の TLS SLL drain** (`tiny_superslab_alloc.inc.h`)
|
|||
|
|
- 工数: 小(1-2 時間)
|
|||
|
|
- 影響: Larson double-free の根本修正
|
|||
|
|
|
|||
|
|
2. **Adaptive drain interval** (`tls_sll_drain_box.h`)
|
|||
|
|
- 工数: 小(2-3 時間)
|
|||
|
|
- 影響: Hot classes の drain 頻度 ↑ → `meta->used` 正確性 ↑
|
|||
|
|
|
|||
|
|
### 中期的な改善
|
|||
|
|
|
|||
|
|
3. **Slab reuse guard** (`box/slab_reuse_guard_box.h`)
|
|||
|
|
- 工数: 中(1-2 日)
|
|||
|
|
- 影響: 再発防止の予防的チェック
|
|||
|
|
|
|||
|
|
4. **診断機能の強化**
|
|||
|
|
- 工数: 中(3-5 日)
|
|||
|
|
- 影響: デバッグ効率の向上
|
|||
|
|
|
|||
|
|
### 長期的なアーキテクチャ改善
|
|||
|
|
|
|||
|
|
5. **Meta->used の正確な同期** (`TinySlabMeta.used_tls_sll`)
|
|||
|
|
- 工数: 大(14-21 日)
|
|||
|
|
- 影響: 根本的な解決、将来的な問題の予防
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**END OF REPORT**
|