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

相关推荐
qq_2148032921 分钟前
ArcGIS Runtime与GeoTools融合实践:加密SHP文件的完整读写方案
java·安全·arcgis
海蓝可知天湛2 小时前
Ubuntu24.10禁用该源...+vmware无法复制黏贴“天坑闭环”——从 DNS 诡异解析到 Ubuntu EOL 引发的 apt 404排除折腾记
linux·服务器·安全·ubuntu·aigc·bug
独行soc14 小时前
2025年渗透测试面试题总结-234(题目+回答)
网络·python·安全·web安全·渗透测试·1024程序员节·安全狮
机器学习之心14 小时前
MATLAB基于变权理论和灰色云模型的海岛旅游地生态安全评价
安全·matlab·旅游·灰色云模型
No Big Deal17 小时前
ctfshow-_Web应用安全与防护-Base64多层嵌套解码
安全
Oxo Security18 小时前
【AI安全】检索增强生成(RAG)
人工智能·安全·网络安全·ai
2301_7951672019 小时前
玩转Rust高级应用 如何让让运算符支持自定义类型,通过运算符重载的方式是针对自定义类型吗?
开发语言·后端·算法·安全·rust
合作小小程序员小小店1 天前
web安全开发,在线%服务器日志入侵检测%系统安全开发,基于Python,flaskWeb,正则表达式检测,mysql数据库
服务器·python·安全·web安全·flask·安全威胁分析·安全架构
Fanmeang1 天前
华为防火墙基础功能详解:构建网络安全的基石
运维·网络·安全·华为·防火墙·策略·安全域
数字供应链安全产品选型1 天前
公示 | 悬镜安全通过首批《信息技术 软件物料清单数据格式规范》行业标准符合性试点验证
安全