max_semi_space_size 设置值与实际值不一致的原因分析

简介:

问题由来

因为业务的需求,某 Node.js 性能平台用户需要调节新生代大小,Node.js 的启动参数里面的max_semi_space_size可以设置新生代堆空间的大小。

node --v8-options | grep max_semi -A 3 -B 2
  --min_semi_space_size (min size of a semi-space (in MBytes), the new space consists of twosemi-spaces)
        type: int  default: 0
  --max_semi_space_size (max size of a semi-space (in MBytes), the new space consists of twosemi-spaces)
        type: int  default: 0
  --semi_space_growth_factor (factor by which to grow the new space)
        type: int  default: 2

相关文档里该值是一个以MB为单位的整数,并没有其他特别的约束。

然而,当用户设置 max_semi_space_size 为200时,Node.js 性能平台 GC Trace分析 结果显示 inactive new_space semispace:256MB,说明新生代中未使用的那部分是256MB(有关GC的一些知识可以参阅文档最后的列表),如下图所示。

undefined

我们也线下验证了一下:

  • 当设置 max_smi_space_size 为100,110时,inactive new_space semispace:128MB
  • 当设置 max_smi_space_size 为50,60时,inactive new_space semispace:64MB

那么问题来了,是 Node.js 性能平台的bug?还是v8引擎本身的设计就是如此?

问题定位

其实,从最终64/128/256,这些数值就能推测到,设计就是如此。在计算机的世界里,2的整数次幂会带来很多方便。

还是老方法,既然文档里没有明确说明,而 Node.js 性能平台运行时在该部分功能跟社区运行时是完全一致的,那么只能去代码里找原因了。
一番操作之后,在 deps/v8/src/heap/heap.cc 中找到函数:

bool Heap::ConfigureHeap(size_t max_semi_space_size, size_t max_old_space_size, size_t code_range_size))。

这里摘抄一点max_semi_space相关代码,相关注释直接写到代码里面了,感兴趣的同学建议去看看完整代码。

bool Heap::ConfigureHeap(size_t max_semi_space_size, size_t max_old_space_size,
                         size_t code_range_size) {
  if (HasBeenSetUp()) return false;

  // Overwrite default configuration.
  // 未设置 max_semi_space_size 时,默认值是 0 
  if (max_semi_space_size != 0) {
    max_semi_space_size_ = max_semi_space_size * MB;
  }
  ...

  // If max space size flags are specified overwrite the configuration.
  // 命令行 --max_semi_space_size 设置的新生代大小是通过 FLAG_max_semi_space_size 传递到v
  if (FLAG_max_semi_space_size > 0) {
    max_semi_space_size_ = static_cast<size_t>(FLAG_max_semi_space_size) * MB;
  }
  ...

  /* 
  ROUND_UP 的定义:
  // Round up n to be a multiple of sz, where sz is a power of 2.
     #define ROUND_UP(n, sz) (((n) + ((sz) - 1)) & ~((sz) - 1))
  */
  // 操作系统相关的内存页大小,我的ubuntu16.04上该值是 512KB
  if (Page::kPageSize > MB) {
    max_semi_space_size_ = ROUND_UP(max_semi_space_size_, Page::kPageSize);
    ...
  }
  // 该参数默认是false
  if (FLAG_stress_compaction) {
    // This will cause more frequent GCs when stressing.
    max_semi_space_size_ = MB;
  }

  // The new space size must be a power of two to support single-bit testing
  // for containment.
  // 关键点在这里
  // 为什么 50 变成了 64, 100/120 变成了128, 200 变成了 256
  // 下面的函数 RoundUpToPowerOfTwo32 就是这个变化的原因。
  /*
uint32_t RoundUpToPowerOfTwo32(uint32_t value) {
  DCHECK_LE(value, uint32_t{1} << 31);
  if (value) --value;
// Use computation based on leading zeros if we have compiler support for that.
#if V8_HAS_BUILTIN_CLZ || V8_CC_MSVC
  return 1u << (32 - CountLeadingZeros32(value));
#else
  value |= value >> 1;
  value |= value >> 2;
  value |= value >> 4;
  value |= value >> 8;
  value |= value >> 16;
  return value + 1;
#endif
}
  */ 
  max_semi_space_size_ = base::bits::RoundUpToPowerOfTwo32(
      static_cast<uint32_t>(max_semi_space_size_));
  // 这里是 min_semi_space_size 的设置,这里不讨论
  if (FLAG_min_semi_space_size > 0) {
    ...
  }
  
  // 新生代初始大小是 min_semispace_size,如果需要,那么增大到 max_semi_space_size
  initial_semispace_size_ = Min(initial_semispace_size_, max_semi_space_size_);

  if (FLAG_semi_space_growth_factor < 2) {
    FLAG_semi_space_growth_factor = 2;
  }

  ...

  configured_ = true;
  return true;
}

问题结论

max_semi_space_size 设置看起来是个任意整数,但是实际使用中 v8 会把该值转换成一个不小于该值的2的整数次幂的值。也就是说:

  • max_semi_space_size 设置为 33, 34, ..., 64,最终结果都是 64MB。
  • max_semi_space_size 设置为 65, 66, ..., 128,最终结果都是 128MB。
  • 依次类推

heap.cc 里面的注释是

  // The new space size must be a power of two to support single-bit testing
  // for containment.

相关知识

目录
相关文章
|
文字识别 计算机视觉 开发者
基于QT的OCR和opencv融合框架FastOCRLearn实战
本文介绍了在Qt环境下结合OpenCV库构建OCR识别系统的实战方法,通过FastOCRLearn项目,读者可以学习Tesseract OCR的编译配置和在Windows平台下的实践步骤,文章提供了技术资源链接,帮助开发者理解并实现OCR技术。
981 9
基于QT的OCR和opencv融合框架FastOCRLearn实战
|
机器学习/深度学习 人工智能 测试技术
阿里云百炼已上线超强推理开源模型QwQ-32B,尺寸更小,性能比肩DeepSeek满血版
通义千问团队推出了320亿参数的QwQ-32B模型,通过大规模强化学习和多阶段训练,在数学、编程及通用能力上达到或超越了DeepSeek-R1等先进模型。QwQ-32B模型已在阿里云百炼上线,支持API调用,用户可通过官方文档了解详细使用方法。未来,团队将继续探索智能体与RL集成,推动人工通用智能的发展。
10191 29
|
JavaScript 前端开发
Element_文件上传&&多个文件上传
Element_文件上传&&多个文件上传
1461 0
|
存储 JSON 数据格式
什么情况,一夜之间冲上热搜,狂揽29.6k星,再见吧SQLite!这个嵌入式分析引擎实在太香了
DuckDB是一款嵌入式OLAP数据库,专为高效分析型查询设计,被誉为“分析型SQLite”。它采用列式存储和向量化查询引擎,显著提升分析任务性能。无需独立服务器,支持Python、R、Java等语言,安装简单,5分钟即可上手。DuckDB可直接查询CSV、JSON、Parquet文件,支持Pandas零拷贝交互,优化SQL语法简化复杂查询。适用于探索性数据分析、数据湖ETL流水线及边缘设备实时分析等场景,是数据科学家和开发者的理想工具。项目地址:https://github.com/duckdb/duckdb
1507 4
|
JavaScript
Vue使用element中table组件实现单选一行
如何在Vue中使用Element UI的table组件实现单选一行的功能。
1368 6
Vue使用element中table组件实现单选一行
|
Java Linux 开发工具
OpenOffice4: 软件包安装, Docker安装,集成SpringBoot应用
OpenOffice4: 软件包安装, Docker安装,集成SpringBoot应用
OpenOffice4: 软件包安装, Docker安装,集成SpringBoot应用
|
JSON JavaScript 定位技术
Vue结合ECharts绘制省市地图:数据驱动区域颜色展示,支持省市下钻与经纬度打点功能
Vue结合ECharts绘制省市地图:数据驱动区域颜色展示,支持省市下钻与经纬度打点功能
3456 1
|
JavaScript Linux iOS开发
详解如何实现自由切换Node.js版本
不同的项目中需要使用不同版本的 Node.js,有时旧项目需要旧版本,而新项目则可能依赖最新的 Node.js 版本
2710 0
|
Apache
Axure rp9 引入Echarts图表 |手动引入图表 Apache Echarts
Axure rp9 引入Echarts图表 |手动引入图表 Apache Echarts
1588 1
|
域名解析 监控 网络协议
内网穿透介绍
内网穿透介绍

热门文章

最新文章