在腾讯文档表格中,如果用户打开表格的内容非常多,比如有几万行或者几十万个单元格。内存占用会居高不下,在使用的过程中非常容易出现崩溃,卡顿等问题,在进行一系列的优化之后,写下这篇文章,总结下期间用到的一些优化方案,希望可以或多或少帮助或者启发一下他人。

怎么找出内存问题

在内存这里,我们一般是借助 Google 浏览器的开发者工具,然后获取下页面的内存快照,然后分享具体占用过大内存的对象。

除了查看内存占用过大的对象之外,也可以切换到 Containment 这里查看一些占用内存过大的全局对象。

常见的内存优化方案

按需加载

按需加载可能是能够想到的最简单的内存优化的方式,比如当某一个功能没有使用的时候,不加载这个功能模块,也不进行初始化。举个例子,比如帐号管理模块,在没有点击右上角头像部分的时候,是不需要加载的,这个时候就可以改成按需加载。

在腾讯文档表格的优化中,部分改造为按需加载的模块使用率:

  1. Account 组件改成按需加载 用户使用率:0.8%

  2. QuickFill 组件改成按需加载 用户使用率:1.2%

优化了这些模块之后,内存占用大概降低了 10M 左右,优化效果并不明显。老板说:

对象池

对象池模式在初始化的时候会创建固定数量的缓存,并且每个对象都可以复用,对象没有更新的需求。

对象池这种模式主要用于游戏或者数据库连接池等场景。因为腾讯文档表格优化的时候没有使用到对象池的方式,这里就不详细描述了,有需要的可以自行 Google。

享元

享元模式的特点:

  • 属性全部只读或者私有化

  • 提供创建对象实例的工厂方法

  • 修改时重新创建对象实例

  • 使用 key 缓存对象,key 相同直接返回缓存对象实例

举个例子:假设目前有三个变量 ABC,引用的都是同一个内存中的对象。

假设这个时候需要修改对象 A 的属性,则需要使用新的属性参数,创建一个新的对象,讲 A 指向新创建的对象,此时内存当中的对象应该是这样的:

这个时候,内存当中就有两个对象了,BC 还是指向的之前的对象,但是这个时候的 A 已经指向新创建的对象了。

下图是在腾讯文档表格中,部分使用享元模式优化之后的内存对比,可以发现优化效果还是比较明显的。

享元遇到的问题

以为用享元就能解决问题了?

直到看到这个图:

用户在操作设置数据格式之后,前端提交到数据后台,落盘失败!

在做享元优化的时候,遇到一个特殊的问题,这里也记录下,方便大家后面使用享元的时候注意。我们有一个对象,用来描述单元格的值,以及值的类型。如下所示:

type 表示类型,value 就是存储的值。

type 的类型有如下几种

这个对象的 key=type+,+value

假设此时实用如下参数创建一个对象:

{type: 1,value: 123,
}

此时生成的 key='1,123'

很明显,这个时候的 value 和 type 的类型对应不上,但是由于我们没有任何校验,导致错误的创建了一个对象,并且以 key='1,123' 被缓存起来了。

当下次创建的一个对象的时候,传入如下的参数:

{type: 1,value: '123',
}

这里的参数,跟上面的区别是 value 和 type 是对应上的,但是注意,这个时候生存的 key='1,123'。嗯?发现这个 key 在之前已经创建过了,并且缓存了一个对象。这个时候,不会再去创建一个新的对象,而是将上面的对象直接返回给到使用方,然后报错了。

这个情况在腾讯文档表格中就出现过,有的业务模块创建了临时的 ExtenedValue 对象,在传参数的时候,类型和值对应不上,导致数据层在提交数据的时候报错。

那如何解决这个问题呢? 在创建 key 的时候,获取到 value 的真实类型,将真实类型作为 key 的一部分,这样,上述的两个参数创建的就会是两个不同的对象了。

进阶版内存优化方案

对象存储优化

一个 demo

上图中的代码,占用内存 4.8M。

上图中的代码,占用内存 3.6M。

对比两者的内存快照发现相差的就是 1.2M。这是为什么呢?

对象在 v8 中的存储方式

首先来看下对象在 v8 中的存储方式,在 v8 中,js 的对象使用 JSObject 对象来描述。这个对象有下面几个主要属性:

  • HiddenClass 用来存储对象属性存储的位置信息

  • properties 用来存储具名属性

  • elements 用来存储有顺序的属性,类似数组 在这种情况下,存储在 properties 中的属性,都是快属性模式(使用数组的形式存储)。

那针对上面的问题,猜测可能是因为

this.a = null

初始化属性 a 的时候,HiddenClass 的结构会变化,从而增加内存。接着使用 v8 的调试工具 d8 来验证下:

从上面的 debug 信息中可以看出,初始化的时候,如果赋值为 null,会在 HiddenClass 的 DescriptorArray 中添加记录,用来描述属性 a 的初始值。 所以,在初始化属性的时候,如果参数没有传递该属性的值,那就不要初始化为 null,因为这样会占用内存。

优化验证

针对上述的情况,我们优化了腾讯文档表格的单元格对象 CellData 的初始化过程。

可以发现,优化的效果非常明显。30w 单元格的表格,CellData 的内存占用从 24M 减少到了 12M。

优化到到这里的时候,老板再问我能达到预期目标的时候:

清除 delete 操作

一个 demo

有 delete 操作

没有 delete 操作

从上面的图中可以看出,没有 delete 操作会比有 delete 操作的内存占用少非常多。这又是为什么呢?

v8 HiddenClass 的转化过程

v8 在初始化对象,给对象添加属性的时候,会变更 hiddenClass 指针的指向,将其指向最新的节点。先看一个 v8 官方网站的图:

在这个图中,当我们给对象 o 添加属性的时候,hiddenClass 的指针一直在变化,也就是说每次给对象添加新的属性,都会生成一个新的 HiddenClass 节点,所有的 HiddenClass 节点组成了一个的链表(其实准确的说应该是 tree ),然后对象中的 hiddenClass 指针指向最新的节点。

上面说的所有的 HiddenClass 节点其实组成了一棵树,在 v8 里面叫做 transition tree。例如:假设这个时候,新创建一个对象,并且添加了一个新的属性:

这样看是不是就知道为什么是一棵树了。

那这些跟 delete 操作有什么关系呢?

delete 操作导致 v8 的对象存储模式退化为字典模式

当对属性进行 delete 操作的时候,会将上面所说的链表结构打破,并且对象在 v8 内的存储方式也会发生变化。变成如下所示的结构:

或者直接看 v8 官方网站提供的图:

可以看到,如果你对一个对象的属性进行 delete 操作,就会导致对象的存储方式退化到字典模式(慢属性模式)。相对于之前的快属性模式,这种存储方式更加消耗内存。所以这也是为什么 delete 操作会导致对象内存占用增加的根本原因。

验证:

const obj = {a:1,b: 2,
}
%DebugPrint(obj);
delete obj.a;
%DebugPrint(obj);

删除属性 a 之后,退化到字典模式:

再看一下这个图,我们发现在 HiddenClass 节点也有一个 back pointer ,指向上一个节点。

所以,如果你是按照对象添加属性的反方向删除属性的话,对象并不会退化到慢属性模式,或者对象的内存占用并不会增加。这里更多详细的描述可以参考 v8 的官方文档或者 superzheng 大佬在知乎的专栏。

如何替换掉 delete 操作?

赋值给 undefined

假设有如下代码:

const data = {a: '1',b: 'c',
}
// do something...
delete data.c;

如果没有特殊的要求,建议直接修改为:

const data = {a: '1',b: 'c',
}
// do something...
data.c = undefined;

使用如下代码测试:

const startTime = +new Date();for (let i = 0; i < 1000000; i++) {const a = {a: '1',b: '2',c: '3',};// a.b = undefined;delete a.b;}const endTime = +new Date();console.log(endTime - startTime);

赋值给 undefined 比直接 delete 删除,要快几十倍,甚至上百倍。

使用 Map 替代

假设你的对象必须就是要删除,那么还可以使用 Map 来替代对象。

const data = {a: '1',b: 'c',
}
// do something...
delete data.c;

可以修改为如下的形式:

const data = new Map<string, string>();
data.set('a', '1');
data.set('b', 'c');
// do something...
// 使用 Map 对象提供的 delete 方法删除元素
data.delete('a');

性能监控(保护优化成果)

内存监控

内存优化的手段固然重要,但是如果在优化之后一段时间,随着需求的增加,由于一些新代码的引入,或者写代码时候不注意,就有可能导致内存暴增。这个时候,就在想是否能够在发布之前,就可以自动化的检测到腾讯文档的特征表格的内存占用,然后跟现网环境的内存占用对比。如果发现内存占用比现网的要高很多,则可以在发布阶段终止发布,接着进入代码 review 阶段,分析哪里导致的内存占用增加。解决了问题之后,再重新进行发布流程。

针对这个自动化测试,腾讯文档这边目前也实现了一个可以自动化获取页面内存快照和大小的工具,可以接入到流水线当中,作为一个性能检测的方式集成到发布流水线当中,保护内存优化成功。

参考资料

  • https://flaviocopes.com/how-to-remove-object-property-javascript/

  • https://v8.dev/blog/fast-properties

  • https://zhuanlan.zhihu.com/p/28872382 https://v8.dev/docs/build

内推社群

我组建了一个氛围特别好的腾讯内推社群,如果你对加入腾讯感兴趣的话(后续有计划也可以),我们可以一起进行面试相关的答疑、聊聊面试的故事、并且在你准备好的时候随时帮你内推。下方加 winty 好友回复「面试」即可。

腾讯文档表格内存优化总结相关推荐

  1. antd 嵌套子表格_大型前端项目架构优化探索之路腾讯文档表格

    腾讯文档表格是一个非常复杂的业务,它实现了传统 excel 的大部分核心功能,包括函数计算.条件格式.图表.智能分列等:除此之外还支持高效的多人协同编辑:它的代码量百万级别,启动也流程多达十几步.在前 ...

  2. AI × OCR:腾讯文档表格图像识别技术实践

    本文主要介绍基于深度神经网络的表格图像识别解决方案. 作者:腾讯QQ研发中心--CV应用研究组的yonke 1.前言 1.1背景 大多数人日常办公处理的文件,无非就是表格和文档,其中表格的重要性毋庸置 ...

  3. 腾讯文档智能表格定时自动提醒如何设置?

    腾讯文档智能表是什么?可以做什么? 可能有些小伙伴已经留意到了,最近腾讯文档新推出了一个"智能表"的功能,在"在线表格"的左下角,点击"新建空白智能表 ...

  4. python 打卡程序_如何用python实现腾讯文档自动打卡并定时执行

    最近学了些Web,了解了一些selenum包内函数使用,就写了下自动健康打卡,并用windows任务计划程序定时执行,健康打卡这个针对特定网站,所以对于通用化使用倒没有太大用处,但关于腾讯文档如何填写 ...

  5. python实现网页自动健康打卡以及腾讯文档打卡

    需求描述: 现在学校要求每天健康打卡,而且是可以用网页访问,那使用上次的selenium可以轻松解决; 另外一个,为了方便班级统计打卡人数,还需要在腾讯文档进行打卡,即文档上填ok;这个就比较难解决了 ...

  6. Python实现Excel表格图片下载-腾讯文档收集表下载Excel表格后下载图片

    代码目的 QQ腾讯文档收集表下载本地后变成Excel表格,可是腾讯文档内的图片变成了链接,为了不手动保存图片,所以写出以下代码来实现自动保存图片. 代码原理 准备工作: python编译器 pytho ...

  7. 腾讯文档在线表格修改工具栏大小

    项目场景: 腾讯文档在线表格 问题描述 使用腾讯文档在线表格时发现工具栏太小,按住Ctrl+滑轮滚动只能放大缩小内容部分,工具栏大小并不改变. 解决方案: 点击浏览器(这里以Edge为例)右上角三个点 ...

  8. 小程序中读取腾讯文档的表格数据

    目录 1 创建连接器 2 创建腾讯文档 3 应用中访问腾讯文档 3.1 获取sheet中的所有数据 3.2 迭代行和列的数据 4 总结 日常生活中我们使用腾讯文档在线的收集各类数据,数据收集是比较方便 ...

  9. html设置表格行高和列宽,怎么在腾讯文档中设置表格的行高和列宽

    摘要 腾兴网为您分享:怎么在腾讯文档中设置表格的行高和列宽,云集,虚拟机,我的世界,卫星云图等软件知识,以及cad2009,kmp,mt4,usb转串口驱动,wifi吸粉,csgo动态组名,亿方云,单 ...

  10. 【工具】更新云文档办公利器汇总,腾讯文档测试文件上传,云文档对比测试报告...

    昨天在论坛里看到有大神分享"书籍索引目录.xlsx",据说是某宝买的资源,里边是约5T电子书资源的网盘书籍引目录,这个excel表格文档大小6M左右,我想分享给大家,然后便有了今天 ...

最新文章

  1. 谷歌某程序员抱怨“招人难”:招了小半年,8个岗位才招到1个,现在又空出6个岗位!...
  2. 有了这几个神器,瞬间逼格就上去了
  3. linux gcc 链接静态库的几种方式
  4. 海思 HI35* rtsp服务器
  5. 【DIY】震精!他居然用esp8266做出掌上游戏机......恐龙跑酷游戏还能这样玩!请广泛转发!...
  6. Android --- 订单编号怎样不重复?一秒钟如果有n个人同时下单怎么解决?凌晨12点限量抢购1000件商品,直到抢完为止订单编号怎么处理?
  7. 【渝粤教育】广东开放大学 数据结构 形成性考核 (30)
  8. 实战:自定义简易版SpringBoot
  9. 做春节海报没有思路?传统节日年味十足,PSD分层模板,给你灵感!
  10. sitemesh 2.4
  11. 做软件项目经理需要具备的品质和素质
  12. 有关Android插件化思考
  13. CleanMyMac最新版V4.11.4版MAC电脑系统加速器
  14. 【转】ARM GIC中断系列(三):gicv3架构基础
  15. Visionpro工具用途中文介绍
  16. 【076】朴素贝叶斯介绍
  17. 合同法律风险管理 合同签字主体
  18. TensorFlow 1.13.安装总结
  19. 数学模型课程期末复习提纲(上)
  20. 农夫养牛问题怎么用java实现,经典的农夫养牛问题(Java实现)

热门文章

  1. 校验身份证的行政区域代码(包含已撤销区域代码)2021年8月31日更新
  2. 2019 杭电多校第6场 HDU - 6638 Snowy Smile 线段树 最大子段和
  3. 个人征信要良好,申请信用卡需注意哪些事项?
  4. python实现12306火车票查询
  5. Linux系统中xz命令用法详解(压缩和解压缩)
  6. 剖析矩阵的本质及其意义
  7. [转]初中英语书中的LiLei和Hanmeimei应该结婚了吧!
  8. mirserver传奇服务端各文件夹注解
  9. OGRE: Ogre第一个程序
  10. office安装找不到office.zh cn的解决办法