公开课回放

讲师简介 王鸿,2020 中国 .NET 开发者峰会主论坛讲师,葡萄城表格技术负责人,GrapeCity Documents for Excel项目组资深架构师。 自 2014年起,王鸿老师便一直聚焦于企业高性能表格技术领域的研究,为葡萄城设计了全新的表格组件架构,并带领研发团队推出了一款性能在业界领先的电子表格组件GrapeCity Documents for Excel(简称:GcExcel), 积累了大量高并发、高可用性表格组件的架构设计经验。 课程内容 在2020中国 .NET开发者大会上,王鸿老师受邀与微软、龙芯等知名企业的技术大咖一起,为50 余万名开发者带来了一场关于《高性能表格技术优化实践》的技术讲座。 王鸿老师从葡萄城研发电子表格组件的背景与初衷出发,详细对比了 Excel 与原生 C# 代码的读取性能差异,并总结了若干针对prototype 原型进行性能调优的手段,如减少垃圾回收的影响、共享对象提升性能、压缩数据降低内存、充分利用高速缓存等方式。 本期,葡萄城公开课将继续邀请王鸿老师担任演讲嘉宾,为错过大会的同学深入讲解葡萄城表格组件的性能调优实践,以及葡萄城表格技术的落地产物SpreadJS和 GcExcel。

Excel读取1亿个单元格的极限性能是34秒 为了测试Excel 文件的读取性能极限,王鸿老师使用了StopWatch 函数来监听 Excel 的打开时间,测试对象为一个包含30列、1,000,000行、30,000,000 个单元格数据的电子表格文件。 经过测试, Excel打开这个文件需要等待34 秒。

葡萄城 GcExcel 读取这份文件的极限性能是12秒

GrapeCity Documents for Excel (简称:GcExcel)是一款由王鸿老师参与设计,由西安葡萄城自主研发的基于 Java 和 .NET平台的服务端高性能表格组件。

使用该组件,系统无需依赖 Office、POI或第三方应用软件,即可在服务端批量创建、加载、编辑、打印、导入/导出Excel 文档,满足在线文档的前后端数据同步、服务端批量导出与打印,以及类 Excel 报表模板设计和服务端高性能数据处理的业务需求,提供一套完善的类 Excel 全栈解决方案。

经过测试,用葡萄城的表格组件 GcExcel 打开这样一份文件,仅需 12 秒。

为了实现与 Excel 近22秒的性能差距,葡萄城 GcExcel 都做了哪些性能优化呢?实际上,在推出自主研发的表格组件之前,葡萄城便已经对Excel 文档的格式、样式和存储结构进行了深入的研究。

从Office 2007 开始,Excel文件就是一个标准的 Zip 文件,对其解压后,找到一个名为“Worksheets”的文件夹,在其中的“sheet1.xml”文件中,存放了每个 Excel文件单元格对应的位置和值。

因此,如何通过更高效的算法取出这些位置和值,便是葡萄城的重点优化方向。

对于一段未经优化的 C# 代码而言,把每个单元格的值读出来,存放在一个List中,需要27秒。在这里,仅仅将代码中的 object 改为 double,就可以让其在20秒完成。

葡萄城表格组件的性能优化实践

一、减少垃圾回收

由27秒变为20秒的差距,便是垃圾回收带来的影响。

在 List中有太多的object对象,这耗费了大量的垃圾回收时间,尽管它没有被回收掉,但因为它们是object,所以在垃圾回收的过程中,需要不断的检测它们是否可以回收,将object 改为 double,垃圾回收的时间便可以忽略不计。

GcExcel 如何克服垃圾回收的影响?

  1. 消除单元格概念,用double 存储数字、文本、布尔和错误类型。

  2. 行存储改成列存储。利用C# 的泛型,让字典中存储值类型数据。

二、共享存储及样式压缩

所谓共享存储,就是把整个软件**同的对象只生成一份,放在一个全局的地方,每个对象用一个数字做ID,其它地方只存这个ID。

以下面的文本为例,所有的文本集中存储在一个表中,每个文本有一个唯一的ID,单元格中只存储这个ID。

共享存储对降低内存和提升性能有非常明显的作用:

  1. 降低内存消耗。对一个数据量很大的软件,会有很多数据都是重复的,比如电子表格中,如果文本的数量很大,那么重复率就非常高,把这些文本集中存储,相同的只存一份,你会发现,不同的文本数量其实很少,这会极大降低内存消耗。

  2. 提升性能。以对字符串做查找、替换为例,如果没有共享集中存储,必须要扫描所有的单元,对每一个单元格文本都做一次比较操作。但有了共享存储,就只需要查找“字典”就可以了;在字符串比较时,只需要比较数字是否相等就可以了;在排序等操作中,只需先把表中的字符串建立一个索引,排好序,后面需要时利用这个索引就可以了。

通过共享存储,GcExcel已经把内存的消耗降低了很多,但仍可以进一步优化,便是利用样式压缩。

如上图所示,保存这张样式表不需要在每个单元格中存储一个数字,而是通过建立一个索引表,表的一栏记录一个区域(一个矩形),另一栏记录这个区域对应的样式的ID。

通过这样的方式,便可以把样式的存储空间再度降低。极端一点讲,如果整个工作表都是同一种样式,那么这里只需要存储一个矩形和一个数字。

三、充分利用高速缓存

现在的CPU都有高速缓存,它能提升CPU处理数据的性能。这个图展示了不同存储介质和CPU的关系:越高速的存储介质离CPU更近,速度越快、空间更小、价格越贵。

CPU在读取数据的时候,先从最近的缓存中读,只有没命中,才从下一级缓存中读。因此,提升缓存命中率对改善性能意义重大。

同时,当数据从较慢的存储介质往较快的存储介质复制时,不是依次进行,而是一块一块的复制,也就是说,当访问内存中某一个数据时,它周围相邻的数据也一起读入了离CPU最近的高速缓存。因此,如果下一个时刻就读相邻的数据,便可以直接在缓存中找到,读取速度就会非常快。

知道了高速缓存的原理,如何编写缓存友好的代码?

  • 在组件设计的时候,选择什么样的数据结构得注意,比如数组和字典,受缓存的影响可能不一样。

  • 在访问数据的时候,要注意数据的存储方式。

当然,除了上述三个优化方法,葡萄城还有更多优化实践。如创建Cache、使用基于集合的操作运算、利用SIMD计算大量数据等。

以上就是王鸿老师即将分享的部分内容,目前,这些高性能的表格技术均已经实现落地。

葡萄城表格技术的落地产物:SpreadJS 和 GcExcel

在前端,纯前端表格控件 SpreadJS可针对Excel、Grid数据进行在线编辑、在线填报、公式计算,以及各类 Excel 的模板设计;在后端,服务端表格组件 GcExcel可快速批量处理 Excel 文档,提供高度基于 Excel 的文档对象模型,并执行更高效的导出与打印。