Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

关于 $digest 性能优化的一点建议 #1090

Closed
ystarlongzi opened this issue Mar 16, 2018 · 23 comments
Closed

关于 $digest 性能优化的一点建议 #1090

ystarlongzi opened this issue Mar 16, 2018 · 23 comments

Comments

@ystarlongzi
Copy link
Contributor

ystarlongzi commented Mar 16, 2018

在使用 wepy 开发时,目前感觉性能瓶颈在以下两个点比较突出:

1. $apply 触发过于频繁

关于这点,比如会在 onUnloadonPageScroll 时触发 $apply,之前有 issue#1022 提到过并已修复

let PAGE_EVENT = ['onLoad', 'onReady', 'onShow', 'onHide', 'onUnload', 'onPullDownRefresh', 'onReachBottom', 'onShareAppMessage', 'onPageScroll', 'onTabItemTap'];

上面的代码是可能会触发 $apply 的一些生命周期,可以看到,wepy 默认还会对除了 onUnloadonPageScroll 之外这些生命周期后执行 $apply,这样确实方便了开发,但在某些场景下,比如:

  • 从「列表页 ---> 详情页」,此时列表页触发了 onHide
  • 然后在从「详情页 ---> 列表页」,此时列表页又触发了 onShow
  • 在「列表页」点击了转发,触发了 onShareAppMessage

上面的场景中,「列表页」的数据并可能并没有发生变化,但也会触发 $apply,对性能就会一定的影响,尤其是列表数据比较多时。

但如果开发者能知道当前页面数据并没有发生变化,其实是可以通过一些手段,避免 wepy 进入 $digest,提高页面性能。比如尝试使用以下方法:

onShow () {
  this.disableDigest()
}

onHide () {
  this.disableDigest()
}

onShareAppMessage () {
  this.disableDigest()
}

disableDigest () {
  this.$$phase = true

  setTimeout(() => {
    this.$$phase = false
  })
}

这利用了 wepy 会根据 this.$$phase 的值决定是否进入 $digest,相关代码如下:

$apply (fn) {
if (typeof(fn) === 'function') {
fn.call(this);
this.$apply();
} else {
if (this.$$phase) {
this.$$phase = '$apply';
} else {
this.$digest();
}
}
}

2. $digest 数据量大时,「脏检测」比较耗性能

============== 2018年03月19日15:47:20 更正 ✏️==============================
⚠️⚠️ 实际上,wepy 在数据脏检测上没有性能问题。下面的分析过程有问题,性能原因见评论
==================== ================================================

$digest 时,wepy 为了能找出新旧数据的差异,会对数据进行【深比较】,然后在比较完成后,还会对数据进行【深拷贝】,当数据量比较多时,这两个过程都是比较耗性能的点。

============== 2018年03月17日12:29:56 更正 ✏️==============
【深拷贝】对性能影响很小,这里描述不对,具体见下面评论
====================== ===============================

但在某些场景下,可以从其它角度优化这个过程,比如:

  1. 对 Immutable 数据类型,仅比较是否相等即可
  2. 建议开发者对数据修改时,每次都返回一个新的对象,而不是在原来的对象上修改

由于在我们项目里使用了 Immutable,所以,根据方案 1,我在本地对 $digest 代码做了调整,简单的在【深比较】前(代码位置)添加了对 Immutable 数据类型的判断,相关代码如下:

// ......
function isImmutable(maybeImmutable) {
  if (!maybeImmutable || typeof maybeImmutable !== 'object') {
    return false
  }

  const IMMUTABLE_KEYS = [
    '@@__IMMUTABLE_ITERABLE__@@',
    '@@__IMMUTABLE_KEYED__@@',
    '@@__IMMUTABLE_INDEXED__@@',
    '@@__IMMUTABLE_ORDERED__@@',
    '@@__IMMUTABLE_RECORD__@@'
  ]

  return !!IMMUTABLE_KEYS.filter(key => maybeImmutable[key]).length
}

// ......

$digest () {
  // ......
  while (this.$$phase) {
    // ......
    for (k in originData) {
      let immutableAndNotEq = false
      if (isImmutable(this[k])) {
        // ⚠️⚠️ 因为 `originData` 已经是【深拷贝】后的数据,所以这里和 `this.data` 做对比
        if (this[k] === this.data[k]) {
          continue
        }
        immutableAndNotEq = true
      }

      if (immutableAndNotEq || !_util2.default.$isEqual(this[k], originData[k])) {
        // ......
      }
    // ......
  }
  // ......
}

// ......

这样优化后,当无限滚动列表加载 10 页左右后,列表数据量大致在 (15 * 10 = 150) 条时,对比效果非常明显,即使列表在加载 20页、30页,页面也明显的飞快🚀🚀,如下:

image

所以,我觉得 wepy 在【深比较】和【深拷贝】时,是否可以加上对 Immutable 数据类型的判断,如果是 Immutable 数据类型,只做浅比较和浅拷贝即可

@ystarlongzi
Copy link
Contributor Author

ystarlongzi commented Mar 16, 2018

另外对第 1 点 $apply() 触发频繁的问题,我建议对 $digest 也加个 debounce

@Gcaufy
Copy link
Collaborator

Gcaufy commented Mar 16, 2018

  1. 同意,但具体实现可能需要再思考一下看怎么处理。
  2. 最初的设计考虑是避免多次setData,因为一个是在内存里做深比较,一个是在做类似于postMessage之类的事情,性能损耗不是一个量级的。详情可以看这个:https://segmentfault.com/a/1190000008975448。因此本质上来说是对setData的一种性能优化。当然肯定还是存在一些优化的空间的。
  3. 现在 2.0 是完全排除了这个做法,不会再去做深比较了,有兴趣可以看已提交的代码。

非常感谢对WePY的深入研究,但1.x版本目前只计划做一些必要的bug修复,至于优化和增加还是交给大家来做吧。
这个问题建议可以写一些使用wepy性能优化的相关文章给其它人参考学习,或者是将想法更合理的实现出来PR进来。

@Gcaufy Gcaufy closed this as completed Mar 16, 2018
@cuijiemmx
Copy link

@ystarlongzi 我擦哥们你写得这么详尽,佩服啊

@cuijiemmx
Copy link

@ystarlongzi 插个嘴,onShow时$apply的需求我之前在没有使用wepy-redux时遇到了,A,B两个页面共享同一个数据,如果从A跳到B,在B中变更了共享的数据后返回A,此时A的onShow中如果不$apply页面就不会同步了

@ystarlongzi
Copy link
Contributor Author

ystarlongzi commented Mar 16, 2018

@Gcaufy
嗯嗯,如果你赞成添加对 Immutable 数据类型的判断,我先对 1.x 提个 PR,然后再讨论

@ystarlongzi
Copy link
Contributor Author

@cuijiemmx
嗯嗯,是的,所以开发者如果想用那种方案,就需要能判断处在页面 onShowonHide 时,数据是否可能会发生变化,如果不能确定是否会发生变化,那就不建议使用了

@cuijiemmx
Copy link

@ystarlongzi 我准备转mpVue了,可能可以解决遇到的大部分问题...

@ystarlongzi
Copy link
Contributor Author

😂😂😂 还是用亲儿子好,如果 mpVue 是 KPI 产物就哭了,等 wepy 2.0 发布后,应该会好很多

@cuijiemmx
Copy link

@ystarlongzi 我在想那头会不会也是一堆坑等着我,伤不起啊

@ystarlongzi
Copy link
Contributor Author

【更正】下面这个截图里的描述信息不对 ❌❌
image

今天在 debugg 的时候,发现【深拷贝】在这个测试里对性能的影响很小很小。然后最终在 debugg 到下图位置:

image

发现这时是将 Immutable 数据发送给小程序的 JavascriptCore 的,然后就尝试了下在
setData 时,将 Immutable 数据类型全部给 toJS() 掉,发现性能提升很大,基本上也就 100ms 的样子

@ystarlongzi
Copy link
Contributor Author

@Gcaufy 小程序组件化框架 WePY 在性能调优上做出的探究 作者是你嘛?

如果是你,文章里关于小程序 setData 代码实现部分,是怎么看到源码的

@ystarlongzi
Copy link
Contributor Author

@Gcaufy @cuijiemmx
抱歉,我刚又验证了下,这个 issue 里性能问题描述的不对。

实际上,wepy 在数据脏检测上没有性能问题

我这里上拉加载 15 * 10 = 150 条数据,不管是纯 js 对象,还是 Immutable 数据对象,【深比较】都没啥性能问题。

我这里碰的性能问题,最终定位到是由于 this.setData() Immutable 数据类型导致的,只要在 this.setData 前,将 Immutable 数据类型转换为纯 js 对象,性能就没啥问题了 😅 😅😅

@cuijiemmx
Copy link

@ystarlongzi 性能问题还是有的吧,毕竟是深比较,可能纯js对象数据结构不够深不够复杂?

@ystarlongzi
Copy link
Contributor Author

我这里深度只有 3,随着数据量的增加,发现性能变化并不大

@ystarlongzi
Copy link
Contributor Author

我这里碰到的性能是 this.setData() 包含 Immutable 数据导致的

@bigmeow
Copy link

bigmeow commented Mar 20, 2018

@ystarlongzi this.setData()被封装在wepy里面了,所以你最后还是改的框架?

@ystarlongzi
Copy link
Contributor Author

嗯嗯,我本地修改后,然后 debug 调试,对比了下

@lingxuan630
Copy link

在数据量较多的情况下,深度对比是非常损耗性能的,在手机上,会产生明显的卡顿。已经在项目中验证了。其实wepy采用了类似angularjs 1的脏检查机制,效率并不高。阅读wepy 2.0部分代码后,发现这方面确实有改善,期望2.0能尽快发布。

@ystarlongzi
Copy link
Contributor Author

@lingxuan630 我这里碰到的性能是因为 Immutable 数据导致的,脏检测对性能影响并不大

@Gcaufy
Copy link
Collaborator

Gcaufy commented Jun 27, 2018

@ystarlongzi 作者是我,那篇文章中setData 的代码是通过查看和调试 WAService.js 的代码得出的结论。我也没有权限拿到非压缩的代码,也只是对着压缩混淆后的代码阅读的。

如果这样问题上还有更好的想法可以随时跟我沟通

@wcdxhl0117
Copy link

也遇到这个问题,畏怯还必须解决,大佬们最后如何优化?

@Slngle
Copy link

Slngle commented Aug 1, 2018

@ystarlongzi Immutable 数据导致的这部分代码是在wepy里面的吗?我也遇到这个问题,还没有解决

@ystarlongzi
Copy link
Contributor Author

@Slngle 升级下这两个库试一下

  1. wepy-cli v1.7.3
  2. wepy v1.7.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants