V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
retrocode
V2EX  ›  分享发现

论: 为什么每个程序员都需要有一台破电脑, 我与 10 后收银机的二三事

  •  1
     
  •   retrocode · 2023-07-19 16:22:33 +08:00 · 2225 次点击
    这是一个创建于 494 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    最近我在做收银机相关的自研项目, 此前外包的原生代码过于烂到了无法修改的底部, 加上 deadline 临近. 我临时上马开始做翻版, 由于是安卓/windows 双端的, 最后整合出的方案是接入 webview, 优先接管主要页面, 解决业务 bug 及性能问题, 后续逐步接管其他页面.

    整下来等于是推倒重来了, 时间紧任务重, 我们基本上的工作就是将 安卓端的 java 代码翻译成 typescript 扔进 vue 里, 然后 c#开 webview2/安卓接腾讯 X5 去嵌套调用. 在此先感谢亲爱的chatgpt的代码翻译, 极大减少了我的工作量.

    性能瓶颈

    讲真此前我是没有特别关注过性能问题的, 相当多的性能问题, 在当代硬件上都不是问题(几毫秒和几十毫秒除非是高频操作,一般的确是没人关心的), 以下的这些问题均是在我们理论上需要兼顾的最低配置硬件上压测发现的, 测试机大概配置如下:

    1. 灵动 D525(10 年 Q2)
    2. 服役估计有 10 年的内存和硬盘
    3. win7 32 位系统
    4. webview2 109(最后一个支持 win7 的版本)

    过去俩月我感觉我把此生学到的知识算是都用上了. 期间遇到了一堆性能问题.我大致总结下比较典型的几个问题如下:

    1. new 对象时向原型链挂载函数的性能损耗
    2. 扫码枪输入过快引起的css 重排卡顿
    3. JSBridge 沟通原生的调用耗时
    4. vuex 对大对象的劫持时间损耗以及内存损耗
    5. webview 所有逻辑运行在UI 线程导致与原生 UI 线程抢占CPU 问题

    逐个解释

    1. new 对象时向原型链挂载函数的性能损耗

    这点是在极限优化时发现的, 由于是从安卓移植过来的 java 代码, 移植的时候是直接沿用的面向对象模式.所以代码中存在大量的 new 对象操作, 在性能优化到达某一步时我们发现, 遍历构建对象也是个性能点, 100 来个对象在开发机上数毫秒, 在测试机上却 100 毫秒起步, 起初我们是决定先略过的, 直到最后回顾时才开始着手解决, 经过测试最后发现是 typescript 的问题, ts 编译后的代码会将对象方法挂载在原型链上, 而耗时就出在这一步上.

    最终解决方案是 将此处的对象改为直接使用 {} 字面量对象, 耗时一下降低到了 20 毫秒左右

    顺带一提, 测试发现在 10W 数据量下创建 带 get set 的 {} 对象, 耗时比不带的也有数倍提升, 我们是直接将 get 只读改为了普通的属性, 并留下注释表面不可修改解决这一题.

    2. 扫码枪输入过快引起的 css 重排卡顿

    新版本的 webview 页面是采用 flex 和 grid 混用开发完成的, 所以全部布局都是动态的, 当我们最终解决完所有 js 层的性能问题, 绝大多数测试机型上都测试通过后, 最低配置的测试机上不出意外的没让我们失望, 在这台老爷机上, input 内容变更带来的单次 css 重排达到了恐怖的 86 毫秒, 而扫码枪是毫秒级输入的, 在扫码时瞬间输入了十几位数的条码, 于是出现了接近 1 秒的卡顿.

    解决 css 重排问题的方法倒也简单, 使用绝对定位和相对定位将 input 输入框跳出文档流即可, 这样重排将只发生在 input 内, 而不会带没动其他元素发生重排现象.

    3. JSBridge 沟通原生的调用耗时

    这里也是老生常谈的问题了, 由于多了层桥接, webview 沟通原生的速度始终为人诟病, 不过精测在绝大多数机器上这点时间是可接受范围内的, 又是我们的老爷机, 在其他一众几年内的机器上沟通原生的速度都可以控制在 10 毫秒内, 而这机器由于硬件严重老化, 沟通成本达到了 50 毫秒, 而部分业务中有单次执行中查询多次数据库的操作, 导致最终耗时整体达到了 500 毫秒.

    解决方案只能是, 优化代码减少查询次数, 构建针对性的函数, 在原生一次性将所有需要数据查询完成一并返回, 以及将配置类参数提前读取全局持有.

    4. vuex 对大对象的劫持时间损耗以及内存损耗

    这个点应是最离谱的了, 考虑到硬件性能的确不好, 对于绝大多数的数据, 一般的做法是应用启动时读整表然后缓存到内存全局持有的, 其中就有 10W 加的商品数据, 当最终我们将其余优化完成后, 发现启动加载数据还是慢, 在开发机上加载数据可能只需要 100 毫秒, 一般的测试机也可以在 2-4 秒内完成数据加载, 而老爷机上这个时间则是 13-15 秒.

    最终我们定位到了问题在 vuex 上, 整个过程大部分时间都花在了 vuex 的 state.obj = data 上, 最终挂载到 vuex 上的数据是以 kv 和数组形式保存的, 即:

    {
        key: {...},
        ...
    }
    
    [
        {...},
        {...},
        ...
    ]
    

    而 vuex 会对对象的所有属性进行劫持处理, 以保证响应式. 这一过程在测试机的 10W+个属性上最终耗时达到了十几秒, 而我们是整表缓存,也就是这些数据理论上是不需要响应式的, 每次都是重新整表赋值即state.obj = data, 此时我们可以使用Object.freeze对对象进行冻结, 以停止 vuex 的响应式开销.

    最终结果喜人, 这一过程的耗时从十几秒降低到 几毫秒 😅 同时附带的好处, 当项目加载完成后整个 webview 的内存占用从 330m 降低到了 100m

    5. webview 所有逻辑运行在 UI 线程导致与原生 UI 线程抢占 CPU 问题

    webview 运行在 UI 线程, 理所当然的 webview 中的 js 也运行在 ui 线程, 理所当然的 js 调用的原生也会在 UI 线程, 理所当然 js 调用原生的数据库查询也在 UI 线程. 那么原生拉起 webview 的同时后台渲染下一页以加快速度, webview 页面的一切初始化操作都会与原生 UI 强制 cpu 资源, 造成实际在前台的 webview 的页面加载卡顿. 最终的解决方案是 webview 页面加载完成后, 之后在由原生执行预加载操作, 与 webview 至少在页面加载时错开以完成首屏加载.

    总结

    经过优化后的成果是喜人的, 即时在服役 10 年的老爷机上依然纵享丝滑, 原先各种诡异的卡顿掉帧也都销售了, 扫描条码终于可以看到数字输入了, 原先由于卡顿条码甚至来不及渲染就会清理掉.心情舒畅可以交差了.

    随着硬件发展, 很多以前的性能瓶颈已经不是瓶颈了,像我开头说的几毫秒和几十毫秒绝大多数情况下没差, 但是一些该了解的知识还是需要了解做知识储备的, 不然碰上了到处撞墙是真的难受.

    19 条回复    2023-07-20 20:15:48 +08:00
    leon233333
        1
    leon233333  
       2023-07-19 17:01:16 +08:00
    🐂
    rimworld
        2
    rimworld  
       2023-07-19 17:04:53 +08:00
    🐂,我大概会选择升级配置来解决。
    retrocode
        3
    retrocode  
    OP
       2023-07-19 18:22:53 +08:00
    @rimworld #2 升配置简单, 这种老机器花几块钱换个新的内存条都能带来大幅度, 主要要是更新配置就得去线下一家店一家店的更新, 就比较蛋疼了
    chingyat
        4
    chingyat  
       2023-07-19 18:35:02 +08:00 via iPhone
    🐮,佩服 op 的探究精神
    SeanTheSheep
        5
    SeanTheSheep  
       2023-07-19 18:36:40 +08:00 via Android
    牛逼
    taotaodaddy
        6
    taotaodaddy  
       2023-07-19 18:46:10 +08:00
    前来学习
    kinghly
        7
    kinghly  
       2023-07-19 19:16:25 +08:00 via Android
    牛逼
    FreeEx
        8
    FreeEx  
       2023-07-19 19:23:10 +08:00
    厉害的,感谢分享。
    freewind64
        9
    freewind64  
       2023-07-19 20:15:16 +08:00
    🐂,感谢大佬分享.
    elmagnificogg
        10
    elmagnificogg  
       2023-07-19 20:27:44 +08:00 via Android
    厉害👍🏻
    putaozhenhaochi
        11
    putaozhenhaochi  
       2023-07-19 20:31:18 +08:00 via Android
    基础好
    ericFork
        12
    ericFork  
       2023-07-20 04:23:14 +08:00
    体验到了屎上雕花的艰辛与伟大
    jazzg62
        13
    jazzg62  
       2023-07-20 08:50:07 +08:00
    厉害👍
    sinotw
        14
    sinotw  
       2023-07-20 10:19:35 +08:00 via iPhone
    楼主真棒👍Respect
    corcre
        15
    corcre  
       2023-07-20 10:21:08 +08:00
    我见过差不多的, 一个是在工控机上做一个仓管的进出库程序, web 端, 那个设备性能差到什么程度呢, 就一个页面, 几个输入框, 也没什么数据需要加载的, 用 vue 写的输入框, 扫码以后要等 3 秒输入框才开始慢慢悠悠往外蹦字符, 用原生输入框也要等 1 秒(不是扫码枪的设置问题, 接到正常电脑上面秒出)...
    还写过一个 wince 的, 那个更离谱, 只能用 vs2008 开发, 内存用 m 为单位, .net Compact Framework 2.0 环境, 便宜是真便宜, 慢也是真慢, 那个破打印机的文档还写得不清不楚, 不支持二维码转换, 需要自己生成二维码以后传图片传给他打印, 然后这玩意网速还奇慢, 结果就是 24k 的报文需要 24 秒以后才开始打印, 平均 1kb/s, 后来老板主动放弃了使用这玩意的想法...
    yehai
        16
    yehai  
       2023-07-20 17:19:28 +08:00
    @corcre #15 哈哈,我接触过大多数 wince 没有你说的那些问题,其实 wince 最麻烦的是分辨率。不同型号做适配要死人。现在都直接用安卓的丢给安卓开发折腾去吧
    yehai
        17
    yehai  
       2023-07-20 17:20:21 +08:00
    而且好像正常好的 PDA 并不便宜,摩托罗拉的都 8K 起步好像
    retrocode
        18
    retrocode  
    OP
       2023-07-20 17:35:07 +08:00
    @yehai #16 安卓都算好的了, 最多也就是兼容性问题, 改吧改吧就能解决, 主要 win 太长寿了, 现在有的场景还在使用十几年前的硬件配 win7 甚至 XP, 但是现在的 安卓机 过 5 年都已经算是高龄了. 我们开发的时候还遇到一台测试机用的 09 年版初版 win7 连 SP 都没有, .net 直接装不上, 对着报错找了好久, 才在微软的某个存档仓库里找到上传时间为 2011 年的升级补丁, 升级到 SP1 打上补丁才装的.net
    yehai
        19
    yehai  
       2023-07-20 20:15:48 +08:00
    @retrocode #18 哈哈体验过 win7 工控机用.NET CORE ,还有 Server 2012 用.NET CORE ,各种想不到的问题
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   877 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 21:50 · PVG 05:50 · LAX 13:50 · JFK 16:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.