我们的Web3.0 DApp上线之后性能很差,页面加载慢,交易确认慢,数据查询也慢。经过一系列优化之后,性能提升了很多。本文是我们的Web3.0性能优化实战总结,包括前端优化、合约优化、数据查询优化、网络优化等。如果你在做Web3.0项目,遇到了性能问题,希望这篇文章能帮到你。

一、项目背景

先说说我们的项目情况。

我们的项目是一个DeFi应用,用户可以在上面做质押、借贷、交易等操作。前端用的是React + ethers.js,智能合约用Solidity写的,部署在以太坊上。数据查询用的是The Graph,钱包用的是MetaMask。

上线之后发现性能问题很严重。页面首次加载要10秒以上,用户等不及就走了。连接钱包之后加载用户数据要好几秒。发送交易之后等待确认的时间很长,用户体验差。数据查询经常超时,页面一直显示加载中。移动端体验也很差,卡顿明显。

老板要求优化性能,目标是页面加载控制在3秒以内,数据查询控制在1秒以内。我负责这次性能优化,花了三周时间,从前端到合约到数据层做了全面的优化。最终页面加载降到了2秒左右,数据查询也快了很多。下面就来分享一下优化的过程和经验。

二、第一步:分析性能瓶颈

优化之前首先要找到性能瓶颈在哪里。不要上来就优化,要先分析。

我们用了几个工具来分析。Chrome DevTools的Performance面板可以记录页面加载和交互的性能数据,找到耗时的操作。Lighthouse给出性能评分和优化建议。ethers.js的性能日志记录每个RPC调用的耗时。The Graph的查询分析找出哪些子图查询慢。Etherscan的Gas分析查看合约调用的Gas消耗。

分析之后我们发现了几个主要的性能瓶颈。前端打包体积太大,主要是因为ethers.js和一些UI库没有按需加载。页面加载的时候发起了大量的RPC调用,而且是串行的,导致加载慢。智能合约的一些函数Gas消耗高,执行慢。The Graph的查询没有优化,每次都查全量数据。没有做数据缓存,每次进入页面都重新查询。

找到了瓶颈之后就开始针对性地优化。

三、前端优化

前端优化是性能优化的第一步,主要目标是减小打包体积,提升加载速度。

1. 路由懒加载和代码分割

这是最有效也是最简单的优化。把路由组件改成动态导入,这样首屏只加载当前页面的代码。

const StakePage = () => import('../pages/StakePage.vue')

除了路由懒加载,一些不常用的大组件(比如交易历史弹窗、高级设置面板)也用异步组件,需要的时候才加载。

改完之后首屏的JS体积减少了50%,效果非常明显。

2. 按需引入第三方库

我们用了一些比较大的第三方库,比如ethers.js、antd、lodash。最开始是全量引入的,导致打包体积很大。

ethers.js可以只引入需要的模块。antd用babel-plugin-import按需引入,lodash用lodash-es按需引入。

改完之后打包体积减少了30%。

3. RPC调用优化

这是Web3.0项目特有的优化。页面加载的时候我们发起了大量的RPC调用(获取余额、获取用户数据、获取合约状态等),而且是串行的,导致加载很慢。

优化方法包括并行调用、批量调用和减少调用。把不互相依赖的RPC调用改成并行的,用Promise.all。用Multicall合约批量调用多个只读函数,减少RPC请求次数。有些数据可以从其他数据推导出来,不需要单独调用。

用了Multicall之后RPC请求次数减少了70%,数据加载速度提升了很多。

4. 数据缓存

很多数据不需要每次都查询,比如合约的配置参数、代币的价格、用户的基本信息等。我们加了一层缓存,把查询结果缓存起来,设置合理的过期时间。

加了缓存之后重复进入页面的时候,数据几乎是瞬间加载的。

5. 状态管理优化

最开始的状态管理很混乱,每次RPC调用都会触发整个应用的重新渲染。我们引入了状态管理库,把状态按模块拆分,只在相关数据变化的时候更新对应的组件。

同时对一些大列表用了虚拟滚动,只渲染可视区域的元素,大大减少了DOM节点数量。

四、智能合约优化

智能合约的优化主要是降低Gas消耗,提升执行速度。

1. 减少存储读写

以太坊上存储的读写是最昂贵的操作。优化合约的时候要尽量减少存储的读写次数。可以用内存变量做中间计算,最后一次写入存储。

2. 用合适的数据类型

Solidity中数据类型越小Gas消耗越低。比如能用uint128就不要用uint256。但是要注意小的数据类型在运算的时候可能会被转换成uint256,所以要合理使用。把多个小变量打包在一个存储槽里可以节省存储费用。

3. 避免循环

在合约中避免循环,特别是循环长度不确定的情况。循环的Gas消耗会随着迭代次数增加,可能会超过区块Gas限制。如果需要批量处理,可以让用户分批调用,或者用链下计算加链上验证的方式。

4. 用事件代替存储

有些数据不需要存在合约存储里,只需要记录历史,可以用事件来记录。事件的Gas消耗比存储低很多,而且前端可以通过监听事件来获取数据。

五、数据查询优化

我们用The Graph来做数据查询,但是最开始的查询没有优化,导致查询很慢。

1. 只查需要的数据

最开始我们的查询每次都查全量数据,包括很多不需要的字段。优化之后只查页面需要的字段,减少数据传输量。

2. 分页查询

对于列表数据不要一次查全部,用分页查询。每页查20条,用户滚动的时候再加载下一页。

3. 建立索引

在The Graph的子图中对经常查询的字段建立索引,可以大大提升查询速度。比如按用户地址查询、按时间查询、按金额查询等。

4. 预计算和聚合

有些聚合数据(比如总质押量、总用户数)不要每次查询的时候实时计算,而是在子图中预计算好存起来。查询的时候直接取,速度快很多。

六、网络优化

1. 选择合适的RPC节点

RPC节点的速度对DApp的性能影响很大。我们最开始用的是公共RPC节点,经常超时。后来换成了专业的RPC服务(比如Alchemy、Infura),速度和稳定性都提升了很多。同时我们还做了多节点备份,如果主节点超时自动切换到备用节点。

2. 交易优化

交易确认慢是Web3.0应用的通病。我们做了几个优化。合理设置Gas Price,根据当前网络情况动态调整。发送交易之后给用户明确的状态提示。发送交易之后先在前端乐观更新状态,交易确认之后再同步链上数据。

3. CDN加速

静态资源用CDN加速,让用户从最近的节点加载资源,提升加载速度。

七、优化效果

经过三周的优化,我们的DApp性能提升很明显。页面加载时间从10秒以上降到了2秒左右。数据加载时间从5秒以上降到了1秒以内。RPC请求次数减少了70%。合约Gas消耗平均降低了30%。用户留存率提升了40%,因为加载快了用户愿意等了。

老板对优化结果很满意,用户体验也提升了很多。

八、经验总结

这次性能优化我总结了一些经验。

Web3.0的性能优化有其特殊性。除了常规的前端优化,还要考虑RPC调用、智能合约Gas、链上数据查询等特殊的优化点。做Web3.0性能优化要对区块链有一定的了解。

RPC调用是最大的瓶颈。在Web3.0应用中RPC调用往往是最大的性能瓶颈。减少RPC调用次数、并行调用、批量调用是最有效的优化手段。

缓存非常重要。链上数据查询比较慢,一定要做好缓存。能缓存的数据都缓存起来,设置合理的过期时间,可以大大提升用户体验。

合约优化要提前做。智能合约一旦部署就很难修改,所以合约的Gas优化要在部署之前就做好。不要等上线之后发现Gas太高了再优化,那时候改起来很麻烦。

性能优化是持续的过程。性能优化不是一次性的工作,项目在不断迭代,新的代码可能会引入新的性能问题。要建立性能监控机制,定期检查,持续优化。

九、写在最后

Web3.0应用的性能优化是一个系统工程,需要从前端、合约、数据层、网络等多个方面入手。虽然比传统Web应用复杂,但是只要找到瓶颈,针对性地优化,效果还是很明显的。

希望我们的优化经验能帮到正在做Web3.0项目的你。记住先分析再优化,用数据说话,针对性地解决问题。

最后用一句话结束本文:"性能优化没有银弹,只有持续的关注和改进。"愿每一个Web3.0开发者都能做出又快又好的DApp,给用户带来更好的体验。