背景
二零年年末,我所在如视的前端团队针对核心 C 端项目 VR 3D 看房 做了次从 2.0 到 3.0 的系统重构——交互风格、前端架构等等都重新整了遍。灰度阶段前,通过 PerfDog 性能狗 性能分析发现:我们一个 VR 3D 页面在 PC 端占用 120MB 左右内存,在 iPhone 12 上竟然高达 360MB。
在加上业务能力的升级——除了传统实景 VR 之外,我们还新增了虚拟 VR 用以展示房源装修前后的效果对比,这又新增了一个 VR 实例,内存占用已超 700MB。
此外,随着用户的交互(开启地图、逐帧动画等),内存还在不断递增,高峰期已经超过 1G。而 iOS 系统 WebView 内存溢出的阈值最高也才 1.5G,VR 页面已经濒临崩溃。
很好奇为啥会占用了那么多的内存?让我们来简单探究一下吧。
图片内存占用
三维模型一般由面片数据(顶点、线)和贴图组成,内存占用的大头是图片。那一张图片渲染至浏览器占用的内存该怎么计算呢?
一般浏览器渲染图片BitMap选用的是 ARGB_8888:颜色信息由透明度 A(Alpha)与 R(Red),G(Green),B(Blue)四部分组成,每个部分都占 8 位,总共占 32 位。即一个像素:
- A - alpha 透明 8bit(位)
- R - Red 8bit(位)
- G - Green 8bit(位)
- B - Blue 8bit(位)
"1Byte(字节)=8bit(位)" 因此,一个像素会占用四个字节。
所以一张 2048*2048
的图片占用的内存有:2048*2048*4 Byte
,换算成 MB 单位 2048*2048*4/ (1024*1024) Byte = 16MB
。
图片占用的内存跟图片文件体积大小无关,仅跟其分辨率相关。压缩图片目的是为了 CDN 下载速度更快、节省存储空间,但无法节省浏览器占用内存。
"一个像素会占用四个字节" 这个结论适用于绝大部分 PC、macOS 等终端设备,但在移动端并不完全适用,详细内容请往下看。
终端设备
以 iPhone 为例,先统计下历代 iPhone 屏幕信息:
机型 | 逻辑像素 | 渲染像素 | 物理像素 | 设备像素比 DPR | 一个像素用几个字节 |
---|---|---|---|---|---|
iPhone 3G/3Gs | 320*480 | 320*480 | 320*480 | 1 | 4 个字节 |
iPhone 4/4s | 320*480 | 640*960 | 640*960 | 2 | 4 * 2 个字节 |
iPhone 5/5C/5s/SE | 320*568 | 640*1136 | 640*1136 | 2 | 4 * 2 个字节 |
iPhone 6/6s/7/8/SE2 | 375*667 | 750*1334 | 750*1334 | 2 | 4 * 2 个字节 |
iPhone XR/11 | 414*896 | 828*1792 | 828*1792 | 2 | 4 * 2 个字节 |
iPhone X/Xs/11 Pro | 375*812 | 1125*2436 | 1125*2436 | 3 | 4 * 3 个字节 |
iPhone 12 mini | 375*812 | 1125*2436 | 1080*2340 | 3 | 约 4 * 2.88 个字节 |
iPhone 12/12 Pro | 390*844 | 1170*2532 | 1170*2532 | 3 | 4 * 3 个字节 |
iPhone 6/6s/7/8/ Plus | 414*736 | 1242*2208 | 1080*1920 | 3 | 约 4 * 2.61 个字节 |
iPhone Xs Max / 11 Pro Max | 414*896 | 1242*2688 | 1242*2688 | 3 | 4 * 3 个字节 |
iPhone 12 Pro Max | 428*926 | 1284*2778 | 1284*2778 | 3 | 4 * 3 个字节 |
- 物理像素:硬件真实的像素,即屏幕分辨率。
- 逻辑像素:前端使用的像素,即
px
。- 渲染像素:操作系统抽象的像素。
从 iPhone 4 代开始,iPhone 屏幕的物理分辨率是很高的,除了 "iPhone 6/6s/7/8/ Plus" 和 "iPhone 12 mini" 设备之外,iOS 系统基本是把 2 个或 3 个物理像素当作 1 个逻辑像素来使用的(放大倍数了)。
Android 系统则比较凌乱,但本质还是将多个物理像素当作一个逻辑像素来渲染使用。因此,一张2048*2048
图片内存占用换算公式是: (物理分辨率/逻辑像素)*2048*2048*4/ (1024*1024) MB
。
这基本解释了移动端设备图片占用的内存要比 PC 上统计的要多出 2 倍、3 倍甚至 4 倍以上。这也解释了明明是旗舰机型崩溃率反而增加了,比如 iOS 系统 WebView 内存崩溃的阈值固定在 1.5G 以下,旗舰机型 iPhone 12 Pro Max 更加容易达到这个阈值。
介于设备屏幕 LCD、OLED 等材质差异,实际统计会有些许偏差,但是数量级不会有太多出入。
Five 实例内存占用
@realsee/five
是如视基于 Three.js 实现的在浏览器环境中运行的三维空间渲染引擎。创建 Five
实例并渲染一个三维空间需要耗费多少内存呢?
常态情况下,Five
渲染依赖的图片是三维模型的 UV 贴图和一个立方体全景贴图(立方体六个面六张图),如图二、三所示。
因此,我们以贝壳·VR 看房 | 常楹公元 2 室 1 厅 房源为例,其实景 VR 的 UV 贴图有 12 张。
所以,此看房 VR 图片所占用的内存有:
① 常态情况
- PC 端:
2048*2048*4/ (1024*1024) *6 + 512*512*4/ (1024*1024)*12= 108MB
- iPhone 8:
(2048*2048*4/ (1024*1024) *6 + 512*512*4/ (1024*1024)*12) * 2= 216MB
- iPhone 12:
(2048*2048*4/ (1024*1024) *6 + 512*512*4/ (1024*1024)*12) * 3= 324MB
此处分析的这还仅仅是一个实景 VR 依赖图片占用的内存。
② 走点 moveToPano
由于走点为了过渡动画效果,一般会出现两个立方体全景,所以全景图片由 6 张图片变成 12 张。
- PC 端:
2048*2048*4/(1024*1024)*6*2 + 512*512*4/ (1024*1024)*12= 204MB
- iPhone 8:
(2048*2048*4/(1024*1024)*6*2 + 512*512*4/ (1024*1024)*12)*2= 408MB
- iPhone 12:
(2048*2048*4/(1024*1024)*6*2 + 512*512*4/ (1024*1024)*12)*3= 612MB
看此数据,基本解释:
- 高端 iOS 设备比低端 iOS 设备更容易出现黑白屏内存溢出问题。(iOS 端 WebView 内存崩溃的阈值在 1.5G 以下)。
- 全景走点时更加容易内存溢出。
- 除了图片占用内存之外,
Five
涉及的其他部分其实并没有占用过多内存。(也就意味着图片之外的优化空间不多)。
序列帧动画
如图五所示,这是一个如视 Logo 组成的循环关键帧动画:
这张帧动画雪碧图分辨率是14065*265
,占用内存:
- PC 端:
14065*265*4/(1024*1024)=14.21823501586914MB
- iPhone 8:
14065*265*4/(1024*1024)*2= 28.43647003173828MB
- iPhone 12:
14065*265*4/(1024*1024)*3=42.65470504760742MB
将这张雪碧图放在<image>
标签中确实是这样的内存占用。但是,一旦套用 CSS 帧动画实现之后:
|
通过 PerfDog 统计的内存占用却是图片内存的三倍:
- PC 端:
14065*265*4/(1024*1024)*3=42.65470504760742MB
- iPhone 8 端:
14065*265*4/(1024*1024)*2*3= 85.30941009521484MB
- iPhone 12 端:
14065*265*4/(1024*1024)*3*3=127.96411514282227MB
这个三倍是怎么来的,目前尚未找到相关资料,个人猜测的逻辑是:
此处的逐帧动画本质上是个补间动画,用在帧动画中,需要上一帧、当前帧、下一帧 来计算补间动画,同时需要三张图片,所以可能会同时存在三张图片实例。
这个目前尚属猜测逻辑。但需要关注的经验是:逐帧动画慎用,帧数最好限制在 24 帧以内,且占用内存不要超过 20MB。
最后
有兴趣的同学,可以安装 PerfDog 性能狗 工具自己实践一下本文的数据是否存在偏差。