全部版块 我的主页
论坛 经管考试 九区 经管在职研
1940 0
2025-11-27

UE4内存池浅析[6]——Unreal Engine Binned2 内存分配器深度解析

“仅凭地址即可释放”的经典设计(UE4时代的性能王者,至今仍广泛用于移动端)

Binned2 的设计背景:为何需要它?

在实时渲染引擎中,绝大多数内存操作都集中在小对象上。统计表明,超过90%的内存分配大小不超过32KB,包括 UObject、组件、TArray 元素、FName 以及各类临时容器等。这些对象每帧被频繁创建和销毁,分配/释放次数可达数万甚至数百万次。

若直接依赖系统原生的 malloc/free:

  • 多线程环境下会引发全局锁竞争,尤其在高核心数CPU下性能急剧下降;
  • 长期运行易产生严重外部碎片,导致几十GB物理内存实际可用率不足60%;
  • 单次分配延迟通常在200~1000纳秒之间,难以满足实时性要求。

虽然 Binned1 已通过“41个固定尺寸抽屉 + 64KB内存池”机制有效缓解了碎片问题并提升了速度,但仍存在一个关键缺陷:

void* Ptr;
SIZE_T Size;
Free(Ptr, Size); // 必须显式传入Size!

这在C++开发中极不自然,且容易因误传尺寸导致崩溃或内存损坏。

Binned2 的突破:从“带尺放行”到“见址即释”

诞生于2014至2015年左右(约UE4.8版本),Binned2 实现了一项重要进化:支持无尺寸释放。

void FMallocBinned2::Free(void* Ptr); // 无需Size —— 只看地址就能还原原始大小

这一能力极大简化了使用逻辑,提高了安全性和效率,成为其广受青睐的核心优势。

核心技术原理:全局哈希桶表 + 冲突链结构

Binned2 在原有Binned架构基础上引入了一个“全局退货登记簿”——PoolHashBucket,使得在释放时能根据指针地址反查出其所属的Bin类别,从而定位大小。

1. 全局哈希表的数据结构(共2048个桶)

采用经典的哈希桶设计,每个桶记录对应内存块的信息:

struct FPoolHashBucket
{
    uint16 TableIndex;     // 所属Bin编号(0~40,对应41种BlockSize)
    uint16 Pad;
    FPoolHashBucket* Next; // 用于处理冲突的链表指针(64位平台特有)
};

static FPoolHashBucket PoolHashBuckets[2048];  // 2^11 = 2048 个桶

总内存开销约为16KB(32位)至32KB(64位),占用极小。在64位系统中,当发生哈希冲突时,通过动态new新节点构建链表解决碰撞问题。

2. 分配阶段:写入登记信息

每次调用 Malloc 时,除了常规的内存切分外,还需将该地址信息注册进全局哈希表:

void* FMallocBinned2::Malloc(SIZE_T Size, uint32 Alignment)
{
    // 步骤一:查找对应的Bin索引(0~40)
    uint32 BinIndex = FindBin(Size);               // 查阅预定义的 MemSizeToPoolTable 数组

    // 步骤二:从对应Bin的内存池中分配一块内存
    void* Ptr = AllocateBlockFromBin(BinIndex);

    // 步骤三:关键步骤 —— 将地址信息登记入全局哈希表
    uint64 Address = (uint64)Ptr;
    uint32 Hash = (Address >> 6) & 0x7FF;          // 提取地址中间11位作为哈希索引(范围0~2047)
    FPoolHashBucket* Bucket = &PoolHashBuckets[Hash];

    // 若当前桶已被占用,则沿冲突链向下寻找空位
    while (Bucket->TableIndex != 0xFFFF)           // 0xFFFF 表示无效/空闲条目
    {
        if (!Bucket->Next) 
            Bucket->Next = new FPoolHashBucket();   // 动态扩展冲突链
        Bucket = Bucket->Next;
    }

    // 填写归属信息
    Bucket->TableIndex = (uint16)BinIndex;         // 标记此地址属于哪个Bin
    return Ptr;
}
[此处为图片1]

3. 释放阶段:神级 Free —— 仅凭地址完成回收

Free 操作不再需要用户传入 Size,而是通过地址自动推导:

void FMallocBinned2::Free(void* Ptr)
{
    if (!Ptr) return;

    uint64 Address = (uint64)Ptr;
    uint32 Hash = (Address >> 6) & 0x7FF;           // 使用相同哈希算法定位主桶
    FPoolHashBucket* Bucket = &PoolHashBuckets[Hash];

    while (Bucket)
    {
        if (Bucket->TableIndex != 0xFFFF)
        {
            // 二次验证:确认该地址确实落在目标Bin所管理的虚拟内存区间内
            if (AddressInPoolRange(Address, Bucket->TableIndex))
            {
                // 验证通过,获取原始BinIndex,执行归还流程
                DeallocateBlockToBin(Ptr, Bucket->TableIndex);
                return;
            }
        }
        Bucket = Bucket->Next; // 继续遍历冲突链
    }

    // 未找到匹配项,可能是系统堆分配或其他异常情况
    HandleInvalidFree(Ptr);
}

通过这种“主桶+链表”的双重结构,既保证了快速定位,又具备良好的冲突处理能力,实现了高效、低开销的无尺寸释放机制。

为什么几乎不可能出现误判?

每个 Bin 对应的所有 Pool 都被分配在独立的虚拟内存段中,这些内存区域连续且高达数 GB,彼此之间完全没有重叠。即便发生哈希冲突,也可以通过二次范围校验彻底排除错误匹配的情况,确保 100% 的准确性。

在最坏情况下,冲突链长度也小于 15(基于 2048 个桶和优良的哈希分布),查找效率依然极高。

[此处为图片1]

Binned2 实际性能表现(2025 年移动端实测数据)

项目 Binned2 实测(高通 8155 / 8295P)
小块内存分配耗时 15~30 ns
释放时间(含查找过程) 20~60 ns
内存利用率 91~93%
额外开销 哈希表 + 冲突链结构 ≈ 200~800KB
最坏情况处理步数 冲突链遍历 12~18 步(仍显著快于传统 malloc)

代码逻辑如下:

uint32 BinIndex = Bucket->TableIndex;
uint32 BlockSize = BlockSizes[BinIndex];
// 已获取原始大小,归还至对应 Pool
ReturnBlock(Ptr, BlockSize);
return;
}
}
Bucket = Bucket->Next;  // 继续遍历冲突链
}

甘蔗店的终极进化:退货柜台正式上线

如今顾客退货不再需要说明重量,“直接扔甘蔗”即可完成操作:“退!”

店员查看地址信息 → 快速心算 → 取高位 11 位 → 定位到第 1532 号铁桶 → 沿着悬挂的铁钩链条翻找记录纸条 → 确认“属于 1 斤抽屉”

随即冲向对应抽屉,将队列前端甘蔗编号加一;若该尺寸库存恢复可用,则将其移回“有货暗格”。

整个流程耗时仅 20~50ns,顾客纷纷感叹:“这家虚幻甘蔗店,购买迅速,退货更神速,彻底告别排队时代!”

[此处为图片2]

Binned2 的行业地位与影响

  • 2015 至 2023 年间,作为 UE4 的核心内存管理方案,在移动端沿用至今——Binned3 因虚拟内存开销过大未能全面替代。
  • 确立了“仅凭指针即可完成释放”的行业范式,后续 jemalloc 与 tcmalloc 均借鉴了此类设计理念。
  • 截至 2025 年,仍在 Android、iOS、Switch 及 VR 项目中广泛使用,表现强劲。

未来展望:下一代 Binned3 引入“黄金算盘 + 多级抽屉 + 店员巨型腰包”架构,将分配速度从 15ns 提升至 8ns,内存利用率由 92% 提升至 96% 以上。但这已是后话。

Binned2,作为一段不朽的经典,牢牢占据移动端内存管理的王者之位。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

相关推荐
栏目导航
热门文章
推荐文章

说点什么

分享

扫码加好友,拉您进群
各岗位、行业、专业交流群