【问题标题】:Figuring out performance issues找出性能问题
【发布时间】:2018-02-05 16:00:52
【问题描述】:

在过去的几天里,我一直在解决我创建的应用程序中 FlatList 的性能问题。

FlatList 由静态标题和 x 行组成。在测量的情况下,有 62 行,每行由 4 个组件组成——其中一个被使用了 4 次,总共有 7 个行单元格。此列表中的每个单元格都是 TouchableNativeFeedback 或 TouchableOpacity(出于测试目的,那些已将 () => null 附加到 onPress。在几个级别(列表容器、行、单个单元格)上使用了 shouldComponentUpdate,我认为渲染性能对于这种情况来说已经足够好了列表。

为了获得一致的测量结果,我使用了initialNumToRender={data.length},因此整个列表会立即呈现。列表使用按钮呈现,数据加载不是我测量的一部分 - 它已预加载到组件本地状态。

根据附加的 Chrome Performance Profile JS 线程需要 1.33s 来渲染组件。我已经使用 CPU 减速 x6 来更准确地模拟 Android 设备。

但是,列表会在大约 15 秒标记处显示在设备上,因此从按下按钮到显示列表的实际渲染需要超过 14 秒!

我想弄清楚的是 JS 渲染组件和实际显示在屏幕上的组件之间发生了什么,因为设备是 整个那段时间反应迟钝。每个触摸事件都被注册,但只有在列表最终出现在屏幕上时才会播放。

我已经附加了来自 chrome 开发工具的跟踪、使用 android systrace 工具获取的 systrace 以及来自 android profiler 的屏幕(遗憾的是我找不到导出后者的选项)。

跟踪几乎同时运行 - 顺序是 systrace、android profiler、chrome 开发工具。

我应该采取哪些步骤来帮助我了解应用冻结时发生的情况?

Simple reproduction application (code in src.js, first commit)

Chrome Performance Profile

Systrace HTML

Android 分析器

【问题讨论】:

  • 您能否在例如小吃.expo.io ?
  • 我已经准备好复制应用,但它正在等待发布批准。我明天早上第一件事就回来了。
  • 我在第一篇文章中添加了复制应用程序仓库的链接

标签: react-native react-native-android


【解决方案1】:

我尝试不同的东西有一段时间了,我什至考虑封装原生 Android RecyclerView,但公平地说,这似乎是一个很大的挑战,因为我之前没有使用原生 Android 代码的经验。

过去几天我尝试过的其中一件事情是使用react-native-largelist,但它并没有带来承诺的性能改进。公平地说,它可能比FlatList 还要慢,但我没有进行精确测量。

经过几天的谷歌搜索、编码和分析后,我终于设法接触到this Medium post,它引用了recyclerlistview 包,它似乎提供了比 FlatList 更好的体验。对于profiled case渲染时间下降到2s左右,包括300ms的JS线程工作。

必须注意,初始渲染的改进来自于渲染项目数量的减少(在我的例子中是 11 个)。 FlatList 和 initialNumToRender={11} 设置最初在大约同一时间呈现。

在我的例子中,初始渲染虽然仍然很重要,但并不是唯一重要的事情。 FlatList 较大列表的性能下降主要是因为在滚动时它将所有呈现的行保留在内存中,而 recyclerlistview 回收呈现的行并放入新数据。

重新渲染观察者性能改进的原因实际上很容易测试。我在行组件中添加了console.log 到shouldComponentUpdate,并计算了实际重新渲染的行数。对于我的行高和测试设备分辨率,recyclerlistview 仅重新渲染 17 行,而FlatList 为数据集中的每个项目触发shouldComponentUpdate。还值得注意的是,为 recyclerlistview 重新渲染的行数与数据集大小无关。

我的结论是,FlatList 的性能在使用更大的数据集时可能会进一步下降,而recyclerlistview 的速度应该保持在相似的水平。

TouchableNativeFeedback 内的recyclerlistview 似乎也更敏感,因为动画会立即启动,但我无法解释查看分析器的行为。

我的行组件肯定还有改进的余地,但现在我对整体列表呈现性能感到满意。

Simple reproduction application with recyclerlistview (code in src.js, second commit)

Chrome 性能配置文件 (recyclerlistview)

Systrace HTML (recyclerlistview)

【讨论】:

    【解决方案2】:

    只需再提供一个资源即可尝试。

    你可以像这样在android/app/build.gradle中启用Hermes。

    project.ext.react = [
            entryFile   : "index.js",
            enableHermes: true,  // clean and rebuild if changing
    ]
    

    我的应用程序没有崩溃,但经过一些繁重的处理后,可触摸设备将停止工作。发生这种情况时,进程仍然运行正常,应用程序的抽屉仍在工作。但是我的应用程序中的所有可触摸都停止工作,没有任何错误消息。我花了3天的时间寻找解决方案,最后尝试了hermes。在react native docs,上面写着

    Hermes is an open-source JavaScript engine optimized for running React Native apps on Android.

    【讨论】:

    • 哦,我听说过 Hermes,它看起来很棒,但我不再从事那个项目了 :D。
    【解决方案3】:

    看source code you posted,我不认为这是一个React渲染问题。

    问题是您在 render 方法和渲染过程中调用的辅助方法中做了太多工作。

    每次在数组上调用.filter、.forEach 或.map 时,整个列表都会迭代n 次。当您对m 组件执行此操作时,您将获得O(n * m) 的计算复杂度。

    例如这是TransportPaymentsListItem渲染方法:

    /**
     * Render table row
     */
    render() {
      const {
        chargeMember,
        obligatoryChargeNames,
        sendPayment,
        eventMainNavigation
      } = this.props;
    
      /**
       *  Filters obligatory and obligatory_paid_in_system charges for ChargeMember
       */
      const obligatoryChargesWithSys = this.props.chargeMember.membership_charges.filter(
        membershipCharge =>
          membershipCharge.type === "obligatory" ||
          membershipCharge.type === "obligatory_paid_in_system"
      );
    
      /**
       *  Filters obligatory charges for ChargeMember
       */
      const obligatoryCharges = obligatoryChargesWithSys.filter(
        membershipCharge => membershipCharge.type === "obligatory"
      );
    
      /**
       *  Filters obligatory adjustments for ChargeMember
       */
      const obligatoryAdjustments = this.props.chargeMember.membership_charges.filter(
        membershipCharge =>
          membershipCharge.type === "optional" &&
          membershipCharge.obligatory === true
      );
    
      /**
       *  Filters obligatory trainings for ChargeMember
       */
      const obligatoryTrainings = this.props.chargeMember.membership_charges.filter(
        membershipCharge =>
          membershipCharge.type === "training" &&
          membershipCharge.obligatory === true
      );
    
      /**
       *  Filters paid obligatory adjustments for ChargeMember
       */
      const payedObligatoryTrainings = obligatoryTrainings.filter(
        obligatoryTraining =>
          obligatoryTraining.price.amount ===
          obligatoryTraining.amount_paid.amount
      );
    
      // This one does a .forEach as well...
      const sums = this.calculatedSums(obligatoryCharges);
    
      // Actually render...
      return (
    

    在示例代码中有 11 个这样的迭代器调用。对于您使用的 62 行数据集,数组迭代器总共调用了 4216 次!即使每个迭代器都是非常简单的比较,简单地遍历所有这些列表也太慢并且阻塞了主 JS 线程。

    为了解决这个问题,您应该在组件链中将状态转换提升到更高的位置,这样您就可以只进行一次迭代并构建一个视图模型,您可以将组件树向下传递并以声明方式呈现,而无需额外的工作。

    【讨论】:

    • 谢谢!您肯定说得很好,应该修改我向组件提供数据的方式。不过,我的理解是 render 方法在 JS 线程 (mqt_js) 中运行,而不是在 mqt_native_modules 中运行。文档说 TouchableNativeFeedback 是原生模块包装的。它没有提及FlatList。我已经将所有TouchableNativeFeedback 换成了TouchableOpacity,所以所有工作都应该在 JS Thread 中完成,但在 Chrome Profiler 中仍然只有大约 1.5 秒的工作。那么我对问题的理解是错误的吗?实际上render 中的所有内容都在mqt_native_modules 中运行?
    • 所有原始视图,包括TouchableOpacity、View、Text,都是封装在 JavaScript 中的原生模块。 FlatList 不是,但它在内部使用本机视图。我不确定分析器的统计信息,但听起来mqt_js 应该包含主 JS 线程。但是,作为比较点,我有一些 UI 组件渲染的视图比您的列表多得多,并且挂载时的渲染性能不受影响。同样,将 Touchable* 组件更改为 Views 并不会显着提高代码中的呈现速度。不幸的是,我没有时间深入研究分析。
    • 好的,我想我可以执行的最简单的测试是删除所有 .map、.filter 和 .forEach 调用,并尝试使用完全静态的数据来渲染我的组件,以查看几乎是纯的绘图将执行。我确信所有这些过滤调用都在前约 1.5 秒内运行,此时mqt_js 正在显示活动,与mqt_native_modules 活动时间的约 13.5 秒相比,这似乎并不多。当我有时间做一些测试时,我会回来提供结果。
    • 你很可能是对的。我只是在代码中看不到任何可以保证高安装成本的东西,但我很愿意犯错。
    • 好的,我做了静态数据测试,结果还是一样。我认为问题在于我的分析方式。我一直在远程调试中运行每一个测试,所以我可以在 Chrome 中看到功能瀑布。它使mqt_js 在浏览器中而不是在设备上运行,即使 CPU 速度降低 6 倍,它似乎仍然太快了。另一方面,mqt_native_modules 仍在设备上运行,这使其运行缓慢。在关闭调试的情况下在 Android Studio 中进行分析表明,实际上mqt_js 比mqt_native_modules 做的工作要多得多,这使您建议立即优化状态转换。
    猜你喜欢
    • 1970-01-01
    • 2017-03-25
    • 2011-04-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-13
    • 2010-09-08
    • 2022-01-04
    相关资源
    最近更新 更多