引文
测试中发现,一个17M的文件打开时间超过了20秒。追踪发现,这个文件包含一个图层,图层有10多万个对象。为了进行对比,导出dwg,用AutoCAD打开,发现耗时不到3秒钟。看起来这个优化的空间很大!
优化加载图层
打印日志,统计时间看出加载数据集耗时1秒,但是加载图层耗费了近9秒。按理说,两个记录数一致的两张表,为何会差距如此之大?
查询语句不同
图层的查询与数据集最大的不同在于使用了order by关键字,按照level排序检索。用来调整对象的显示顺序。当在这个字段上补充上索引之后,加载图层时间立即降到了4秒。
经验总结:当查询语句包含order by时,在相应的字段补充索引。
风格集
图层的查询第二个特色便是:加载时通过维护一个风格集合哈希表来减少风格的内存占用,十几万个对象可能使用的风格在2000个以内甚至更少。但是每一条记录都需要进行哈希表的查询,这会增加加载的时间。而哈希表键值越大,比较的内容越多,耗时越长。类似于线宽的键值,用float代替double可以满足基本需求,但是键值占据空间减少了一半,比较的时间也就越短了。修改之后加载图层耗时842毫秒,已经与加载数据集不相上下了。
下图是加载图层优化过程计时。
优化解析元胞
经过图层加载优化之后,整个图层打开耗时从21秒下降到了13秒,算是一个比较大的提升了。但是与CAD(3秒)相比,还有很大的差距。需要做更深层次的优化。
在这个图层中,10几万个对象都是多段线,而多段线是由圆弧组成的。在解析元胞时执行了一个非常重要的函数,getCoordinates,从圆弧得到拟合点,最后以线段输出。
原始代码:
1 | LrUInt32 GeoCircularArc::getCoordinates(std::vector<Coor>& reCos, LrUInt8 flag/* = ESplineFit_Null*/) const |
全局变量查找优化
函数依据全局配置,获取间隔角度,然后依据此角度计算拟合点。其中:
1 | ConfigUtility::instance()->getDouble(ArcStep); |
访问了一个map表,从其中获取全局变量。map的效率是很高的,但是在重复访问10w次时,效率当然比不上直接访问数值。所以果断改为:
1 | ConfigUtility::instance()->getArcStep(); |
耗时从11秒降到10秒3,降低幅度虽然有限,但是提升了效率,修改之后代码如下(注意注释掉的代码作为对比):
1 | LrUInt32 GeoCircularArc::getCoordinates(std::vector<Coor>& reCos, LrUInt8 flag/* = ESplineFit_Null*/) const |
坐标调序
函数的最后,依据圆弧顺逆时针插入坐标数组的顺序有差别。当圆弧是顺时针,将tempCos插入reCos时,顺序与逆时针是相反的。也就是说,这里需要有一个tempCos的取反操作。这样写代码简单,但是逆序操作比较耗时,我们可以手动控制插入的顺序,修改后如下:
1 | LrUInt32 GeoCircularArc::getCoordinates(std::vector<Coor>& reCos, LrUInt8 flag/* = ESplineFit_Null*/) const |
代码量比刚才多了一些,但是耗时从10秒3降到了7秒2,优化了3秒钟!!!
三角函数结果存储
不难发现,这个函数中用得最多的三角函数运算,cos&sin。而平台最小角度为1°,我们完全可以预先把360度的cos&sin预先计算出来,存储到一个数组中,在使用时直接从数组读取,减少计算的时间。代码如下:
1 | LrUInt32 GeoCircularArc::getCoordinates(std::vector<Coor>& reCos, LrUInt8 flag/* = ESplineFit_Null*/) const |
这里之所以用720,是为了让类似于起始角度270、终止角度90、逆时针圆弧计算更加便捷。
这个优化,让解析的时间从7.2秒降低到6秒,提升空间也不是很大,但是也提供了一种思路。
下图是解析元胞优化过程计时图。
到此,打开图层从20秒降低到了8秒,时间减少了60%,算是比较大的提升了。
但是为什么距离cad还有这么大的距离?
我们每隔1度取一个点,是不是有点多了?尝试着将角度改为15、60,测试结果如下:
很显然,当间隔增大时,优化结果极其明显,当间隔角度为15度时,解析元胞只需要995毫米,当间隔角度为60时,耗时695毫秒!此时图层打开时间2652毫米,已经与cad接近了!
但是当间隔角度60,我们打开图形,看到的是一个个六边形,不是圆或者圆弧。
圆弧元胞
从显示需求来看,每间隔1°获取拟合点也不是一个合理的行为。在圆弧很小时,360个点显得非常的多余,当放大时360个点可能还不够用。
windows底层提供了绘制圆弧的API,我们可以尝试不再获取圆弧拟合点,只给出圆心、起始、终止的基础数据,让绘制引擎去决定这个圆弧最终如何显示吧!
增加圆弧元胞之后对比如下:
很明显,解析元胞时间进一步缩短为423毫秒,打开图层总时间2秒,降低了90%!(从21到2)。
占用内存与角度间隔60相差无几,比1度间隔降低了70%。而CAD打开相同文件占据内存0.8G。虽然时间上还落后与CAD,但是内存占用上这个文件我们胜出了。
即:本次优化空间和时间维度都得到了很大的性能提升!
可能有的问题和进一步优化
可能的问题
唯一可能担心的是,windows的圆弧绘制在视图放大到一定程度无法显示了(半径太大)。
解决:当视图放大到半径太大时,说明视图区域可以显示的圆弧为极少数了,我们可以在这个时候再进行上面的getCoordinates,手动获取拟合点。
这个函数经过优化之后,在这里正好能够再次派上用场!
进一步可能的优化
打开整个文件耗时在3.1~3.5左右,而CAD则基本在3秒以内。
每一个数据集和图层数据是相互独立的,或许可以开启多个线程加载多个图层。