关于"好奇移动端、桌面端是怎么实现列表控件渲染大量元素不卡顿的?"引申的问题

56 天前
 Kinnikuman

上一个问题( https://www.v2ex.com/t/1059281 )我看已经讨论结束了,所以新开一个帖子来讨论下。

我的问题是,大量的列表会导致滑动卡顿吗?移动端有"回收,重用,缓存"这种策略,但如果不使用这种策略,而将大量的列表数据加载到内存中,滑动时候会卡顿吗?

我的理解是它们已经加载到内存中了,滑动只是将其展示出来,缺点是占用内存特别大。

如果使用了回收策略,只有屏幕展示的那几条列表会被加载到内存中,滑动出去的放到复用列表中,以供下次使用,这样可以节约大量内存,但在快速滚动刷新的列表中,这需要 cpu 进行大量的计算来刷新列表中的数据吧?

所以我觉得,如果不使用回收策略,那么 cpu 会在第一时间创建很多列表数据,这会导致一开始卡顿,创建完数据后,占用很大内存。但之后的刷新,不应该卡顿。

如果使用回收策略,内存压力小了,开始不需要进行大量的 cpu 计算,所以不会有开始渲染卡顿问题。但后面的快速刷新会消耗 cpu 。

2945 次点击
所在节点    程序员
29 条回复
LuckyLauncher
56 天前
数据是数据,展示出来是“绘制”这个动作
你把你的手机屏幕上展示的东西想象成一张静态的图,你滑动的时候这张图是被实时绘制出来的,不管数据在哪,要渲染的组件有没有创建(哪怕是已经创建了但是给隐藏了),“绘制”这个动作是跑不了的

你可以用 canvas 之类的自己写一个列表看看
jones2000
56 天前
虚拟表格不需要把所有数据都加载到内存, 只加载一个索引序号就可以了,滚到到哪里就请求哪一屏的数据。就算存在内存里面, 能用多少内存, 比起创建 10W 个 DOM 占用的内存,100W 条数据内存就小多了。
IvanLi127
56 天前
CPU: 谢谢你
GPU: 我谢谢你
RightHand
56 天前
单纯数据其实还好,绘制才是卡的大头。
weixind
56 天前
这个场景的瓶颈首先是在渲染,而不是数据源。有定论的知识点,有啥可讨论的。
iOCZS
56 天前
绘制的消耗是必然存在的,在绘制间隙的 CPU 时间片可以用于计算,这是合理的,CPU 闲着也是闲着。
不使用回收策略,那么 cpu 会在第一时间创建很多列表数据。这个有待商榷,一般是分页获取数据,因此内存消耗是累计的,CPU 未必存在一开始就卡的问题。
Kinnikuman
56 天前
@LuckyLauncher

@RightHand

绘制是另一个问题了,有专门的硬件处理。

所以,我的问题有问题是吗?相对于绘制来讲,那些 cpu 计算复杂的列表如何更换数据,都是很 easy 的计算?
Kinnikuman
56 天前
@iOCZS "CPU 闲着也是闲着",不这么认为,能让 cpu 少工作,也算是性能优化的一部分吧。当然这个话题中是舍弃掉内存来换取 cpu 的部分工作。而且这个讨论不考虑分页获取数据,就是一个几万几十万条的数据一次性加载到 list 中。
Chuckle
56 天前
虚拟列表是在滚动时计算出要渲染的元素在数组中的索引范围,普通的定高、不定高的计算量不大,很流畅,但是不定高的瀑布流,还伴随着图片加载的话,计算量就很大了,写过个 demo ,https://list.qcqx.cn/#/list/virtualwaterfall
Chuckle
56 天前
@Chuckle 后端把图片宽高返回的话,计算量能小点,小红书就是这么干的
ipwx
56 天前
在桌面 UI 时代,有一个东西叫做 onDraw (clipRect):UI 框架告诉你,现在你这个控件需要显示 (x0,y0) -> (x1,y1) 区域的东西,你自己画吧。

所以你根本不需要构造一堆 DOM 元素。列表在你的内存里面仅仅是数据,比如 List[{name: Steven, age: 13, ...}],然后你自己先把每个列表项渲染出来的坐标范围给计算出来存着,然后根据 UI 的需求把显示出来的对象画出来就可以了。

而且如果你遍历一遍你的列表找 (x0,y0) -> (x1,y1) 范围内的元素慢(这是 O(n) 的操作),你可以上数据结构,比如线段树,然后你就 O(log n) 了。

用上这套优化,百万个元素也不在话下。毕竟内存里面放一百万个对象才多少,也就几百兆么(注意 1 兆 = 一百万字节)。
----

题外话,所以很多前端不理解 “干嘛老考数据结构和算法”,那是因为没遇上需求。。。
ipwx
56 天前
另外吐槽一句,上古时代 onDraw 要写的东西太多了以至于是大神才能玩的。

后来各大 UI 框架都有了它们自己的绘图的框架,降低了这套东西的难度。我学得少,只知道一个 Qt 的 GraphicsView 干这事,还有 JS 可能有一些 Canvas 的库干这些活。其他就不知道了。
Chuckle
56 天前
@Chuckle #9 拟列表一般都是滑到底部后增量加载,类似分页,并不是一次性把所有数据加进 list ,而且计算布局也限制在视口附近的元素,优化手段还是很多的,查找要渲染的元素范围用二分,当然,往下滑动多了,list 还是会很大,可以考虑分数组、按范围计算,甚至上 canvas ,不过一般来说那点数据量 cpu 应付得过来的,总比上万个 dom 元素好多了,至于内存占用,这个没特殊限制倒没大问题,100w 个对象也才多大,重点还是列表布局的渲染,数据量大了怎么搞都是妥协,布局还是得老老实实算。这 demo 写得也一般,但是不定高虚拟瀑布流也能应付无图片的上万条数据。
Skifary
56 天前
"所以我觉得,如果不使用回收策略,那么 cpu 会在第一时间创建很多列表数据,这会导致一开始卡顿,创建完数据后,占用很大内存。但之后的刷新,不应该卡顿。

如果使用回收策略,内存压力小了,开始不需要进行大量的 cpu 计算,所以不会有开始渲染卡顿问题。但后面的快速刷新会消耗 cpu 。"

------------------------------------------------

1 ,你没有理解 UI 对象,任何框架的 UI 对象都有绘制函数,而绘制函数是每一帧都会执行的,不管 UI 对象是否处于可视范围内,这是大数据列表卡顿最根本的原因。

2 ,使用缓存只会创造固定数目的 UI 对象,那么无论多大的数据量,最终的需要绘制的对象永远只有那几个,不会随着数据量的增长而增长。

3 ,对比“后面的快速刷新会消耗 cpu”需要的 cpu 算力和“渲染成千上万 UI 对象”所需要的 cpu 算力相比就是九牛一毛。
crz
56 天前
@Chuckle 不定高的问题是滚动条,普通元素的滚动条是和内容线性对应的,虚拟列表要手动对齐这部分交互体验。

以你的 demo 为例子,不定高度一页是 2000 条数据,ctrl+end 定位的话尾部应该是第 2000 条,我这里操作结果是 1946 。再一个是拖动滚动条,能明显看到鼠标指针和滚动条慢慢错位。

滚动条拉到一半位置,定高只要按索引折半,再细点就算上容器高度,简单的计算。不定高就不一样了。

碰到不定高度的直接想法就是算出来缓存,后台绘制,缓存。问题是虚拟列表数量往往不会少,就算只绘制一遍也是不小的开销,要是再加上加载图片的场景,时间和精确至少少一个。

后端给出高度也是一个方案,不考虑后端如何得到数据的问题,前端的一个问题场景是不定宽度的容器,数据高度受宽度影响也会变化。

不考虑滚动条的事行不行?也可以,虽然体验有区别,细节处大部分人没碰到也不会在意,也许以后大家都默认虚拟列表的交互就是这样的
kera0a
56 天前
生成这么多元素,就算不绘制只去判断元素是否要绘制都是一笔很大的开销,列表每滚动一帧就得来一次批量判断,计算元素在不在显示范围
archxm
56 天前
有优化手段的,做过 duilib
xu33
56 天前
这个主要是渲染问题,一般数据全部放内存够了,数据如果实在大,可以考虑持久化存储再 load ,内存里只放一个 id
unco020511
56 天前
你还是没理解
DOLLOR
56 天前
不是应该提供分页、跳页和条件筛选吗?
这么大量的数据,即使 UI 不卡,要找到想要的数据也不方便,用户体验也不会好。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1059633

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX