V8 Number类压缩

V8 Number类压缩

之前知道V8对堆地址和小整数(Smi)即Number类进行了压缩,只保存了低32bit的地址,但调试时就会发现高位地址并非全0,便好奇高位地址的作用是什么。

Smi就是指针是合法的整形。

arduino 复制代码
class Smi: public Object {
public:
    inline int32_t value(){
        return static_cast<int32_t>(reinterpret_cast<intptr_t>(this)) >> 1;
    }
​
    static Smi* fromInt(int32_t value){
        intptr_t tagged_value = (static_cast<intptr_t>(value) << 1) | 0;
        return reinterpret_cast<Smi*>(tagged_value);
    }
​
    static bool isSmi(Smi* ptr){
        return (reinterpret_cast<intptr_t>(ptr) & 1) == 0;
    }
};

原因

任何独特的操作都有其出现的原因。

1. 64位系统的内存利用率问题
  • 在64位系统中,原生指针占用8字节(64位),而JavaScript对象(如对象头、属性指针等)大量使用指针,导致内存浪费。

    • 有一个生活中比较能理解的例子,用过edge的朋友都知道,网页一开多内存的占用会达到非常夸张的巨大程度。
2. 堆内存范围的限制
  • V8将堆内存限制在 4GB或不到的连续虚拟地址空间 内。

    • 4GB的地址空间仅需32位((2^{32} = 4,294,967,296) 字节)即可覆盖,因此低32位足以表示堆内任何对象的偏移量。
3. 高32位的固定基址设计
  • 基址的高位固定 : V8在初始化堆时,分配一块连续内存区域,并确保其起始地址(基址)的高32位是固定的(如0x00001234)。
4. 压缩的收益
  • 内存节省: 将指针从8字节压缩为4字节,显著减少JavaScript对象的内存占用(如对象头、隐藏类、属性指针等),降低GC压力。
  • 性能提升: 更小的内存占用提高了CPU缓存命中率,减少内存带宽消耗,从而加速代码执行。

标记值(Tagged Values)的存储

  • Smi:

    • 64位架构下使用32位存储数值,低32位直接保存整数(符号扩展到64位)。
    • 标记位为最低位0,与指针区分。
  • 指针 :

    • 低32位存储从基地址开始的偏移量,标记位为01(区分强/弱引用)。
    • 高32位通过基地址隐式确定,无需存储。

所以高位地址的作用是什么?同样是32位内容的存储。

性能优化过程

  1. 分支优化 :发现有分支解压代码比无分支快7%(依赖CPU分支预测)。
  2. 消除冗余操作 :在TurboFan编译器中新增"解压消除"阶段,减少不必要的压缩/解压操作。
  3. 汇编优化 :直接加载并符号扩展32位值,减少指令数。
  4. 模式匹配修复 :调整优化阶段的匹配规则,恢复分配预配置等关键优化。
  5. Smi处理改进 :忽略Smi高32位,统一解压逻辑(无条件加基地址)。

回归源代码

v8/src/heap/heap-write-barrier.cc at main · v8/v8

scss 复制代码
#ifdef V8_COMPRESS_POINTERS  // 条件编译:仅在启用指针压缩时生效
​
// 获取当前线程的标记屏障(Marking Barrier)
// 用于追踪堆中对象的存活状态,确保垃圾回收的正确性
MarkingBarrier* marking_barrier = CurrentMarkingBarrier(host);
​
// 通过标记屏障获取当前Isolate实例(V8的隔离环境)
// IsolateForPointerCompression 是用于指针压缩的上下文封装
IsolateForPointerCompression isolate(marking_barrier->heap()->isolate());
​
// 获取C++堆指针表(管理外部C++对象的压缩指针)
CppHeapPointerTable& table = isolate.GetCppHeapPointerTable();
​
// 获取指针表对应的空间(Space是内存管理的基本单位)
CppHeapPointerTable::Space* space = isolate.GetCppHeapPointerTableSpace();
​
// 从插槽中以宽松内存顺序加载压缩后的外部指针句柄(32位偏移量)
// Relaxed_LoadHandle() 避免不必要的内存屏障开销
ExternalPointerHandle handle = slot.Relaxed_LoadHandle();
​
// 标记该外部指针为存活:
// 1. 通过基地址(隐含在Isolate中) + 偏移量(handle)解压指针
// 2. 在垃圾回收中标记对应的C++对象为存活
// 3. slot.address() 提供原始存储地址,用于记录或更新指针
table.Mark(space, handle, slot.address());
​
#endif  // V8_COMPRESS_POINTERS  // 结束指针压缩条件编译

包括ForRangeImpl 函数中获取页地址也是通过压缩后的地址获取的。

ini 复制代码
Tagged_t compressed_page = tagged_value & kPageMask;

效果

让V8拥有64位应用的性能,同时拥有32位的内存占用。

相关推荐
独行soc4 分钟前
2025年渗透测试面试题总结-某快手-安全工程师(题目+回答)
网络·数据库·python·安全·面试·职场和发展·红蓝攻防
燕雀安知鸿鹄之志哉.33 分钟前
ctfshow WEB web5
安全·web安全·网络安全·系统安全
Sinokap2 小时前
Let’s Encrypt 宣布推出短期证书与 IP 地址支持,推动 Web 安全迈向新高度
前端·tcp/ip·安全·ocr
屎派克3 小时前
物理安全——问答
安全
是懒羊羊吖~5 小时前
RCE漏洞
笔记·安全
网络安全-老纪6 小时前
网络安全-网络安全基础
安全·web安全·php
矿渣渣7 小时前
嵌入式系统安全架构白皮书
安全·安全架构
Sweet_vinegar7 小时前
Wire1
安全·ctf·misc·攻防世界
Blockchina9 小时前
第 4 章 | Solidity安全 权限控制漏洞全解析
安全·web3·区块链·智能合约·solidity
不会kao代码的小王9 小时前
DeepSeek-R1国产大模型实战:从私有化部署到内网穿透远程使用全攻略
学习·安全·ai·stable diffusion·开源