LCUI 1.1.0 Beta 开发日志

2018年12月22日  ·  4870 字  ·  阅读

开发计划:

  • 优化内存占用
  • 优化样式表计算性能
  • 优化在应对上万部件时的处理性能
  • 添加支持自定义部件更新规则

2019-03-04

测试 LCUI.css 的模态框时发现操作按钮未响应鼠标事件,看上去是 pointer-events 的问题,在 _modal.scss 中,.modal-dialog 将 pointer-events 设为 none, 而 .modal-content 里又将 pointer-events 设为 auto,难不成 pointer-events 是默认继承父元素的?查了 MDN 文档:[pointer-events - CSS:层叠样式表 MDN](https://developer.mozilla.org/zh-CN/docs/Web/CSS/pointer-events#%E5%80%BC),的确是这样。可问题是 pointer-events 的默认值是 auto,怎么判断这个 auto 是手动指定的还是默认值?可以将默认值改为 inherit。

2019-03-03

准备发行版的 dll 真麻烦,这次加了 x64 配置,又得手动编译 x64 版的依赖库,UWP 版也需要编译。之前有试过 vcpkg 提供的静态链接配置,但貌似没有效果,这次再点时间试试看。

2019-02-24

每次追加部件都会涉及 first-child 和 last-child 伪类的改动,然而大部分情况下都不会有与这些伪类匹配样式表,挺浪费资源的。可以考虑做些调整,查询样式表的时候只精确到类,然后再判断匹配的样式表中有没有指定伪类,有指定的话再判断部件是否有伪类,是则选取该样式表,例如有 first-child 时就判断部件是不是排在第一个,这样改的话就可以扩展支持 :not():empty:last-of-type 这些伪类了,而且是按需计算,节省了不少开销。

销毁 50 万个 TextView 部件耗时 7 秒,其中销毁事件触发器耗时 2.4 秒,销毁 TextView 自带的互斥锁耗时 1 秒。

vs2017 CPU 使用率报告

优化方案:

  • 销毁事件触发器时会停销毁未处理的事件和触屏事件捕捉器,它们都涉及了互斥锁操作,可以先判断记录数是否为 0,省去互斥锁操作。
  • 取消 TextView 的互斥锁。

2019-02-23

写了个测试程序,更新两万个部件耗时十秒,将数量调整到五万个后耗时一百秒,根据 CPU 使用率报告可知大部分时间消耗在 Widget_ExecUpdateZIndex() 里,这个函数负责更新部件 z-index 值并重新排序部件列表,排序算法用的是插入排序,先将当前部件从列表中取出,然后遍历列表找到合适的插入位置。

vs2017 CPU 使用率报告

优化方法很简单,在子部件 z-index 有更新时标记父部件需要重新排序子部件列表,重新排序时清空 children_show 列表,然后从 children 列表中逐个取出子部件插入到 children_show 列表中。一般情况下子部件的 z-index 都是 0,排序操作的耗时相当于复制两次 children 列表。

子部件的增减和 z-index 更新都会触发排序,给这些操作函数加代码触发排序有点麻烦,那就在每次自身或子部件有更新时触发排序吧。

2019-02-16

准备添加性能统计功能,统计近一秒内每一帧的运行情况,方便确定哪里需要优化。比较纠结的是如何获取这些数据,有以下几种解决方案:

  • 每个功能模块自己统计,提供一个接口用于获取这个统计数据
  • 给现有的接口追加一个参数,用于接受统计数据
  • 添加新的接口

统计相关代码只在 main.c 中有定义 DEBUG 宏时才启用,应用程序如果要启用这个功能还得手动改 LCUI 源码并重新编译,挺麻烦的,可以加个 config 模块,方便在运行时改变 LCUI 相关功能配置。

配置是全局共用的,按常规套路应该将代码放到 config.c 和 config.h 里,可问题是 configure.ac 生成的配置文件也是 config.h,如何解决这个冲突?

靠应用程序代码来修改配置还不够灵活,要改配置就要改代码,改了代码后要重新编译,应考虑支持从外部加载配置,例如:命令行参数,传入 --enable-profile 参数时启用性能分析功能。其它 GUI 库在初始化时要求传入 argc 和 argv 估计也是为了加载配置,LCUI 以前也是这样,后来因为没有具体用处就砍掉了,现在加回来又有些不妥,那就只能加新的接口了。

渲染部件时会计算每个部件的实际样式,当部件数量达到四万个时,总耗时比预想的要高很多。如下图所示,大部分 CPU 资源都用在计算实际样式上。

vs2017 CPU 使用率报告

看了下相关代码,可以做些优化,例如:

  • 减少函数调用。计算一个实际值就一个简单乘法运算,却要调用好几个函数。
  • 使用部件现有的边界框(widget->box)判断是否需要渲染,等确定要渲染后才计算实际样式。

2019-02-12

使用 Widget_CopyHash() 前需要准备模板部件,在列表清空后还要重置部件指针为 NULL,而且递归遍历复制 hash 值的效率也不高。可以加个 Widget_GetHashList(),将部件及子集部件的 hash 都写到数组里,然后加个 Widget_SetHashList() 用来将 hash 数组应用到自身及子部件里,这样改了后,限制条件少了,使用起来也方便一些。

给部件设置最大子部件渲染数量后,排列在后面的子部件会有闪烁,即便它是绝对定位的且在可见区域内也会被忽略掉。

2019-02-11

克隆部件是只克隆自己还是包括子集所有部件?有的类型的部件在初始化时会追加一些子部件,要是把子集部件也克隆一遍的话会重复的。

克隆功能就不加了,只是为了复制 hash 值的话那可以加个 Widget_CopyHash(),指定目标部件然后遍历子部件逐个复制 hash。

2019-02-10

添加了个 only_on_visible 规则,只在该部件处于可见区域时才更新它的子部件,节省 CPU 资源给其它部件。

一个列表四万多张图片,越滚到后面,部件更新会越慢,因为后面的部件都在等前面的部件更新完。需要加一个“优先更新可见区域内的部件”的规则,在启用该规则后,遍历列表,找到第一个在可见区域的部件,然后开始更新它和它后面的部件,直到部件不可见为止。部件的查找方法可以优化,比如用二分法查找,但感觉搞起来有些麻烦,所以先不做调整。

改成一帧更新一次部件后,界面更新有点延迟,还是再加几次吧。

清除缩略图缓存时应该显示个操作中的提示,在缓存大小到了 600MB 以上后,操作耗时明显变长,一直卡住界面会影响用户体验。

又多了个 bug,滚动条的长度计算不正确。

2019-02-09

虽然样式表有缓存,但计算索引 key 很耗时,而且大部分是重复计算。

补充需求:

  • 支持计算部件 hash 值
  • 支持计算部件和子集部件 hash 值。列表项通常由多个部件组成,只计算容器部件的 hash 值还不够
  • 添加部件复制函数。列表项数量很多,给每个列表项计算 hash 值太浪费时间,直接复制会更简单些
  • 添加一项部件规则:是否缓存子集部件样式表缓存。缓存使用当前部件 hash 值 + 子部件 hash 值作为索引键值
  • 当部件的 class 和 status 改变后,如果有 hash 值,则重新计算

每次更新都要重新布局,部件多了后很耗时间,LCUI_ProcessEvents() 每处理一个任务都会调用 LCUIDisplay_Update() 更新部件,为降低耗时,决定移除 LCUI_ProcessEvents() 中的 LCUIDisplay_Update() 调用,一帧只调用一次 LCUIDisplay_Update()。

测试时发现渲染耗时也很长,可是渲染时除了填充像素点比较耗时外,剩下的就是遍历部件列表和判断部件区域与重绘区域是否重叠,难道这简单的判断做几万次也需要几百毫秒的时间?找原因挺麻烦的,先加个规则,限制子部件渲染数量,对于图片列表来说,整个屏幕也就够显示几十张图片,剩下的就没必要判断了。

2010-02-08

style 样式表还有其它用处,在部件更新时用来对比新旧样式,布局时也要读取子部件的 style 样式表,看样子只能先改 custom_style 和 inherited_style 表了。

2010-02-07

每个部件都有 style、custom_style 和 inherited_style 三张样式表,太占空间,需要优化:

  • 将继承的样式表放到缓存里,部件的 inherited_style 只引用它。样式表缓存每次添加新样式表后会清空,那在更新部件样式时,如何判断是否需要更新继承的样式表?看上去移除 inherited_style 也行,每次更新样式时从缓存里取,不存在则查询样式库。
  • custom_style 里的有效样式很少,可以改用链表存储,节省空间。数据结构改了后,部件的样式操作接口也需要调整。
  • style 表改成在更新部件时临时生成。部件的其它属性更新任务依赖这个样式表,需要调整部件的任务处理流程。

2019-02-05

每次调用 Widget_Append() 追加部件时都会更新部件的 last-child 和 first-child 状态,在部件样式类和状态变更后都会调用 Widget_HandleChildrenStyleChange() 检查是否需要更新子级部件,这个检查比较耗时,部件数量多的时候就会很慢。

有以下几个优化方案可以考虑:

  • 添加 Widget_AppendList(),用于一次性追加多个部件,避免频繁改动部件的状态。
  • 在每帧更新部件时处理部件的 first-child、last-child 状态。
  • Widget_HandleChildrenStyleChange() 只在子部件数量大于 1000 时才查样式库检查是否需要更新子部件,否则默认子部件需要更新样式。
  • 添加支持自定义部件更新规则,可设置在 active、hover 等状态下是否更新子级别部件样式、仅更新可见区域内的子部件样式。
  • 添加支持更新和设置部件 hash 值,当 hash 值不为 0 时,直接从样式表缓存里取对应的样式,hash 值相同的子部件都共用同一样式表,不重新计算。像 active、hover 这些状态作用范围小,一般只会有几个部件处于这些状态,那么在状态变更时可以直接重新计算样式。

2019-02-04

用 VS2017 的性能探查器检测 LC-Finder 的内存占用,生成的报告大小有 3GB,这要是用以前的机器来搞的话读取报告都得等很久。

vs2017 内存使用率报告

vs2017 内存使用率快照#2

从快照中可以看出样式表占用内存最多,约有 244MB,字符串占用空间 101MB,比部件还多。

以下是可优化的地方:

  • 添加字符串池。样式表和部件中的字符串重复率很高,频繁申请内存来存储字符串既浪费空间也降低性能,有了字符串池后,相同内容的字符串只需要申请一次内存。
  • 用链表存储样式表。LCUI_StyleSheet 虽然能容纳所有样式属性,读写操作快,但内存利用率低,换成链表存储后,只存储有效的样式属性。
  • 初始化部件时不为它创建事件触发器,等到绑定事件时才创建。

2019-01-30

有点纠结该怎么触发 mouseout 和 mouseover 事件,如果给目标部件和它父级所有部件单独触发事件的话,父级部件就无法捕捉到子部件的事件;如果只给目标部件触发事件然后冒泡的话,父部件又不方便判断是不是自己的事件,因为在子部件之间移动鼠标时也会触发 mouseover 和 mouseout 事件,尤其是 mouseout 事件,无法判断是不是因鼠标光标移出父部件而触发的。

mouseout 事件触发条件可以改成单独触发,只要是移出部件,就给这个部件触发 mouseout 事件并默认冒泡,这样就能根据 target 来判断事件是不是自身触发的。

2018-12-23

在给 LCFinder 的标签编辑器输入文字时按了方向键,结果被图片查看器捕获到并切换图片,导致正编辑的输入框被销毁。当输入框获得焦点时,应该阻止事件冒泡,如果绑定的全局按键事件,则需要有个函数用于判断是否有激活输入焦点。

2018-12-22

绑定和触发事件比较麻烦,例如绑定 change 事件,需要先找与 change 关联的事件 id,没有就会绑定失败。需要调整一下,以字符串作为事件名时,如果没有与之关联的事件 id,则创建一个事件 id 并关联它。

文章版权归作者所有,未经许可不得转载。

问题反馈

对此文章有疑问?你可以点击 这里 反馈