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位的内存占用。

相关推荐
Blurpath3 小时前
免费代理IP服务有哪些隐患?如何安全使用?
网络·安全·ip代理·住宅ip
快消前瞻5 小时前
百度导航广告“焊死”东鹏特饮:商业底线失守,用户安全成隐忧
安全·百度
可怜的Tom被玩弄于股掌之中7 小时前
BUUCTF——ReadlezPHP
安全·web安全·网络安全·网络攻击模型·安全架构
你好我是小美8 小时前
信息收集+初步漏洞打点
安全·web安全·网络安全
weixin_473894778 小时前
Web安全核心内容与常见漏洞总结
安全·web安全
朱包林9 小时前
day10-别名-MD5,aide-堡垒机
linux·运维·安全·ubuntu·centos·云计算
海尔辛11 小时前
学习黑客Kerberos深入浅出:安全王国的门票系统
学习·安全·kerberos·window
Allen_LVyingbo11 小时前
医院网络安全托管服务(MSS)深度解读与实践路径
安全·web安全·健康医疗
20242817李臻11 小时前
李臻20242817_安全文件传输系统项目报告_第12周
数据库·安全
玉笥寻珍12 小时前
医疗信息系统安全防护体系的深度构建与理论实践融合
安全·web安全·安全威胁分析·健康医疗