-
Notifications
You must be signed in to change notification settings - Fork 217
perf: 复用行列头/数值单元格 以提升百万滚动性能至50FPS #3222
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
base: next
Are you sure you want to change the base?
Conversation
# Conflicts: # packages/s2-core/src/facet/base-facet.ts
…header # Conflicts: # packages/s2-core/src/cell/header-cell.ts
# Conflicts: # packages/s2-core/package.json # packages/s2-core/src/sheet-type/spread-sheet.ts # pnpm-lock.yaml
…_0718 # Conflicts: # packages/s2-core/src/group/grid-group.ts # packages/s2-core/src/utils/g-renders.ts # packages/s2-react/playground/components/BigDataSheet.tsx
…header # Conflicts: # packages/s2-core/src/cell/DataCellPool.ts # packages/s2-core/src/facet/base-facet.ts # packages/s2-core/src/utils/g-renders.ts
|
你好 @Alexzjt,非常感谢你的贡献. Hello, @Alexzjt, Thanks for your contribution. In order to make the code more robust, please add the corresponding unit tests, and update the docs if there are API changes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Summary of Changes
Hello @Alexzjt, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
此拉取请求的核心目标是大幅提升表格在百万数据量滚动时的渲染性能,旨在达到 50FPS 的流畅度。这主要通过实现全面的图形对象复用和引入单元格对象池机制来达成,从而减少了不必要的对象创建和销毁,优化了渲染循环。
Highlights
- 核心性能优化:图形对象复用: 通过引入
batchSetStyle和createOrUpdateRect等工具函数,实现了单元格(数据单元格、行列头单元格)、边框、背景、文本、链接、条件图标以及网格线等图形元素的复用渲染。这意味着在滚动或更新时,不再频繁创建和销毁图形对象,而是更新现有对象的属性,显著减少了垃圾回收的压力和渲染开销。 - 单元格对象池机制: 新增了
BaseCellPool及其子类(ColCellPool、DataCellPool、RowCellPool),为行列头和数据单元格提供了高效的复用和回收机制。当单元格不再可见时,会被放入对象池等待下次复用,进一步提升了大数据量下的滚动性能。 - 实验性功能开关: 在
s2Options中引入了future.experimentalReuseDataCell实验性功能开关,允许开发者选择性地启用数据单元格的复用特性,便于测试和逐步推广。 - 渲染引擎配置增强: 现在可以通过
s2Options.rendererConfig配置 AntV/G 渲染引擎的参数,提供了更灵活的底层渲染控制能力。
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in issue comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
Footnotes
-
Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩
This comment was marked as duplicate.
This comment was marked as duplicate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
本次 PR 旨在通过复用单元格来提升滚动性能,这是一个非常棒的优化方向。代码的改动范围较广,涉及了单元格基类、各种头单元格、数据单元格以及 Facet 和 Header 的实现,核心思路是在已有图形和单元格对象上更新属性,而不是频繁销毁和创建,这能有效减少 GC压力并提升渲染速度。
代码整体实现得很好,特别是引入了单元格对象池(Cell Pool)和一系列工具函数来支持复用逻辑。
我发现了一些可以进一步优化的地方,主要集中在对象池的实现上,希望能帮助你让这次性能优化更加彻底。具体请看我的详细评论。
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
这个 PR 通过引入对象池和复用图形元素(单元格、形状、图标等)来显著提升滚动性能,这是一个非常棒的性能优化。核心改动包括:
- 为行头、列头和数据单元格实现了对象池,以减少频繁的对象创建和销毁。
- 在单元格渲染逻辑中,对背景、边框、文本、图标等图形元素进行了复用,而不是每次都重新创建。
- 引入了
batchSetStyle工具函数,利用 G 的setAttributes批量更新样式,减少不必要的重绘事件,进一步提升性能。 - 为
GuiIcon添加了reRender和updatePosition方法,支持更高效的图标更新。 - 通过
future配置项引入了实验性功能开关experimentalReuseDataCell,这是一个很好的实践,可以安全地引入重大变更。
整体代码改动清晰地遵循了“复用代替创建”的原则,对提升大数据量下的滚动流畅度有很大帮助。代码质量很高,但有几个可以改进的地方,主要关于类型安全。
| public setHeaderConfig(headerConfig: ColHeaderConfig) { | ||
| super.setHeaderConfig(headerConfig); | ||
| // this.drawResizeArea(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| this.scrollGroup.childNodes.forEach((colCell: ColCell) => { | ||
| if (!this.isColCellInRect(colCell.getMeta())) { | ||
| colCell.getMeta().belongsCell = null; | ||
| this.colCellPool.release(colCell); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
在 forEach 循环中使用 @ts-ignore 并将 colCell 断言为 ColCell 类型不是类型安全的。childNodes 数组的类型是 DisplayObject[],其中可能包含非 ColCell 类型的实例。如果未来 scrollGroup 中添加了其他类型的对象,这可能会导致运行时错误。建议添加类型检查来增加代码的健壮性。
| this.scrollGroup.childNodes.forEach((colCell: ColCell) => { | |
| if (!this.isColCellInRect(colCell.getMeta())) { | |
| colCell.getMeta().belongsCell = null; | |
| this.colCellPool.release(colCell); | |
| } | |
| }); | |
| this.scrollGroup.childNodes.forEach((child) => { | |
| if (child instanceof ColCell) { | |
| if (!this.isColCellInRect(child.getMeta())) { | |
| child.getMeta().belongsCell = null; | |
| this.colCellPool.release(child); | |
| } | |
| } | |
| }); |
| this.scrollGroup.childNodes.forEach((rowCell: RowCell) => { | ||
| if (!this.isCellInRect(rowCell.getMeta())) { | ||
| rowCell.getMeta().belongsCell = null; | ||
| this.rowCellPool.release(rowCell); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
与 col.ts 中的问题类似,此处使用 @ts-ignore 并将 rowCell 断言为 RowCell 类型不是类型安全的。childNodes 数组包含 DisplayObject,增加一个类型检查可以防止潜在的运行时错误。
| this.scrollGroup.childNodes.forEach((rowCell: RowCell) => { | |
| if (!this.isCellInRect(rowCell.getMeta())) { | |
| rowCell.getMeta().belongsCell = null; | |
| this.rowCellPool.release(rowCell); | |
| } | |
| }); | |
| this.scrollGroup.childNodes.forEach((child) => { | |
| if (child instanceof RowCell) { | |
| if (!this.isCellInRect(child.getMeta())) { | |
| child.getMeta().belongsCell = null; | |
| this.rowCellPool.release(child); | |
| } | |
| } | |
| }); |
| renderLine(this.gridGroup, { | ||
| x1: x - halfVerticalBorderWidthBorderWidth, | ||
| x2: x - halfVerticalBorderWidthBorderWidth, | ||
| const children = this.gridGroup.children as Line[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| export function createOrUpdateRect( | ||
| propertyPath: string, | ||
| style: RectStyleProps, | ||
| ) { | ||
| // @ts-ignore | ||
| const context = this as any; | ||
| const obj = get(context, propertyPath); | ||
|
|
||
| if (!obj) { | ||
| set(context, propertyPath, new Rect({ style })); | ||
| } else { | ||
| batchSetStyle(obj, style); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👀 PR includes
✨ Feature
🎨 Enhance
🐛 Bugfix
🔧 Chore
📝 Description
在S2百万数据交叉表滚动场景下(1649个元素),S2花费了巨量的时间在创建、计算和渲染单元格上,伴随着大量单元格、line、rect、text等Destroy与new。
大量的对象在短时间内被创建然后又被废弃,会导致频繁的垃圾回收和较高的性能开销。
所以有必要采取复用已有DisplayObject这样性能友好的写法来提高性能。
依赖 antvis/G#2001
🖼️ Screenshot
🔗 Related issue link
🔍 Self-Check before the merge