画板-性能优化方案
  8CtDmU74qicX 2023年11月24日 28 0

一 画板现阶段性能瓶颈

  1. 图片内存占用过高
  2. 操作(拖动,缩放)导致的过度重绘制
  3. react、redux数据更新机制导致绘图出现延迟
    二 内存优化
    现阶段内存的瓶颈在哪里?到底是哪些东西占用了内存?
    优化点1:地图Map中的图片大小尽可能小
    现阶段地图所有的数据都是走的 服务端(非webp图片)的百分之25%缩略图,当出现超大图大图,每次都去缓存超大图的25%图片作为地图的数据,当地图中大图的数量较多的时候,初始化 加载和渲染的压力会比较大。
    为什么不走oss图片策略 - Map 组件没有去判断是否可以走oss图片策略 直接访问的服务端图片地址,这样导致了同样的p25缩略图,缓存中存了两份文件(oss 和服务端图片都存下来了);
    初步方案:Map 同画板一样也去走oss图片判断逻辑,能用webp尽量用webp;但是现阶段我们的大图由于oss图片限制,我们是走不了webp的,所以小图虽然能节省一定的内存,重头戏的大图依旧会在初始化页面时候带来比较大渲染压力以及内存上涨。
    进阶方案:实际上地图中不需要清晰度非常高的图,相反我们还可以弱化地图的清晰度,只给用户展示尺寸小图片,比如采用 固定宽度 50px的图片,即能节省大量内存,也能缓解Map组件初始化渲染压力,解决一部分初始化卡顿问题。
    优化点2: 画板图片数据策略优化
    现阶段缩略图都是按照原图的百分比进行获取的数据,且一旦匹配oss 超大图就无法使用webp 缩略图。所以导致大图模式下,内存占用非常大。下图为目前一张大图 的 内存分析。
  • 大图内存分析
    100%原图:27.2MB
    50%缩略图:942kb
    25%缩略图:245kb
    结论:可以看到从25%缩略图-245kb 到50%缩略图942kb 内存增加 3.8倍,从50%缩略图到原图内存增加29倍;对于大图而言,50%画质到原图画质,并没有明显显著的提升。

画板-性能优化方案_缩放

oss上传图片限制 https://help.aliyun.com/document_detail/44688.html

画板-性能优化方案_重绘_02

现阶段判断阿里云是否能加载webp缩略图的逻辑

// 在开启阿里图片服务时针对拖拽添加超大图片的情况下,需要标注为超大图片,防止无法使用。因为当前
// 图片源真实大小可能是size的1 2 4倍,从而导致超出阿里云服务16000的处理限制,
// 所以默认扩大4倍判断。
// 该判断会导致走阿里云服务的范围限制在了4000X4000。直接原因:页面数据中没有存储图片真实倍率
const isBigOSSImage = !parts.length && (size.width * 4 > 16000 ||
size.height * 4 > 16000);

优化策略

  1. 阿里云图片:不使用百分比,还是采用固定当前图片固定尺寸来计算缩略图的尺寸。同蓝湖一个策略。这样能大大降低内存占用。
  2. 无法命中阿里云图片(优化的重点),如超大图模式下缩略图,或者私有部署情况下,无法使用Oss webp缩略图,这时都是使用服务端原图格式,此时要如何进行优化呢?
    方案一:是固定宽度命中的方案,由于服务器并不支持任意尺寸的宽度图片的返回,只能支持一些特殊尺寸的图片。如 (w= 50px,100px,360px,820px,1640px)。通过计算,当前展示的缩略图在这些特殊尺寸之间时向上取
    方案二:依旧采用百分比缩略图(不展示原图策略),优化缩略图算法,判断大图模式下
    缩放比 < 25% 以下 , 展示 15%缩略图
    缩放比 [25%,50%] , 展示 30%缩略图
    缩放比 [50%,100%], 展示 60%缩略图
    缩放比 大于100%, 展示 75%缩略图
    方案三:限制用户上传图片 让其正好命中oss存储规则,参考蓝湖上传图片策略。
    现阶段我们的上传是没有做限制的,很多超过30M的大图也可以上传。这个是导致内存大,以及渲染卡顿的一部分原因。
    方案四:更加精细化的微雕(先拥有流畅使用体验,再开始谈图片质量问题)
    通过不同的用户,不同的权限,不同的使用场景,用户不同的设备下(操作系统,内存,显卡-GPU渲染能力等等),来进行不同的缓存、渲染策略的调整
    比如根据 不同的权限下的使用者(项目拥有者,协作者,被分享者 )对画板 图片质量的要求是不一致的,我们也可以通过这部分入手来让大部分使用者拥有流畅的体验。
    比如(高清模式、流畅模式-对画质做更大一部分的牺牲)主动选择,或者被动选择 - 写一个算法去判断,比如总体图片超过多少张,大图超过多少张,判断用户的设备以及内存情况屏幕dpi,自动启用流畅模式(做到多设备更加精细化的处理图片画质,控制内存 - 精功细活)
    根据不同的用户权限 - 做不同的缓存策略,比如分享用户,只有查看画板权限的用户,缓存策略为长期保存(只存在对页面的放大缩小,画布的拖动)

算法优化(只针对超大图做处理)

优化点3: a标签跳转或者window.open(),造成安全性问题以及性能问题。
打开的新窗口之间会共享进程和线程,如果用户使用此方法同时打开几个内存大的项目,会使得几个页面共享内存成倍增加。
具体参考:https://cloud.tencent.com/developer/article/1008860 优化方式:

  1. 如果是a标签要在新窗口中打开,添加noopener属性
  2. 如果是js中打开新窗口,手动将新窗口的opener置为null

优化点4: 删除数据的同时,删除缓存数据
现阶段,缓存数据只会不断新增,当删除了图片,没有将缓存中对应的数据进行清理,所以不断的操作页面,全部删除,再新增,再全部删除,缓存会不断扩大,造成内存泄漏。
优化点5: 缓存数据 cache 中做 分类检索
比如map的数据,画板的数据,画板数据再区分多少倍缩略图进行检索。
现阶段由于将url作为键名进行全部匹配检索,多少会影响一点搜索的速度。
优化点6: 浏览器缓存策略
现阶段的缓存策略是将加载好的img标签直接缓存到内存中,优势是读取速度快。
思考:引入Service-worke + indexdb进行存储 :indexDB介绍与封装
经过实验:由于indexDB写入和读取数据的速度太慢,甚至最后的速度比不上直接通过网络请求来得快,最终图片加载渲染还是会将加载到内存中,所以将indexDB作为缓存数据库貌似并不可取。所以最终放弃了用indexDB来做数据库缓存。
但是 ,indexDB适合做的离线缓存,我们依旧可以利用这个特性。在加载完画板以后,将数据缓存到indexDB中,当用户从画板切换到其他页面或者文档或者原型稿,先卸载画板内存中缓存的数据。如果用户再度切回画板,就直接从indexDB中读取所有数据,这样就能大大缓解在网速较慢情况下 初次渲染的压力。此方案还有待研究和实践。

三 渲染策略优化
原则:尽可能去减少画板重新绘制的次数

  1. 移动画布减少重绘
    分析:现阶段画板拖动时候重绘情况

画板-性能优化方案_重绘_03


通过咨询以前实现这部分代码的同事,防抖15毫米就去重新绘制的原因是防止用户已经将画面拖出了画布已经绘制的区域。有图可见,一张图片随意拖动一下,更新次数达到22次。每一次的更新里,都做了什么呢?

  1. 计算更新后的画布位置(stage 偏移量)
  2. 计算更新后 画布在容器内的边界坐标
  3. 通过边界坐标 过滤 本次更新 容器范围内需要渲染的图片
  4. 画布重绘
  5. 容器的transform 属性回归初始化位置
    所以现阶段 15ms的更新里的这些操作都是同步进行并且会阻塞渲染以及用户操作的,此时如果页面中需要渲染的大图较多,用户使用的浏览器,电脑配置(内存,显卡)都会对使用流畅度造成影响。(现在有一些线上用户反馈使用异常卡顿,基本都是因为这个原因)
    拖动的具体优化方案:画布拖动方案
    简而言之:
  6. 增大水平方向的画板容器宽度为可视区域的3倍,保证一次拖动,不管用户怎么拖在这个范围内的图片都是已经渲染好了,不需要再进行重绘。
  7. 在鼠标按下去拖动画布过程中,只进行容器的位移,画布不进行重绘制。在用户鼠标mouseUp时候再去重绘。这样整个一次常规拖动就把22次更新变成了1次渲染更新。
  8. 这样可以完全解决拖动卡顿的问题。 由于画布增大,每一次渲染的时间势必增大,还可以对此次渲染图片中在容器内但是不在可视区域内的图片,进行精细化管理,让其渲染清晰度降低一个层次(比如:50%降低为 25%)

缩放画布减少重绘基本分析同画板移动,现阶段缩放画板重绘的防抖时间为45毫秒,策略也同拖动一样,不使用防抖时间作为重绘的触发条件,而是在用户操作期间都不进行重绘。

画板-性能优化方案_缩放_04


初步方案:缩放重绘防抖时间设置为200ms四 并发渲染 - 不阻塞用户行为react18 提供了并发渲染模式,采用 useDeferredValue  和 startTransition 来标记哪些具有最低的渲染优先级。这儿我们可以把画板重绘标记为最低优先级,用户操作的反馈优先级最高。react 将低优先级画板重绘搁置,优先响应用户行为,等用户操作真正完成以后,再处理画板渲染。具体react 的优化史,参考文档: https://www.51cto.com/article/708508.html需要去做的点: 现阶段我们react是17.0.0 版本,升级到18涉及到一些比如路由方式引用,React创建入口Root方式不同,以及其他依赖是否适配都还需要去研究。五 离屏渲染技术利用离屏canvas对象的特性,可以将显示在屏幕上的操作先进行在离屏的canvas上渲染,等到渲染完毕后,将渲染结果再拷贝到屏幕上避免频繁的重绘,从而达到节省计算和提高绘制性能的目的。使用方法:// 创建一个离屏canvas:const offscreenCanvas = document.createElement('canvas');// 将你想在离屏canvas上进行渲染的内容渲染在上面:const offscreenCtx = offscreenCanvas.getContext('2d');offscreenCtx.drawImage(originalImage, 0, 0);// 离屏canvas渲染完成后,将渲染结果拷贝到屏幕上:const mainCanvas = document.querySelector('#mainCanvas');const mainCtx = mainCanvas.getContext('2d');mainCtx.drawImage(offscreenCanvas, 0, 0);参考文档:六 减少redux 和 react 状态改变 对 渲染性能影响现阶段画板容器组件太大了,状态多。很容易就引起重新render,影响性能。减少react中其他状态改变引起 canvas 重绘,对性能造成影响。将画板功能更多进行组件化抽象;比如 BasicLayer层,SlectedLayer层,FlowLayer层等等。每一层严格用shouldComponentUpdate 进行把控,不该渲染的状态更新保证绝对不触发组件的render。七 分片上传大图八  优化计划第一阶段 优化计划
内存优化 ,减小内存,兼容更多低性能设备
渲染策略优化 ,提升移动画板,缩放画板时流畅性
启动并发渲染逻辑,升级react18 使用新特性 useDeferredValue  和 startTransition
缓存数据cache 中分类检索
platform 项目中修改 跳转方式
减少redux 和 react 状态改变 对 渲染性能影响 (memo,shouldComponentUpdate)

第二阶段 优化计划缓存数据(indexDB + serviceWorker + 位图数据)
离屏渲染技术(离屏canvas 和webworker)
大图分片上传
其他(webgl - canvasKit技术)

总结:现阶段卡顿造成原因?
Js 单线程的,相应用户操作的js线程 和 UI 渲染线程 是互斥的。如果渲染过程耗时比较长的话,就可能导致 UI 线程挂起和事件得不到响应,进而出现页面卡顿,也就是我们常说的“阻塞渲染”。

原因一 在拖动和缩放过程中不停重绘,过度重绘了。
解决方案:增大画布体积,默认绘制更多,在拖动过程中只拖动容器,不进行重绘。在鼠标mouseUp 时候才对画布进行重绘。缩放防抖时间增长,缩放的瞬间会模糊一下。

原因二 一次绘制时间过长 (canvas绘制 图片的个数,cancas 绘制图片的像素点多少?)与用户的显卡内存都有关系,gup内存明显上升。
优化点:可以适当减少图片的像素,降低其图片数据量大小,以此达到加快 canvas 绘制的速度。
图片内存如何进行控制?
上传没有做限制。是否应该做限制?
渲染控制:减少单位体积内像素点绘制(不渲染原图)
地图的图片 使用 w-50 ,地图中的图片会相对模糊
后端支援:先让我们能获取任意百分比的图片,再对比测试 压缩后的图片质量和图片大小关系。得出一个内存和百分百的转换算法。再固定 某几个百分比

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月24日 0

暂无评论

推荐阅读
8CtDmU74qicX