PowerVR Splay Tree

2025-11-06 18 0

Splay Tree 代码详细分析

整体架构概述

Splay Tree(伸展树)是一种自调整二叉搜索树,通过 Splay 操作将最近访问的节点移动到根部,利用访问局部性原理提高性能。

核心架构图

┌─────────────────────────────────────────────────────────┐
│                   IMG_SPLAY_TREE 节点                    │
├─────────────────────────────────────────────────────────┤
│ - uiFlags: 键值 (用于排序和查找)                        │
│ - psLeft: 左子树指针                                    │
│ - psRight: 右子树指针                                   │
│ - buckets[]: 存储桶数组 (FREE_TABLE_LIMIT个)           │
│ - bHasEltsMapping: 位图 (快速定位非空桶)                │
└─────────────────────────────────────────────────────────┘

典型树结构:
                    ┌─────────┐
                    │ flags=5 │  <- 根节点
                    │ buckets │
                    └────┬────┘
                         │
            ┌────────────┴────────────┐
            │                         │
       ┌────▼────┐               ┌────▼────┐
       │flags=3  │               │flags=8  │
       │buckets  │               │buckets  │
       └────┬────┘               └────┬────┘
            │                         │
       ┌────┴───┐               ┌────┴───┐
       │        │               │        │
    ┌──▼──┐  ┌──▼──┐        ┌──▼──┐  ┌──▼──┐
    │f=1  │  │f=4  │        │f=7  │  │f=9  │
    └─────┘  └─────┘        └─────┘  └─────┘

特点:
- 每个节点包含一个 flags 值
- 每个节点包含多个 buckets (用于 RA)
- 满足二叉搜索树性质: 左 < 根 < 右

核心数据结构

IMG_SPLAY_TREE 结构

// 在 uniq_key_splay_tree.h 中定义
typedef struct _IMG_SPLAY_TREE {
    IMG_PSPLAY_FLAGS_T uiFlags;        // 键值
    struct _IMG_SPLAY_TREE *psLeft;    // 左子树
    struct _IMG_SPLAY_TREE *psRight;   // 右子树

    // RA 特定字段
    struct _BT_ *buckets[FREE_TABLE_LIMIT];  // 桶数组

#if defined(PVR_CTZLL)
    IMG_ELTS_MAPPINGS bHasEltsMapping; // 位图:标记哪些桶非空
#endif
} IMG_SPLAY_TREE, *IMG_PSPLAY_TREE;

字段详解:

节点内存布局 (64位系统):

┌────────────────────────────────────────────────────┐
│ uiFlags         (8 bytes)                          │
├────────────────────────────────────────────────────┤
│ psLeft          (8 bytes)                          │
├────────────────────────────────────────────────────┤
│ psRight         (8 bytes)                          │
├────────────────────────────────────────────────────┤
│ buckets[0]      (8 bytes)                          │
│ buckets[1]      (8 bytes)                          │
│ ...                                                │
│ buckets[FREE_TABLE_LIMIT-1]  (8 bytes)            │
├────────────────────────────────────────────────────┤
│ bHasEltsMapping (8 bytes, 如果定义 PVR_CTZLL)     │
└────────────────────────────────────────────────────┘

假设 FREE_TABLE_LIMIT = 64:
总大小 = 8 + 8 + 8 + 64*8 + 8 = 544 bytes

每个节点存储:
1. 一个 flags 值 (用于树的排序)
2. 64 个桶指针 (每个桶是一个 BT 链表)
3. 一个 64 位位图 (标记哪些桶有元素)

与 RA 的集成关系

RA_ARENA 结构:
┌────────────────────────────────────────┐
│ per_flags_buckets: IMG_PSPLAY_TREE*   │ <- 指向 Splay Tree 根
└────────────────┬───────────────────────┘
                 │
                 ▼
         Splay Tree (按 flags 组织)
                 │
      ┌──────────┼──────────┐
      │          │          │
   flags=0    flags=1    flags=2
   ┌─────┐    ┌─────┐    ┌─────┐
   │     │    │     │    │     │
   └──┬──┘    └──┬──┘    └──┬──┘
      │          │          │
   buckets[0..63] buckets[0..63] buckets[0..63]
      │          │          │
      │          │          │
      ▼          ▼          ▼
   BT链表     BT链表     BT链表

说明:
1. Splay Tree 的每个节点代表一种 flags 组合
2. 每个节点内部有 64 个桶 (按大小分类)
3. 每个桶是一个 BT 链表 (具体的空闲段)

Splay 操作详解

Splay 操作原理

核心思想: 将访问的节点通过旋转操作移动到根节点。

为什么要 Splay?
┌────────────────────────────────────────┐
│ 1. 访问局部性原理                       │
│    - 最近访问的数据可能很快再次访问       │
│    - 将其放在根部,下次访问 O(1)         │
│                                        │
│ 2. 自平衡                              │
│    - 无需显式平衡操作 (vs 红黑树)        │
│    - 均摊复杂度 O(log n)                │
│                                        │
│ 3. 简单实现                             │
│    - 只需要旋转操作                     │
│    - 无需颜色标记或平衡因子              │
└────────────────────────────────────────┘

PVRSRVSplay 函数详解

IMG_INTERNAL
IMG_PSPLAY_TREE PVRSRVSplay(IMG_PSPLAY_FLAGS_T uiFlags, IMG_PSPLAY_TREE psTree)
{
    IMG_SPLAY_TREE sTmp1;          // 临时节点
    IMG_PSPLAY_TREE psLeft;        // 左树的根
    IMG_PSPLAY_TREE psRight;       // 右树的根
    IMG_PSPLAY_TREE psTmp2;

    if (psTree == NULL)
    {
        return NULL;
    }

    // 初始化临时节点
    sTmp1.psLeft = NULL;
    sTmp1.psRight = NULL;

    psLeft = &sTmp1;
    psRight = &sTmp1;

    for (;;)
    {
        if (uiFlags < psTree->uiFlags)
        {
            // 目标在左子树
            if (psTree->psLeft == NULL)
            {
                break;
            }

            if (uiFlags < psTree->psLeft->uiFlags)
            {
                // Zig-Zig 情况: 右旋转
                psTmp2 = psTree->psLeft;
                psTree->psLeft = psTmp2->psRight;
                psTmp2->psRight = psTree;
                psTree = psTmp2;
                if (psTree->psLeft == NULL)
                {
                    break;
                }
            }

            // Link Right: 将当前节点链接到右树
            psRight->psLeft = psTree;
            psRight = psTree;
            psTree = psTree->psLeft;
        }
        else if (uiFlags > psTree->uiFlags)
        {
            // 目标在右子树 (对称操作)
            if (psTree->psRight == NULL)
            {
                break;
            }

            if (uiFlags > psTree->psRight->uiFlags)
            {
                // Zag-Zag 情况: 左旋转
                psTmp2 = psTree->psRight;
                psTree->psRight = psTmp2->psLeft;
                psTmp2->psLeft = psTree;
                psTree = psTmp2;
                if (psTree->psRight == NULL)
                {
                    break;
                }
            }

            // Link Left: 将当前节点链接到左树
            psLeft->psRight = psTree;
            psLeft = psTree;
            psTree = psTree->psRight;
        }
        else
        {
            // 找到目标节点
            break;
        }
    }

    // 重新组装树
    psLeft->psRight = psTree->psLeft;
    psRight->psLeft = psTree->psRight;
    psTree->psLeft = sTmp1.psRight;
    psTree->psRight = sTmp1.psLeft;

    return psTree;
}

Splay 操作图解

场景1: Zig-Zig (目标在左-左)

初始树:
        ┌───┐
        │ 7 │
        └─┬─┘
          │
      ┌───▼──┐
      │      │
    ┌─▼─┐    │
    │ 5 │    │
    └─┬─┘    │
      │      │
  ┌───▼──┐   │
  │      │   │
┌─▼─┐    │   │
│ 3 │    │   │
└───┘    │   │
         │   │
       ┌─▼─┐ │
       │ 4 │ │
       └───┘ │
           ┌─▼─┐
           │ 6 │
           └───┘

Splay(3) 第一步: Zig-Zig 右旋转
旋转 5 和 7:

        ┌───┐
        │ 5 │
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 3 │   │ 7 │
    └───┘   └─┬─┘
              │
          ┌───┴───┐
          │       │
        ┌─▼─┐   ┌─▼─┐
        │ 6 │   │...│
        └───┘   └───┘

第二步: 再次右旋转
        ┌───┐
        │ 3 │  <- 目标到根
        └─┬─┘
          │
          └───┐
              │
            ┌─▼─┐
            │ 5 │
            └─┬─┘
              │
              └───┐
                  │
                ┌─▼─┐
                │ 7 │
                └───┘

场景2: Zig-Zag (目标在左-右)

初始树:
        ┌───┐
        │ 7 │
        └─┬─┘
          │
      ┌───▼──┐
      │      │
    ┌─▼─┐    │
    │ 3 │    │
    └─┬─┘    │
      │      │
      └───┐  │
          │  │
        ┌─▼─┐│
        │ 5 ││
        └─┬─┘│
          │  │
      ┌───┴──┴───┐
      │          │
    ┌─▼─┐      ┌─▼─┐
    │ 4 │      │ 6 │
    └───┘      └───┘

Splay(5) 过程:
步骤1: Link Right (7)
步骤2: Link Left (3)

        ┌───┐
        │ 5 │  <- 目标到根
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 3 │   │ 7 │
    └─┬─┘   └─┬─┘
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 4 │   │ 6 │
    └───┘   └───┘

Top-Down Splay 算法详解

算法状态维护:

在 Splay 过程中维护三棵树:
1. Left Tree (L): 收集比目标大的节点
2. Middle Tree (M): 当前正在处理的子树
3. Right Tree (R): 收集比目标小的节点

初始状态:
L = 空
M = 原树
R = 空

处理过程示意:
┌────────────────────────────────────────┐
│    L         M         R                │
│   ┌─┐      ┌─┐      ┌─┐               │
│   │ │      │7│      │ │               │
│   └─┘      └┬┘      └─┘               │
│             │                          │
│           ┌─┴─┐                        │
│           │   │                        │
│         ┌─▼┐  │                        │
│         │5 │  │                        │
│         └┬─┘  │                        │
│          │    │                        │
│        ┌─┴┐   │                        │
│        │3 │   │                        │
│        └──┘   │                        │
└────────────────────────────────────────┘

Splay(3) 过程:
步骤1: 3 < 7, Link Right
┌────────────────────────────────────────┐
│    L         M         R                │
│   ┌─┐      ┌─┐      ┌─┐               │
│   │ │      │5│      │7│               │
│   └─┘      └┬┘      └┬┘               │
│             │        └─ (链接到R)      │
│           ┌─┴─┐                        │
│           │   │                        │
│         ┌─▼┐  │                        │
│         │3 │  │                        │
│         └──┘  │                        │
└────────────────────────────────────────┘

步骤2: 3 < 5, Link Right
┌────────────────────────────────────────┐
│    L         M         R                │
│   ┌─┐      ┌─┐      ┌─┐               │
│   │ │      │3│      │5│               │
│   └─┘      └─┘      └┬┘               │
│                      │                 │
│                    ┌─▼┐               │
│                    │7 │               │
│                    └──┘               │
└────────────────────────────────────────┘

步骤3: 重新组装
        ┌───┐
        │ 3 │  <- 根
        └─┬─┘
          │
          └───┐
              │
            ┌─▼─┐
            │ 5 │
            └─┬─┘
              │
              └───┐
                  │
                ┌─▼─┐
                │ 7 │
                └───┘

重新组装代码:
psLeft->psRight = psTree->psLeft;   // L 的右子树 = M 的左子树
psRight->psLeft = psTree->psRight;  // R 的左子树 = M 的右子树
psTree->psLeft = sTmp1.psRight;     // M 的左子树 = L
psTree->psRight = sTmp1.psLeft;     // M 的右子树 = R

插入操作详解

PVRSRVInsert 函数

IMG_INTERNAL
IMG_PSPLAY_TREE PVRSRVInsert(IMG_PSPLAY_FLAGS_T uiFlags, IMG_PSPLAY_TREE psTree)
{
    IMG_PSPLAY_TREE psNew;

    // 步骤1: 如果树非空,先 Splay
    if (psTree != NULL)
    {
        psTree = PVRSRVSplay(uiFlags, psTree);

        // 如果键已存在,直接返回
        if (psTree->uiFlags == uiFlags)
        {
            return psTree;
        }
    }

    // 步骤2: 分配新节点
    psNew = (IMG_PSPLAY_TREE) OSAllocMem(sizeof(IMG_SPLAY_TREE));
    if (psNew == NULL)
    {
        PVR_DPF((PVR_DBG_ERROR, 
                 "Error: failed to allocate memory for splay tree node."));
        return NULL;
    }

    // 步骤3: 初始化新节点
    psNew->uiFlags = uiFlags;
    OSCachedMemSet(&(psNew->buckets[0]), 0, sizeof(psNew->buckets));

#if defined(PVR_CTZLL)
    // 初始化位图:所有桶都为空
    psNew->bHasEltsMapping = ~(((IMG_ELTS_MAPPINGS) 1 << 
                                 (sizeof(psNew->buckets) / sizeof(psNew->buckets[0]))) - 1);
#endif

    // 步骤4: 插入新节点
    if (psTree == NULL)
    {
        // 空树,新节点成为根
        psNew->psLeft  = NULL;
        psNew->psRight = NULL;
        return psNew;
    }

    if (uiFlags < psTree->uiFlags)
    {
        // 新节点插入到根的左边
        psNew->psLeft  = psTree->psLeft;
        psNew->psRight = psTree;
        psTree->psLeft = NULL;
    }
    else
    {
        // 新节点插入到根的右边
        psNew->psRight  = psTree->psRight;
        psNew->psLeft   = psTree;
        psTree->psRight = NULL;
    }

    return psNew;
}

插入过程图解

场景: 插入 flags=4

初始树:
        ┌───┐
        │ 6 │
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 2 │   │ 8 │
    └─┬─┘   └───┘
      │
      └───┐
          │
        ┌─▼─┐
        │ 5 │
        └───┘

步骤1: Splay(4) - 虽然 4 不存在,但会 Splay 到最接近的位置

执行 Splay(4):
- 4 < 6, 向左
- 4 > 2, 向右
- 到达 5 (最接近的节点)

Splay 后:
        ┌───┐
        │ 5 │  <- 最接近 4 的节点
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 2 │   │ 6 │
    └───┘   └─┬─┘
              │
              └───┐
                  │
                ┌─▼─┐
                │ 8 │
                └───┘

步骤2: 插入新节点 4

因为 4 < 5:
psNew->psLeft  = psTree->psLeft;   // = 2
psNew->psRight = psTree;           // = 5
psTree->psLeft = NULL;

最终树:
        ┌───┐
        │ 4 │  <- 新节点成为根
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 2 │   │ 5 │
    └───┘   └─┬─┘
              │
              └───┐
                  │
                ┌─▼─┐
                │ 6 │
                └─┬─┘
                  │
                  └───┐
                      │
                    ┌─▼─┐
                    │ 8 │
                    └───┘

特点:
- 新插入的节点总是成为根
- O(log n) 均摊时间复杂度
- 利用 Splay 操作自动平衡

位图初始化详解

#if defined(PVR_CTZLL)
    psNew->bHasEltsMapping = ~(((IMG_ELTS_MAPPINGS) 1 << 
                                 (sizeof(psNew->buckets) / sizeof(psNew->buckets[0]))) - 1);
#endif

计算过程:

假设:
- sizeof(psNew->buckets) = 64 * 8 = 512 bytes
- sizeof(psNew->buckets[0]) = 8 bytes
- 桶数量 = 512 / 8 = 64

计算:
步骤1: (IMG_ELTS_MAPPINGS) 1 << 64
     = 0x0000000000000001 << 64
     = 0x0000000000000000 (溢出归零, 但实际是 2^64)

步骤2: 2^64 - 1
     = 0xFFFFFFFFFFFFFFFF (所有64位都是1)

步骤3: ~0xFFFFFFFFFFFFFFFF
     = 0x0000000000000000

结果: bHasEltsMapping = 0 (所有桶都标记为空)

位图含义:
bit 0 = 0: bucket[0] 空
bit 1 = 0: bucket[1] 空
...
bit 63 = 0: bucket[63] 空

当插入第一个元素到 bucket[5]:
bHasEltsMapping |= (1 << 5)
                = 0x0000000000000020

现在:
bit 5 = 1: bucket[5] 非空
其他 bits = 0: 其他桶都空

删除操作详解

VRSRVDelete 函数

IMG_INTERNAL
IMG_PSPLAY_TREE PVRSRVDelete(IMG_PSPLAY_FLAGS_T uiFlags, IMG_PSPLAY_TREE psTree)
{
    IMG_PSPLAY_TREE psTmp;

    if (psTree == NULL)
    {
        return NULL;
    }

    // 步骤1: Splay 目标节点到根
    psTree = PVRSRVSplay(uiFlags, psTree);

    // 步骤2: 检查是否找到
    if (uiFlags == psTree->uiFlags)
    {
        // 找到了,删除根节点
        if (psTree->psLeft == NULL)
        {
            // 没有左子树,右子树成为新根
            psTmp = psTree->psRight;
        }
        else
        {
            // 有左子树,需要合并左右子树
            // 在左子树中 Splay 目标值 (会 Splay 出最大节点)
            psTmp = PVRSRVSplay(uiFlags, psTree->psLeft);
            // 将右子树接到左子树的最大节点
            psTmp->psRight = psTree->psRight;
        }

        // 释放原根节点
        OSFreeMem(psTree);
        return psTmp;
    }

    // 步骤3: 未找到,返回 Splay 后的树
    return psTree;
}

删除过程图解

场景1: 删除节点没有左子树

初始树:
        ┌───┐
        │ 8 │
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 4 │   │ 10│
    └─┬─┘   └───┘
      │
  ┌───┴───┐
  │       │
┌─▼─┐   ┌─▼─┐
│ 2 │   │ 6 │
└───┘   └───┘

Delete(6):

步骤1: Splay(6)
        ┌───┐
        │ 6 │  <- 目标到根
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 4 │   │ 8 │
    └─┬─┘   └─┬─┘
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 2 │   │ 10│
    └───┘   └───┘

步骤2: 删除根节点 6
psTree->psLeft == NULL? NO, 有左子树

执行:
psTmp = PVRSRVSplay(6, psTree->psLeft);
// 在左子树中 Splay, 会将左子树最大节点 (4) 提升到根

        ┌───┐
        │ 4 │  <- 左子树最大节点
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   NULL
    │ 2 │
    └───┘

psTmp->psRight = psTree->psRight;

最终树:
        ┌───┐
        │ 4 │
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   ┌─▼─┐
    │ 2 │   │ 8 │
    └───┘   └─┬─┘
              │
              └───┐
                  │
                ┌─▼─┐
                │ 10│
                └───┘

场景2: 删除节点没有右子树

初始树:
        ┌───┐
        │ 8 │
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   NULL
    │ 4 │
    └─┬─┘
      │
  ┌───┴───┐
  │       │
┌─▼─┐   ┌─▼─┐
│ 2 │   │ 6 │
└───┘   └───┘

Delete(8):

步骤1: Splay(8) - 已经在根

步骤2: 删除根节点 8
psTree->psLeft == NULL? NO
psTmp = PVRSRVSplay(8, psTree->psLeft);

        ┌───┐
        │ 6 │  <- 左子树最大节点
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   NULL
    │ 4 │
    └─┬─┘
      │
    ┌─▼─┐
    │ 2 │
    └───┘

psTmp->psRight = NULL (原本就没有右子树)

最终树:
        ┌───┐
        │ 6 │
        └─┬─┘
          │
      ┌───┴───┐
      │       │
    ┌─▼─┐   NULL
    │ 4 │
    └─┬─┘
      │
    ┌─▼─┐
    │ 2 │
    └───┘

查找操作详解

PVRSRVFindNode 函数

IMG_INTERNAL
IMG_PSPLAY_TREE PVRSRVFindNode(IMG_PSPLAY_FLAGS_T uiFlags, IMG_PSPLAY_TREE psTree)
{
    if (psTree == NULL)
    {
        return NULL;
    }

    while (psTree)
    {
        if (uiFlags == psTree->uiFlags)
        {
            return psTree;  // 找到,直接返回
        }

        if (uiFlags < psTree->uiFlags)
        {
            psTree = psTree->psLeft;  // 向左搜索
            continue;
        }

        if (uiFlags > psTree->uiFlags)
        {
            psTree = psTree->psRight;  // 向右搜索
            continue;
        }
    }

    return NULL;  // 未找到
}

与 PVRSRVSplay 的区别:

┌────────────────────────────────────────────────────────┐
│             PVRSRVSplay vs PVRSRVFindNode              │
├────────────────────────────────────────────────────────┤
│                                                        │
│ PVRSRVSplay:                                          │
│ - 修改树结构 (旋转操作)                               │
│ - 将目标移到根                                         │
│ - 时间: O(log n) 均摊                                 │
│ - 用途: Insert/Delete 前的预处理                      │
│                                                        │
│ PVRSRVFindNode:                                       │
│ - 不修改树结构                                         │
│ - 标准二叉搜索树查找                                   │
│ - 时间: O(h), h 是树高度                              │
│ - 用途: 只读查找,不希望改变树结构                    │
│                                                        │
└────────────────────────────────────────────────────────┘

查找过程图解

树结构:
                ┌───┐
                │ 7 │
                └─┬─┘
                  │
          ┌───────┴───────┐
          │               │
        ┌─▼─┐           ┌─▼─┐
        │ 3 │           │ 10│
        └─┬─┘           └─┬─┘
          │               │
      ┌───┴───┐       ┌───┴───┐
      │       │       │       │
    ┌─▼─┐   ┌─▼─┐  ┌─▼─┐   ┌─▼─┐
    │ 1 │   │ 5 │  │ 8 │   │ 12│
    └───┘   └───┘  └───┘   └───┘

查找 flags=5:
┌──────────────────────────────────────┐
│ 步骤1: psTree = root (7)             │
│        5 < 7, 向左                   │
├──────────────────────────────────────┤
│ 步骤2: psTree = 3                    │
│        5 > 3, 向右                   │
├──────────────────────────────────────┤
│ 步骤3: psTree = 5                    │
│        5 == 5, 找到! 返回节点        │
└──────────────────────────────────────┘

查找 flags=9 (不存在):
┌──────────────────────────────────────┐
│ 步骤1: psTree = root (7)             │
│        9 > 7, 向右                   │
├──────────────────────────────────────┤
│ 步骤2: psTree = 10                   │
│        9 < 10, 向左                  │
├──────────────────────────────────────┤
│ 步骤3: psTree = 8                    │
│        9 > 8, 向右                   │
├──────────────────────────────────────┤
│ 步骤4: psTree = NULL                 │
│        未找到, 返回 NULL             │
└──────────────────────────────────────┘

在 RA 中的实际应用

完整的查找流程

// 在 ra.c 中的使用

// 场景: 分配时查找对应 flags 的 buckets
static IMG_BOOL
_AttemptAllocAligned(RA_ARENA *pArena,
                     RA_LENGTH_T uSize,
                     RA_FLAGS_T uFlags,
                     RA_LENGTH_T uAlignment,
                     RA_BASE_T *base,
                     RA_PERISPAN_HANDLE *phPriv)
{
    IMG_UINT32 index_low;
    IMG_UINT32 index_high;
    IMG_UINT32 i;
    struct _BT_ *pBT = NULL;

    // 步骤1: Splay 查找对应 flags 的节点
    pArena->per_flags_buckets = PVRSRVSplay(uFlags, pArena->per_flags_buckets);

    // 步骤2: 检查是否找到
    if ((pArena->per_flags_buckets == NULL) || 
        (pArena->per_flags_buckets->uiFlags != uFlags))
    {
        // 没有这个 flags 的 bucket
        return IMG_FALSE;
    }

    // 步骤3: 在该节点的 buckets 中查找合适的 BT
    index_low = pvr_log2(uSize);
    // ... 计算 index_high ...

    // 步骤4: 搜索 buckets
    for (i = index_low; i < FREE_TABLE_LIMIT; i++) {
        pBT = find_chunk_in_bucket(pArena->per_flags_buckets->buckets[i], 
                                    uSize, uAlignment, ~0);
        if (pBT) break;
    }

    // ... 分配逻辑 ...
}

Splay 带来的性能优势

场景: RA 中频繁使用相同的 flags

假设有 10 种不同的 flags: 0, 1, 2, ..., 9
使用频率:
- flags=5: 60% (最常用)
- flags=3: 20%
- flags=7: 10%
- 其他: 10%

不使用 Splay (普通 BST):
┌────────────────────────────────────────┐
│ 树结构固定                             │
│ 查找 flags=5 可能需要多次比较          │
│ 平均查找: O(log n) = O(log 10) ≈ 3.3  │
└────────────────────────────────────────┘

使用 Splay:
┌────────────────────────────────────────┐
│ 第一次查找 flags=5: O(log n)          │
│ Splay 后, flags=5 在根                │
│                                        │
│ 后续查找 flags=5: O(1)                │
│ (因为 60% 的访问都是 flags=5)         │
│                                        │
│ 偶尔查找 flags=3: O(log n)            │
│ 然后 flags=3 被 Splay 到根            │
│                                        │
│ 实际平均查找:                          │
│ 0.6 * O(1) + 0.2 * O(log n) + ...    │
│ ≈ 0.6 + 0.66 + ... ≈ 1.5             │
│                                        │
│ 性能提升: 3.3 / 1.5 ≈ 2.2x           │
└────────────────────────────────────────┘

实际使用流程图

RA_Alloc 中的 Splay Tree 使用:

┌────────────────────────────────────────┐
│ 1. 用户请求分配                        │
│    - size = 64KB                       │
│    - flags = 0x123                     │
└────────────┬───────────────────────────┘
             │
             ▼
┌────────────────────────────────────────┐
│ 2. Splay 操作                          │
│    pArena->per_flags_buckets =         │
│    PVRSRVSplay(0x123, per_flags_buckets)│
└────────────┬───────────────────────────┘
             │
             ▼
    ┌────────┴────────┐
    │                 │
   YES               NO
    │                 │
    ▼                 ▼
┌─────────┐    ┌─────────────┐
│ 找到节点│    │ 节点不存在  │
│flags=0x123│  │ 返回失败    │
└────┬────┘    └─────────────┘
     │
     ▼
┌────────────────────────────────────────┐
│ 3. 节点在根,访问 buckets              │
│    node->buckets[index] 是 BT 链表     │
└────────────┬───────────────────────────┘
             │
             ▼
┌────────────────────────────────────────┐
│ 4. 遍历 BT 链表查找合适的块            │
│    for (pBT = buckets[i]; pBT; ...)    │
└────────────┬───────────────────────────┘
             │
             ▼
    ┌────────┴────────┐
    │                 │
   YES               NO
    │                 │
    ▼                 ▼
┌─────────┐    ┌─────────────┐
│ 分配成功│    │ 尝试导入资源│
└─────────┘    └─────────────┘

性能分析

时间复杂度

操作              最坏情况    均摊情况    说明
────────────────────────────────────────────────
Splay             O(n)       O(log n)    自平衡特性
Insert            O(n)       O(log n)    先 Splay 再插入
Delete            O(n)       O(log n)    先 Splay 再删除
FindNode          O(n)       O(h)        不改变结构, h=树高

关键点:
1. 均摊分析 (Amortized Analysis)
   - 虽然单次操作可能 O(n)
   - 但 m 次操作总时间 O(m log n)
   - 均摊每次 O(log n)

2. 访问局部性优化
   - 频繁访问的节点保持在根附近
   - 实际性能优于 O(log n)

3. 最坏情况
   - 退化为链表: O(n)
   - 但通过 Splay 自动恢复平衡

空间复杂度

每个节点的空间:

基本字段:
- uiFlags:       8 bytes
- psLeft:        8 bytes
- psRight:       8 bytes
- buckets[64]:   64 * 8 = 512 bytes
- bHasEltsMapping: 8 bytes
─────────────────────────────
总计:            544 bytes

n 个节点的树:
总空间 = n * 544 bytes

示例:
10 个不同 flags -> 10 * 544 = 5440 bytes ≈ 5.3 KB
100 个不同 flags -> 100 * 544 = 54400 bytes ≈ 53 KB

优势:
- 节点数量 = 不同 flags 的数量
- 通常远小于总的 BT 数量
- 层次结构减少查找时间

与其他数据结构对比

Splay Tree vs 红黑树 vs AVL树 vs 哈希表:

┌────────────────────────────────────────────────────┐
│                  Splay Tree (本实现)                │
├────────────────────────────────────────────────────┤
│ 查找: O(log n) 均摊, 常访问节点 O(1)               │
│ 插入: O(log n) 均摊                                │
│ 删除: O(log n) 均摊                                │
│ 有序: 是                                            │
│ 平衡: 自适应 (无显式平衡)                          │
│ 缓存友好: 好 (最近访问在根附近)                    │
│ 实现: 简单                                          │
│ 最坏情况: O(n) (但均摊 O(log n))                   │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│                     红黑树                          │
├────────────────────────────────────────────────────┤
│ 查找: O(log n) 严格                                │
│ 插入: O(log n) 严格                                │
│ 删除: O(log n) 严格                                │
│ 有序: 是                                            │
│ 平衡: 严格保证                                      │
│ 缓存友好: 中等                                      │
│ 实现: 复杂 (颜色规则)                              │
│ 最坏情况: O(log n) 保证                            │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│                     AVL树                           │
├────────────────────────────────────────────────────┤
│ 查找: O(log n) 严格                                │
│ 插入: O(log n) 严格                                │
│ 删除: O(log n) 严格                                │
│ 有序: 是                                            │
│ 平衡: 更严格 (高度差 ≤ 1)                         │
│ 缓存友好: 好 (树更平衡)                            │
│ 实现: 复杂 (旋转规则)                              │
│ 最坏情况: O(log n) 保证                            │
│ 查找最快, 但插入/删除需要更多旋转                  │
└────────────────────────────────────────────────────┘

┌────────────────────────────────────────────────────┐
│                    哈希表                           │
├────────────────────────────────────────────────────┤
│ 查找: O(1) 平均                                    │
│ 插入: O(1) 平均                                    │
│ 删除: O(1) 平均                                    │
│ 有序: 否                                            │
│ 平衡: N/A                                          │
│ 缓存友好: 差 (随机访问)                            │
│ 实现: 中等                                          │
│ 最坏情况: O(n) (所有键冲突)                        │
│ 不支持有序遍历或范围查询                            │
└────────────────────────────────────────────────────┘

选择建议:
─────────────────────────────────────────
场景1: 需要严格 O(log n) 保证
推荐: 红黑树 或 AVL树

场景2: 频繁查找相同的键 + 需要有序
推荐: Splay Tree ✓ (本实现)

场景3: 只需快速查找, 不需要有序
推荐: 哈希表

场景4: 频繁范围查询
推荐: B树 或 B+树

Splay Tree 的优势与劣势

优势

1. 访问局部性优化
┌────────────────────────────────────────┐
│ 场景: 80% 的访问集中在 20% 的节点     │
│                                        │
│ Splay 效果:                           │
│ - 热点数据自动上浮到根附近             │
│ - 实际访问时间远低于 O(log n)         │
│ - 无需程序员手动优化                   │
└────────────────────────────────────────┘

2. 实现简单
┌────────────────────────────────────────┐
│ vs 红黑树:                             │
│ - 无需颜色标记                         │
│ - 无需复杂的平衡规则                   │
│ - 只需要旋转操作                       │
│                                        │
│ vs AVL树:                              │
│ - 无需平衡因子                         │
│ - 无需多种旋转模式                     │
└────────────────────────────────────────┘

3. 自适应性
┌────────────────────────────────────────┐
│ - 根据访问模式自动调整结构             │
│ - 适应数据分布变化                     │
│ - 无需预知访问模式                     │
└────────────────────────────────────────┘

4. 缓存友好
┌────────────────────────────────────────┐
│ - 热点数据在根附近                     │
│ - 减少缓存失效                         │
│ - 提高实际性能                         │
└────────────────────────────────────────┘

劣势

1. 最坏情况不保证
┌────────────────────────────────────────┐
│ - 单次操作可能 O(n)                    │
│ - 不适合实时系统                       │
│ - 对延迟敏感的应用需谨慎               │
└────────────────────────────────────────┘

2. 改变树结构
┌────────────────────────────────────────┐
│ - 每次访问都可能旋转                   │
│ - 并发访问需要写锁                     │
│ - 只读操作也需要修改结构               │
│                                        │
│ 解决: PVRSRVFindNode 提供只读查找     │
└────────────────────────────────────────┘

3. 性能不稳定
┌────────────────────────────────────────┐
│ - 依赖访问模式                         │
│ - 随机访问时性能退化                   │
│ - 难以预测具体性能                     │
└────────────────────────────────────────┘

RA 中 Splay Tree 的作用总结

层次结构

RA 的三层查找结构:

Level 1: Splay Tree (按 flags 分类)
         ├─ 节点数 = 不同 flags 的数量
         ├─ 通常很小 (< 100)
         └─ O(log n) 均摊, 热点 O(1)

Level 2: Bucket Array (按大小分类)
         ├─ 每个节点 64 个 buckets
         ├─ 索引 = log2(size)
         └─ O(1) 直接索引

Level 3: BT Linked List (具体空闲段)
         ├─ 每个 bucket 一个链表
         ├─ 链表长度依赖负载
         └─ O(k), k = 链表长度

总查找时间:
O(log n) + O(1) + O(k)
≈ O(log n + k)

其中 n << 总BT数, k 通常很小

为什么不用哈希表?

Splay Tree vs 哈希表 (针对 flags 分类):

┌────────────────────────────────────────┐
│ Splay Tree 优势:                       │
├────────────────────────────────────────┤
│ 1. 有序遍历                            │
│    - 可以按 flags 顺序遍历             │
│    - 方便调试和统计                    │
│                                        │
│ 2. 无哈希冲突                          │
│    - 保证 O(log n)                    │
│    - 无需处理冲突                      │
│                                        │
│ 3. 空间效率                            │
│    - 节点数 = flags 种类              │
│    - 无需预分配大表                    │
│                                        │
│ 4. 范围操作                            │
│    - 可以查找相近的 flags             │
│    - 支持区间查询                      │
└────────────────────────────────────────┘

┌────────────────────────────────────────┐
│ 哈希表 劣势 (针对这个场景):           │
├────────────────────────────────────────┤
│ 1. 无序                                │
│    - 无法按 flags 顺序遍历             │
│                                        │
│ 2. 空间浪费                            │
│    - 需要预分配较大的表                │
│    - 负载因子 < 1 时浪费空间           │
│                                        │
│ 3. Resize 开销                         │
│    - flags 数量增长时需要 rehash      │
│    - 峰值延迟高                        │
└────────────────────────────────────────┘

结论:
对于 flags 分类这个特定场景,
Splay Tree 是更好的选择

实际性能表现

典型 RA 使用场景分析:

假设:
- 10 种不同 flags
- 其中 flags=0 占 70% 的访问
- flags=1 占 20% 的访问
- 其他 8 种各占 1.25% 的访问

Splay Tree 行为:
┌────────────────────────────────────────┐
│ 初始状态: 树相对平衡                   │
│                                        │
│ 运行一段时间后:                        │
│ - flags=0 经常被 Splay 到根           │
│ - 70% 的访问: O(1)                    │
│                                        │
│ - flags=1 在 flags=0 附近             │
│ - 20% 的访问: O(2) ≈ O(1)            │
│                                        │
│ - 其他 flags 较深                      │
│ - 10% 的访问: O(log 10) ≈ O(3)       │
│                                        │
│ 加权平均:                              │
│ 0.7*1 + 0.2*2 + 0.1*3 = 1.4          │
│                                        │
│ vs 普通 BST: log2(10) ≈ 3.3          │
│ 性能提升: 2.4x                         │
└────────────────────────────────────────┘

位图优化 (bHasEltsMapping)

位图作用

#if defined(PVR_CTZLL)
    IMG_ELTS_MAPPINGS bHasEltsMapping;
#endif

用途: 快速定位非空 bucket

问题: 查找非空 bucket
┌────────────────────────────────────────┐
│ 朴素方法:                              │
│ for (i = 0; i < 64; i++) {            │
│     if (buckets[i] != NULL) {         │
│         // 找到                        │
│     }                                  │
│ }                                      │
│ 时间: O(64) = O(n)                    │
└────────────────────────────────────────┘

┌────────────────────────────────────────┐
│ 位图优化:                              │
│ i = CTZ(bHasEltsMapping);             │
│ // 直接定位第一个 1 bit               │
│ 时间: O(1)                            │
└────────────────────────────────────────┘

示例:
buckets 状态:
[0]: NULL
[1]: NULL
[2]: ●●●  (有元素)
[3]: NULL
[4]: NULL
[5]: ●    (有元素)
...
[63]: NULL

bHasEltsMapping:
0000...0010 0100
        │   │
        │   └─ bit 2 = 1 (bucket[2] 非空)
        └───── bit 5 = 1 (bucket[5] 非空)

查找第一个非空 bucket:
i = CTZ(bHasEltsMapping) = 2
直接访问 buckets[2]

位图操作

// 在 ra.c 中的使用

// 插入元素到 bucket
_FreeListInsert(RA_ARENA *pArena, BT *pBT)
{
    IMG_UINT32 uIndex = pvr_log2(pBT->uSize);

    // ... 插入 BT 到 buckets[uIndex] ...

#if defined(PVR_CTZLL)
    // 标记该 bucket 非空
    pArena->per_flags_buckets->bHasEltsMapping |= 
        ((IMG_ELTS_MAPPINGS) 1 << uIndex);
#endif
}

// 从 bucket 移除元素
_FreeListRemove(RA_ARENA *pArena, BT *pBT)
{
    IMG_UINT32 uIndex = pvr_log2(pBT->uSize);

    // ... 从 buckets[uIndex] 移除 BT ...

#if defined(PVR_CTZLL)
    if (pArena->per_flags_buckets->buckets[uIndex] == NULL)
    {
        // bucket 变空,清除位
        pArena->per_flags_buckets->bHasEltsMapping &= 
            ~((IMG_ELTS_MAPPINGS) 1 << uIndex);
    }
#endif
}

// 快速查找非空 bucket
_AttemptAllocAligned(...)
{
#if defined(PVR_CTZLL)
    // 快速跳过空 buckets
    i = PVR_CTZLL(
        (~(((IMG_ELTS_MAPPINGS)1 << (index_high + 1)) - 1)) 
        & pArena->per_flags_buckets->bHasEltsMapping
    );
#else
    // 线性搜索
    for (i = index_high + 1; 
         i < FREE_TABLE_LIMIT && buckets[i] == NULL; 
         i++);
#endif
}

位图操作示意

操作序列:

初始状态: bHasEltsMapping = 0x0000000000000000
所有 buckets 都空

1. 插入 16KB 块 (bucket[4])
   bHasEltsMapping |= (1 << 4)
   = 0x0000000000000010

2. 插入 128KB 块 (bucket[7])
   bHasEltsMapping |= (1 << 7)
   = 0x0000000000000090

3. 插入 32KB 块 (bucket[5])
   bHasEltsMapping |= (1 << 5)
   = 0x00000000000000B0

当前状态:
Bit:  7 6 5 4 3 2 1 0
      1 0 1 1 0 0 0 0  = 0xB0
      │   │ │
      │   │ └─ bucket[4] 非空 (16KB)
      │   └─── bucket[5] 非空 (32KB)
      └─────── bucket[7] 非空 (128KB)

4. 查找 >= 64KB 的块
   需要查找 bucket[6] 及以上

   掩码: ~((1 << 7) - 1) = ~0x7F = 0xFF...80
        (清除 bit 0-6, 保留 bit 7+)

   结果: 0xB0 & 0xFF...80 = 0x80

   CTZ(0x80) = 7
   找到 bucket[7]

5. 从 bucket[4] 移除最后一个元素
   if (buckets[4] == NULL)
       bHasEltsMapping &= ~(1 << 4)
       = 0x00000000000000A0

新状态:
Bit:  7 6 5 4 3 2 1 0
      1 0 1 0 0 0 0 0  = 0xA0
      │   │
      │   └─── bucket[5] 非空
      └─────── bucket[7] 非空

完整流程总结

RA 中的完整查找流程

用户请求分配: size=80KB, flags=0x123, alignment=4KB

┌─────────────────────────────────────────────────────┐
│ 步骤1: Splay 操作 (查找 flags=0x123)                │
├─────────────────────────────────────────────────────┤
│ pArena->per_flags_buckets =                         │
│     PVRSRVSplay(0x123, pArena->per_flags_buckets)   │
│                                                     │
│ 树结构变化:                                         │
│ Before:         After:                              │
│    ┌──────┐      ┌──────┐                          │
│    │0x100 │      │0x123 │ <- 目标到根              │
│    └──┬───┘      └──┬───┘                          │
│       │             │                               │
│   ┌───┴───┐    ┌───┴────┐                          │
│   │       │    │        │                          │
│ 0x123   0x200 0x100  0x200                         │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 步骤2: 检查是否找到                                 │
├─────────────────────────────────────────────────────┤
│ if (per_flags_buckets->uiFlags != 0x123)            │
│     return IMG_FALSE;  // 没有这个 flags           │
│                                                     │
│ 找到了, 继续...                                     │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 步骤3: 计算 bucket 索引范围                         │
├─────────────────────────────────────────────────────┤
│ size = 80KB                                         │
│ index_low = log2(80KB) = log2(20*4KB) = 4.32      │
│           = 4 (向下取整)                            │
│                                                     │
│ alignment = 4KB, 可能需要额外空间                   │
│ index_high = log2(80KB + 4KB - 1) = 5              │
│                                                     │
│ 搜索范围: bucket[5] 及以上                          │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 步骤4: 使用位图快速定位非空 bucket                  │
├─────────────────────────────────────────────────────┤
│ #if defined(PVR_CTZLL)                              │
│     mask = ~((1 << 6) - 1)  // 清除 bit 0-5       │
│          = 0xFFFFFFFFFFFFFFC0                       │
│                                                     │
│     available = bHasEltsMapping & mask              │
│               = 0x0000000000000290 & 0xFF...C0      │
│               = 0x0000000000000280                  │
│                                                     │
│     i = CTZ(0x280) = 7                             │
│     直接跳到 bucket[7]                              │
│ #else                                               │
│     // 线性搜索                                     │
│     for (i=6; i<64 && buckets[i]==NULL; i++);      │
│ #endif                                              │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 步骤5: 在 bucket[7] 中查找合适的 BT                │
├─────────────────────────────────────────────────────┤
│ bucket[7] 存储 128~255KB 的 BT 链表                │
│                                                     │
│ pBT = find_chunk_in_bucket(buckets[7],             │
│                            80KB, 4KB, ~0);          │
│                                                     │
│ 遍历链表:                                           │
│ BT1: 150KB, base=0x10000 (4KB对齐) ✓              │
│      150KB >= 80KB, 可用!                          │
└─────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────┐
│ 步骤6: 分割和分配                                   │
├─────────────────────────────────────────────────────┤
│ _AllocAlignSplit(pArena, BT1, 80KB, 4KB, ...)      │
│                                                     │
│ 1. 从 bucket[7] 移除 BT1                           │
│ 2. 分割: 80KB (分配) + 70KB (返回 free)           │
│ 3. 70KB 插入 bucket[6] (64~127KB)                 │
│ 4. 80KB 标记为 live, 加入哈希表                    │
│ 5. 更新位图 (如果 bucket 状态改变)                │
└─────────────────────────────────────────────────────┘

性能分析:
- Splay: O(log m), m = flags 种类, 通常 < 100
- 位图定位: O(1)
- BT 查找: O(k), k = 链表长度, 通常 < 10
- 总计: O(log m + k) ≈ O(log 100 + 10) ≈ O(17)

如果 flags=0x123 是热点:
- 下次 Splay(0x123): O(1) (已在根)
- 总计: O(1 + k) ≈ O(11)

Splay Tree 在系统中的角色

完整的数据结构层次:

┌───────────────────────────────────────────────────────┐
│                   RA_ARENA                            │
│                                                       │
│  per_flags_buckets ──> Splay Tree Root                │
└─────────────────┬─────────────────────────────────────┘
                  │
                  ▼
        ┌─────────────────────┐
        │   Splay Tree Node   │  flags=0x123
        │   uiFlags: 0x123    │
        │   buckets[64]       │
        │   bHasEltsMapping   │
        └──────────┬──────────┘
                   │
        ┌──────────┴──────────┐
        │                     │
   ┌────▼────┐          ┌────▼────┐
   │Node     │          │Node     │
   │flags=0x │          │flags=0x2│
   │100      │          │00       │
   └─────────┘          └─────────┘

每个 Splay Tree 节点:
┌───────────────────────────────────────────────────────┐
│ buckets[0]  -> NULL                                   │
│ buckets[1]  -> NULL                                   │
│ buckets[2]  -> BT(4~7KB) -> BT(6KB) -> NULL           │
│ buckets[3]  -> BT(8~15KB) -> NULL                     │
│ buckets[4]  -> BT(16~31KB) -> BT(20KB) -> BT(28KB)    │
│ ...                                                   │
│ buckets[63] -> NULL                                   │
└───────────────────────────────────────────────────────┘

每个 BT 链表:
┌─────────┐   ┌─────────┐   ┌─────────┐
│BT       │ ->│BT       │-> │BT       │ -> NULL
│base=... │   │base=... │   │base=... │
│size=... │   │size=... │   │size=... │
│type=free│   │type=free│   │type=free│
└─────────┘   └─────────┘   └─────────┘

性能优化总结

Splay Tree 带来的优化:

1. 时间优化
┌────────────────────────────────────────────┐
│ 无 Splay Tree (线性查找 flags):           │
│ - 遍历所有 flags: O(m)                    │
│ - m 个 flags, 平均 m/2 次比较             │
│                                            │
│ 有 Splay Tree:                            │
│ - 查找 flags: O(log m) 均摊               │
│ - 热点 flags: O(1)                        │
│                                            │
│ 提升: 当 m=100, 从 O(50) -> O(1~7)       │
│       约 7~50倍 性能提升                   │
└────────────────────────────────────────────┘

2. 空间优化
┌────────────────────────────────────────────┐
│ 数组方案 (所有可能的 flags):              │
│ - 需要预分配: 2^32 个指针                 │
│ - 空间: 2^32 * 8 = 32GB (!!)              │
│                                            │
│ 哈希表方案:                                │
│ - 需要预分配: 足够大的表                  │
│ - 空间: ~4 * m * 8 bytes                  │
│ - 对于 m=100: 3.2KB                       │
│                                            │
│ Splay Tree 方案:                          │
│ - 按需分配: 只分配实际使用的              │
│ - 空间: m * 544 bytes                     │
│ - 对于 m=100: 53KB                        │
│                                            │
│ 权衡: 比哈希表多用空间 (存储树结构)       │
│      但支持有序操作和范围查询              │
└────────────────────────────────────────────┘

3. 自适应优化
┌────────────────────────────────────────────┐
│ 访问模式: 80-20 原则                       │
│ - 80% 访问集中在 20% 的 flags            │
│                                            │
│ Splay 效果:                               │
│ - 热点自动上浮                             │
│ - 冷数据自然下沉                           │
│ - 无需人工干预                             │
│                                            │
│ 实测效果 (假设):                           │
│ - 热点访问: O(1~2)                        │
│ - 普通访问: O(log m)                      │
│ - 加权平均: O(2~3)                        │
│                                            │
│ vs 平衡树: 始终 O(log m) ≈ O(7)          │
│ 提升: 2~3倍                               │
└────────────────────────────────────────────┘

Splay Tree 关键代码片段分析

旋转操作详解

// Zig-Zig 情况 (右旋转)
if (uiFlags < psTree->psLeft->uiFlags)
{
    /* if we get to this point, we need to rotate right the tree */
    psTmp2 = psTree->psLeft;
    psTree->psLeft = psTmp2->psRight;
    psTmp2->psRight = psTree;
    psTree = psTmp2;
    if (psTree->psLeft == NULL)
    {
        break;
    }
}

图解:

旋转前:
       psTree
         │
       ┌─▼─┐
       │ Y │
       └─┬─┘
         │
    ┌────┴────┐
    │         │
  ┌─▼─┐     ┌─▼─┐
  │ X │     │ C │
  └─┬─┘     └───┘
    │
┌───┴───┐
│       │
A       B

代码执行:
psTmp2 = psTree->psLeft;           // psTmp2 = X
psTree->psLeft = psTmp2->psRight;  // Y->left = B
psTmp2->psRight = psTree;          // X->right = Y
psTree = psTmp2;                   // psTree = X

旋转后:
       psTree
         │
       ┌─▼─┐
       │ X │
       └─┬─┘
         │
    ┌────┴────┐
    │         │
  ┌─▼─┐     ┌─▼─┐
  │ A │     │ Y │
  └───┘     └─┬─┘
              │
         ┌────┴────┐
         │         │
       ┌─▼─┐     ┌─▼─┐
       │ B │     │ C │
       └───┘     └───┘

性质保持:
- A < X < B < Y < C (BST性质)
- X 向上移动 (更接近根)

重新组装操作

/* at this point re-assemble the tree */
psLeft->psRight = psTree->psLeft;
psRight->psLeft = psTree->psRight;
psTree->psLeft = sTmp1.psRight;
psTree->psRight = sTmp1.psLeft;
return psTree;

图解:

Splay 过程中的三棵树:

在循环中:
┌─────────┐  ┌─────────┐  ┌─────────┐
│Left Tree│  │Middle   │  │Right    │
│(较大值) │  │(当前)   │  │(较小值) │
└─────────┘  └─────────┘  └─────────┘
     L            M             R

L 结构 (反向):
sTmp1.psRight -> ... (收集的较大节点)

R 结构 (反向):
sTmp1.psLeft -> ... (收集的较小节点)

重新组装:
步骤1: psLeft->psRight = psTree->psLeft
      将 M 的左子树接到 L 的右端

步骤2: psRight->psLeft = psTree->psRight
      将 M 的右子树接到 R 的左端

步骤3: psTree->psLeft = sTmp1.psRight
      L 成为 M 的左子树

步骤4: psTree->psRight = sTmp1.psLeft
      R 成为 M 的右子树

最终树:
         M (目标节点在根)
        / \
       L   R
      (较小) (较大)

插入时的树分裂

if (uiFlags < psTree->uiFlags)
{
    psNew->psLeft  = psTree->psLeft;
    psNew->psRight = psTree;
    psTree->psLeft = NULL;
}
else
{
    psNew->psRight  = psTree->psRight;
    psNew->psLeft   = psTree;
    psTree->psRight = NULL;
}

图解:

Splay 后树的状态:
         psTree (最接近目标的节点)
           / \
        小的   大的

情况1: 新键 < psTree->uiFlags
Before Splay:
         psTree
           / \
          A   B

插入 psNew:
psNew->psLeft  = psTree->psLeft;  // psNew->left = A
psNew->psRight = psTree;          // psNew->right = psTree
psTree->psLeft = NULL;            // psTree->left = NULL

After:
       psNew
        / \
       A  psTree
             \
              B

所有节点关系:
A < psNew < psTree < B ✓

情况2: 新键 > psTree->uiFlags
Before Splay:
         psTree
           / \
          A   B

插入 psNew:
psNew->psRight  = psTree->psRight; // psNew->right = B
psNew->psLeft   = psTree;          // psNew->left = psTree
psTree->psRight = NULL;            // psTree->right = NULL

After:
       psNew
        / \
    psTree B
      /
     A

所有节点关系:
A < psTree < psNew < B ✓

测试场景与边界情况

空树操作

// 空树插入
IMG_PSPLAY_TREE tree = NULL;
tree = PVRSRVInsert(5, tree);

结果:
┌─────┐
│  5  │ (单节点树)
└─────┘

// 空树 Splay
tree = PVRSRVSplay(5, NULL);

结果: NULL (返回 NULL)

// 空树删除
tree = PVRSRVDelete(5, NULL);

结果: NULL (返回 NULL)

// 空树查找
node = PVRSRVFindNode(5, NULL);

结果: NULL (返回 NULL)

单节点操作

树: [5]

// Splay 存在的键
tree = PVRSRVSplay(5, tree);
结果: [5] (不变)

// Splay 不存在的键
tree = PVRSRVSplay(3, tree);
结果: [5] (不变, 但尝试查找)

// 插入已存在的键
tree = PVRSRVInsert(5, tree);
结果: [5] (不变, Splay 后发现已存在)

// 插入新键
tree = PVRSRVInsert(3, tree);
结果:
  ┌─────┐
  │  3  │
  └──┬──┘
     └───┐
       ┌─▼─┐
       │ 5 │
       └───┘

// 删除唯一节点
tree = PVRSRVDelete(5, tree);
结果: NULL (空树)

退化情况

场景: 按升序插入 1, 2, 3, 4, 5

插入 1:
┌───┐
│ 1 │
└───┘

插入 2:
  ┌───┐
  │ 2 │  (新节点成为根)
  └─┬─┘
    │
┌───▼──┐
│      │
1    NULL

插入 3:
    ┌───┐
    │ 3 │  (新节点成为根)
    └─┬─┘
      │
  ┌───▼──┐
  │      │
  2    NULL
  │
┌─▼─┐
│ 1 │
└───┘

最终树 (插入 1-5):
        ┌───┐
        │ 5 │
        └─┬─┘
          │
      ┌───▼──┐
      │      │
      4    NULL
      │
  ┌───▼──┐
  │      │
  3    NULL
  │
┌─▼─┐
│ 2 │
└─┬─┘
  │
┌─▼─┐
│ 1 │
└───┘

特点:
- 退化为链表
- 高度 = n
- 单次操作 O(n)

但是:
访问任意节点后会自动平衡
例如 Splay(1):
      ┌───┐
      │ 1 │
      └─┬─┘
        │
        └───┐
          ┌─▼─┐
          │ 2 │
          └─┬─┘
            │
            └───┐
              ┌─▼─┐
              │ 3 │
              └─┬─┘
                │
                └───┐
                  ┌─▼─┐
                  │ 4 │
                  └─┬─┘
                    │
                  ┌─▼─┐
                  │ 5 │
                  └───┘

再次 Splay(5):
树变回接近平衡的状态

实现技巧与注意事项

Top-Down vs Bottom-Up

本实现使用 Top-Down Splay:

Top-Down 优势:
┌────────────────────────────────────────┐
│ 1. 单次遍历                            │
│    - 从根到目标只需一次                │
│    - 无需回溯                          │
│                                        │
│ 2. 空间效率                            │
│    - 不需要父指针                      │
│    - 不需要栈                          │
│                                        │
│ 3. 代码简洁                            │
│    - 迭代实现                          │
│    - 易于理解                          │
└────────────────────────────────────────┘

Bottom-Up 对比:
┌────────────────────────────────────────┐
│ 1. 两次遍历                            │
│    - 向下查找                          │
│    - 向上旋转                          │
│                                        │
│ 2. 需要额外信息                        │
│    - 父指针 或 栈                      │
│                                        │
│ 3. 代码复杂                            │
│    - 递归 或 栈管理                    │
└────────────────────────────────────────┘

内存管理

// 分配节点
psNew = (IMG_PSPLAY_TREE) OSAllocMem(sizeof(IMG_SPLAY_TREE));
if (psNew == NULL)
{
    PVR_DPF((PVR_DBG_ERROR, 
             "Error: failed to allocate memory for splay tree node."));
    return NULL;
}

// 初始化
psNew->uiFlags = uiFlags;
OSCachedMemSet(&(psNew->buckets[0]), 0, sizeof(psNew->buckets));

// 释放节点
OSFreeMem(psTree);

注意事项:

1. 分配失败处理
┌────────────────────────────────────────┐
│ - 返回 NULL, 保持原树不变              │
│ - 不会导致数据结构损坏                 │
│ - 上层需要检查返回值                   │
└────────────────────────────────────────┘

2. 初始化
┌────────────────────────────────────────┐
│ - buckets 清零 (所有指针 NULL)        │
│ - bHasEltsMapping 初始化               │
│ - 左右指针由插入逻辑设置               │
└────────────────────────────────────────┘

3. 释放时机
┌────────────────────────────────────────┐
│ Delete 操作:                           │
│ - 先 Splay 到根                       │
│ - 合并子树                             │
│ - 释放根节点                           │
│                                        │
│ 注意: 不要释放仍在树中的节点!         │
└────────────────────────────────────────┘

并发访问考虑

Splay Tree 的并发问题:

问题:
┌────────────────────────────────────────┐
│ Splay 操作修改树结构                   │
│ - 即使只是查找也会旋转                 │
│ - 读操作变成写操作                     │
│ - 需要写锁保护                         │
└────────────────────────────────────────┘

在 RA 中的解决方案:
┌────────────────────────────────────────┐
│ 1. Arena 级别的锁                      │
│    - 所有操作都持有 Arena 锁          │
│    - 自然保证了串行化                  │
│                                        │
│ 2. PVRSRVFindNode 不修改树            │
│    - 只读查找时使用                    │
│    - 但性能不如 Splay                 │
│                                        │
│ 3. 接受 Splay 的语义                  │
│    - "查找"本身就是优化操作           │
│    - 未来访问会更快                    │
└────────────────────────────────────────┘

如果需要并发:
┌────────────────────────────────────────┐
│ 方案1: 读写锁                          │
│    - 读操作: 使用 FindNode (共享锁)   │
│    - 写操作: 使用 Splay (独占锁)      │
│                                        │
│ 方案2: 线程本地树                      │
│    - 每个线程自己的 Splay Tree        │
│    - 无竞争                            │
│                                        │
│ 方案3: 改用其他数据结构                │
│    - 并发友好的跳表                    │
│    - 无锁哈希表                        │
└────────────────────────────────────────┘

总结

Splay Tree 核心特性总结

┌─────────────────────────────────────────────────────┐
│                Splay Tree 特性汇总                  │
├─────────────────────────────────────────────────────┤
│                                                     │
│ 1. 自调整 (Self-Adjusting)                        │
│    └─ 通过 Splay 操作自动优化结构                 │
│                                                     │
│ 2. 访问局部性 (Locality)                          │
│    └─ 热点数据自动上浮到根附近                     │
│                                                     │
│ 3. 均摊 O(log n) (Amortized)                      │
│    └─ 单次可能 O(n), 但长期平均 O(log n)          │
│                                                     │
│ 4. 简单实现 (Simplicity)                          │
│    └─ 只需旋转, 无需平衡因子或颜色                │
│                                                     │
│ 5. 统一操作 (Unified)                             │
│    └─ Insert/Delete/Find 都基于 Splay            │
│                                                     │
│ 6. 自适应 (Adaptive)                              │
│    └─ 根据访问模式动态调整                         │
│                                                     │
└─────────────────────────────────────────────────────┘

在 RA 中的价值

RA 使用 Splay Tree 的理由:

1. 场景匹配
┌────────────────────────────────────────┐
│ RA 的 flags 访问特征:                  │
│ - flags 种类有限 (通常 < 100)         │
│ - 存在热点 flags (20-80 原则)         │
│ - 需要快速查找                         │
│                                        │
│ Splay Tree 正好适合:                  │
│ - 小规模数据集                         │
│ - 有访问局部性                         │
│ - 自动优化热点                         │
└────────────────────────────────────────┘

2. 性能收益
┌────────────────────────────────────────┐
│ vs 线性查找:                           │
│ - 提升: 10~50倍 (取决于 flags 数量)  │
│                                        │
│ vs 普通 BST:                          │
│ - 提升: 2~3倍 (利用访问局部性)       │
│                                        │
│ vs 哈希表:                             │
│ - 支持有序操作                         │
│ - 空间效率更高 (按需分配)             │
└────────────────────────────────────────┘

3. 维护简单
┌────────────────────────────────────────┐
│ - 无需手动平衡                         │
│ - 插入/删除逻辑简单                    │
│ - 调试容易 (BST 性质易验证)           │
└────────────────────────────────────────┘

Splay Tree 与其他组件的配合

完整的 RA 查找链:

┌──────────────────────────────────────────────┐
│ Level 2: Bucket Array (大小分类)            │
│ - 桶数: 64 (FREE_TABLE_LIMIT)               │
│ - 复杂度: O(1) 直接索引                     │
│ - 优化: bHasEltsMapping 位图加速            │
│ - 作用: 按大小快速缩小搜索范围              │
└────────────────┬─────────────────────────────┘
                 │
                 ▼
┌──────────────────────────────────────────────┐
│ Level 3: BT Linked List (具体空闲段)        │
│ - 链表长度: 依赖负载, 通常 < 10            │
│ - 复杂度: O(k), k = 链表长度               │
│ - 策略: 按大小排序 (OPTIMAL 策略)          │
│ - 作用: 找到满足对齐和大小要求的具体块      │
└──────────────────────────────────────────────┘

总查找时间:
O(log m) + O(1) + O(k)
= O(log m + k)

其中:
- m: flags 种类 (< 100)
- k: 链表长度 (< 10)

实际性能:
- 最好: O(1 + 1 + 1) = O(1)  (热点 flags, 第一个 BT 满足)
- 平均: O(3 + 1 + 3) = O(7)  (log 100 ≈ 3, 平均链长 3)
- 最坏: O(7 + 1 + 10) = O(18) (冷 flags, 遍历整个链表)

vs 线性搜索所有 BT:
- O(n), n = 所有 BT 数量 (可能 > 1000)
- 提升: 50~100倍

设计权衡分析

Splay Tree 的设计权衡:

┌────────────────────────────────────────────────┐
│ 优势 (Pros)                                    │
├────────────────────────────────────────────────┤
│ ✓ 自适应性                                     │
│   - 自动优化访问模式                           │
│   - 热点数据快速访问                           │
│                                                │
│ ✓ 实现简单                                     │
│   - 代码简洁                                   │
│   - 易于理解和维护                             │
│                                                │
│ ✓ 空间效率                                     │
│   - 无需额外的平衡信息                         │
│   - 按需分配节点                               │
│                                                │
│ ✓ 缓存友好                                     │
│   - 热点数据在根附近                           │
│   - 减少内存访问                               │
│                                                │
│ ✓ 支持有序操作                                 │
│   - 中序遍历得到有序序列                       │
│   - 支持范围查询                               │
└────────────────────────────────────────────────┘

┌────────────────────────────────────────────────┐
│ 劣势 (Cons)                                    │
├────────────────────────────────────────────────┤
│ ✗ 最坏情况不保证                               │
│   - 单次操作可能 O(n)                          │
│   - 不适合硬实时系统                           │
│                                                │
│ ✗ 修改树结构                                   │
│   - 查找也会旋转                               │
│   - 并发访问需要写锁                           │
│                                                │
│ ✗ 依赖访问模式                                 │
│   - 随机访问时性能退化                         │
│   - 最坏情况接近链表                           │
│                                                │
│ ✗ 难以预测性能                                 │
│   - 性能波动                                   │
│   - 基准测试困难                               │
└────────────────────────────────────────────────┘

在 RA 中的权衡结论:
┌────────────────────────────────────────────────┐
│ ✓ flags 数量有限 (< 100)                      │
│   └─ Splay 劣势影响小                         │
│                                                │
│ ✓ 存在明显的访问热点                           │
│   └─ Splay 优势充分发挥                       │
│                                                │
│ ✓ 不是实时系统                                 │
│   └─ 可以接受偶尔的延迟峰值                   │
│                                                │
│ ✓ 需要有序操作                                 │
│   └─ 支持调试和统计                           │
│                                                │
│ 结论: Splay Tree 是最优选择 ✓                 │
└────────────────────────────────────────────────┘

完整示例:从插入到查找

完整操作序列示例:

初始: 空 Splay Tree

操作1: Insert(flags=0x100)
┌─────────┐
│ 0x100   │
└─────────┘

操作2: Insert(flags=0x200)
      ┌─────────┐
      │ 0x200   │ (新节点成为根)
      └────┬────┘
           │
      ┌────┴────┐
      │         │
   0x100      NULL

操作3: Insert(flags=0x050)
           ┌─────────┐
           │ 0x050   │ (新节点成为根)
           └────┬────┘
                │
           ┌────┴────┐
           │         │
         NULL    ┌───▼────┐
                 │ 0x200  │
                 └───┬────┘
                     │
                ┌────┴────┐
                │         │
             0x100      NULL

操作4: 分配 flags=0x100, size=64KB
步骤4.1: Splay(0x100)
                ┌─────────┐
                │ 0x100   │ (0x100 到根)
                └────┬────┘
                     │
            ┌────────┴────────┐
            │                 │
       ┌────▼────┐       ┌────▼────┐
       │ 0x050   │       │ 0x200   │
       └─────────┘       └─────────┘

步骤4.2: 访问 node(0x100)->buckets
index = log2(64KB) = log2(16*4KB) = 4
bucket = node->buckets[4]

步骤4.3: 在 bucket[4] 的 BT 链表中查找
pBT = find_chunk_in_bucket(bucket[4], 64KB, ...)

操作5: 再次分配 flags=0x100, size=32KB
步骤5.1: Splay(0x100)
- 0x100 已经在根,Splay 操作 O(1) 返回
                ┌─────────┐
                │ 0x100   │ (仍在根)
                └────┬────┘
                     │
            ┌────────┴────────┐
            │                 │
       ┌────▼────┐       ┌────▼────┐
       │ 0x050   │       │ 0x200   │
       └─────────┘       └─────────┘

步骤5.2: 直接访问 node(0x100)->buckets[3]
- 无需遍历树 ✓
- 性能提升显著 ✓

操作6: 分配 flags=0x200, size=128KB
步骤6.1: Splay(0x200)
           ┌─────────┐
           │ 0x200   │ (0x200 到根)
           └────┬────┘
                │
       ┌────────┴────────┐
       │                 │
  ┌────▼────┐       ┌────▼────┐
  │ 0x100   │       │ NULL    │
  └────┬────┘
       │
  ┌────┴────┐
  │         │
0x050     NULL

步骤6.2: 访问 node(0x200)->buckets[7]

观察:
- flags=0x100 使用频率高 -> 经常在根附近
- flags=0x200 使用频率中 -> 根据使用上浮
- flags=0x050 使用频率低 -> 在树的深处
- 自动形成"热-温-冷"的层次结构

性能基准对比

假设场景:
- 10 种不同 flags
- 1000 次操作
- 访问分布: 80% 集中在 2 个热点 flags

┌──────────────────────────────────────────────────────┐
│           数据结构性能对比                            │
├──────────────────────────────────────────────────────┤
│                                                      │
│ 1. 线性搜索链表                                      │
│    - 平均: 遍历 5 个节点                             │
│    - 1000 次 * 5 = 5000 次比较                      │
│    - 基准: 1.0x                                      │
│                                                      │
│ 2. 无序数组 + 线性搜索                               │
│    - 平均: 遍历 5 个元素                             │
│    - 1000 次 * 5 = 5000 次比较                      │
│    - 相对: 1.0x                                      │
│                                                      │
│ 3. 哈希表                                            │
│    - 平均: O(1)                                      │
│    - 1000 次 * 1 = 1000 次查找                      │
│    - 相对: 5.0x 提升                                 │
│    - 但: 无序,额外空间,resize 开销                │
│                                                      │
│ 4. 红黑树                                            │
│    - 平均: log2(10) ≈ 3.3                           │
│    - 1000 次 * 3.3 = 3300 次比较                    │
│    - 相对: 1.5x 提升                                 │
│    - 优点: 严格 O(log n) 保证                       │
│                                                      │
│ 5. AVL 树                                            │
│    - 平均: log2(10) ≈ 3.3 (更平衡)                 │
│    - 1000 次 * 3.2 = 3200 次比较                    │
│    - 相对: 1.56x 提升                                │
│    - 查找最快,但插入/删除旋转多                    │
│                                                      │
│ 6. Splay Tree (80% 热点)                            │
│    - 热点 (800次): 平均 1.5 次比较                  │
│    - 冷点 (200次): 平均 4 次比较                    │
│    - 加权: 800*1.5 + 200*4 = 2000 次比较           │
│    - 相对: 2.5x 提升                                 │
│    - 优势: 自适应热点,有序                         │
│                                                      │
└──────────────────────────────────────────────────────┘

结论:
- 纯查找速度: 哈希表 > Splay Tree > AVL ≈ 红黑树 > 线性
- 有序支持: Splay/AVL/红黑 > 线性 > 哈希表 (不支持)
- 空间效率: Splay/红黑/AVL > 线性 > 哈希表
- 实现复杂度: Splay > 红黑 > AVL > 哈希表 > 线性
- 自适应性: Splay (优秀) > 其他 (无)

综合评分 (针对 RA 场景):
1. Splay Tree: ★★★★★ (最佳选择)
2. 红黑树: ★★★★☆
3. 哈希表: ★★★☆☆
4. AVL 树: ★★★☆☆
5. 线性搜索: ★☆☆☆☆

实际应用建议

何时使用 Splay Tree:

✓ 推荐场景:
┌────────────────────────────────────────┐
│ 1. 访问具有局部性                      │
│    - 80-20 原则明显                    │
│    - 热点数据反复访问                  │
│                                        │
│ 2. 数据集规模适中                      │
│    - 节点数 < 10000                   │
│    - 过大会导致旋转开销高              │
│                                        │
│ 3. 需要有序操作                        │
│    - 范围查询                          │
│    - 顺序遍历                          │
│                                        │
│ 4. 非实时系统                          │
│    - 可以接受偶尔的延迟峰值            │
│    - 关注平均性能                      │
│                                        │
│ 5. 实现简单优先                        │
│    - 代码维护成本低                    │
│    - 调试容易                          │
└────────────────────────────────────────┘

✗ 不推荐场景:
┌────────────────────────────────────────┐
│ 1. 需要严格实时保证                    │
│    - 不能接受 O(n) 最坏情况           │
│    - 使用红黑树或 AVL 树               │
│                                        │
│ 2. 访问完全随机                        │
│    - 无访问局部性                      │
│    - 使用哈希表或红黑树                │
│                                        │
│ 3. 高并发读写                          │
│    - 读操作也修改结构                  │
│    - 使用跳表或并发哈希表              │
│                                        │
│ 4. 只需快速查找                        │
│    - 不需要有序                        │
│    - 使用哈希表更简单                  │
└────────────────────────────────────────┘

代码质量与优化建议

当前实现的优点

✓ 优秀的设计:
┌────────────────────────────────────────┐
│ 1. Top-Down 实现                       │
│    - 单次遍历                          │
│    - 无需父指针                        │
│    - 空间效率高                        │
│                                        │
│ 2. 代码简洁                            │
│    - 逻辑清晰                          │
│    - 易于理解                          │
│    - 注释充分                          │
│                                        │
│ 3. 健壮性                              │
│    - 空指针检查                        │
│    - 内存分配失败处理                  │
│    - 保持树的不变性                    │
│                                        │
│ 4. 集成良好                            │
│    - 与 RA 紧密配合                    │
│    - 支持 RA 的 bucket 结构            │
│    - 位图优化                          │
└────────────────────────────────────────┘

可能的优化方向

潜在优化:

1. 惰性 Splay (Lazy Splay)
┌────────────────────────────────────────┐
│ 当前: 每次访问都 Splay                │
│                                        │
│ 优化: 批量操作后统一 Splay            │
│ - 减少旋转次数                         │
│ - 提高批量操作性能                     │
│ - 但增加实现复杂度                     │
└────────────────────────────────────────┘

2. 半-Splay (Semi-Splaying)
┌────────────────────────────────────────┐
│ 当前: Splay 到根                      │
│                                        │
│ 优化: 只 Splay 部分路径               │
│ - 减少旋转开销                         │
│ - 仍保持大部分优势                     │
│ - 权衡: 性能 vs 简洁性                │
└────────────────────────────────────────┘

3. 缓存根节点
┌────────────────────────────────────────┐
│ 当前: 每次从根开始                     │
│                                        │
│ 优化: 记录最近访问的节点               │
│ - 热点直接命中                         │
│ - 进一步提升热点访问                   │
│ - 增加: 额外的缓存字段                 │
└────────────────────────────────────────┘

4. 统计信息
┌────────────────────────────────────────┐
│ 当前: 无统计                           │
│                                        │
│ 优化: 记录访问频率、树高度等           │
│ - 便于性能分析                         │
│ - 辅助调优决策                         │
│ - 增加: 统计字段和维护成本             │
└────────────────────────────────────────┘

建议: 保持当前实现
理由:
- 简洁性 > 微小性能提升
- RA 场景下已足够高效
- 优化收益不明显
- 增加复杂度不值得

测试建议

推荐的测试场景:

1. 基本功能测试
┌────────────────────────────────────────┐
│ - 空树操作                             │
│ - 单节点操作                           │
│ - 多节点插入/删除/查找                 │
│ - 已存在键的插入                       │
│ - 不存在键的删除                       │
└────────────────────────────────────────┘

2. 性能测试
┌────────────────────────────────────────┐
│ - 顺序插入 (最坏情况)                  │
│ - 随机插入                             │
│ - 重复访问热点                         │
│ - 冷热混合访问                         │
│ - 大量插入后批量删除                   │
└────────────────────────────────────────┘

3. 压力测试
┌────────────────────────────────────────┐
│ - 大规模插入 (10000+ 节点)            │
│ - 内存分配失败模拟                     │
│ - 边界条件 (最大/最小 flags)          │
└────────────────────────────────────────┘

4. 正确性验证
┌────────────────────────────────────────┐
│ - BST 性质检查 (中序遍历有序)         │
│ - 内存泄漏检查                         │
│ - 并发访问 (如果支持)                 │
└────────────────────────────────────────┘

最终总结

Splay Tree 是 RA 系统中 按 flags 分类 的核心数据结构,通过自适应的树结构调整,充分利用访问局部性原理,在简单实现和高效性能之间取得了优秀的平衡。
关键要点回顾:

  1. 核心机制: Top-Down Splay 操作将访问节点移到根
  2. 性能特征: O(log n) 均摊,热点数据 O(1)
  3. 主要优势: 自适应、简单实现、缓存友好
  4. 主要劣势: 最坏情况不保证、修改树结构
  5. 应用价值: 在 RA 的三层查找结构中承担第一层,将 O(m) 线性查找优化为 O(log m),配合访问热点优化实际达到 O(1~3)

这个 Splay Tree 实现是一个教科书级别的例子,展示了如何将经典算法应用到实际系统中,并通过与 RA 的紧密集成(buckets 数组、位图优化)发挥最大价值。

相关文章

PowerVR Rogue ZS Buffer
PowerVR RGX Parameter Buffer And Ring Buffer
PowerVR Rogue FreeList
PowerVR RGX Firmware Utils
PowerVR RGX BVNC
PowerVR RGX Multicore

发布评论