当在linux上运行一个大型应用(DevTools)时,我注意到该应用会冻结。当我通过DevTools对其进行性能分析时,我发现它在语义代码上花费了很多时间。_drawFrame
-> _handleDrawFrame
-> ... -> RendererBinding.drawFrame
-> ... -> PipelineOwner.flushSemantics
-> ... -> SemanticsNode.attach
.
观察这段代码,很明显为什么它会花费这么多时间:
@visibleForTesting
void attach(SemanticsOwner owner) {
...
while (owner._nodes.containsKey(id)) {
// Ids may repeat if the Flutter has generated > 2^16 ids. We need to keep
// regenerating the id until we found an id that is not used.
_id = _generateNewId();
}
owner._nodes[id] = this;
...
}
static const int _maxFrameworkAccessibilityIdentifier = (1<<16) - 1;
static int _lastIdentifier = 0;
static int _generateNewId() {
_lastIdentifier = (_lastIdentifier + 1) % _maxFrameworkAccessibilityIdentifier;
return _lastIdentifier;
}
final Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
需要注意的是:如果这仅仅是@visibleForTesting
,那么我只会假设它只应该在flutter test
中运行,而不是与flutter run
/ flutter build
一起运行。
我们可以看到这里只有64k个可用的id。如果系统中有很多节点,那么循环很可能会在找到一个空闲id之前持续很长时间。如果我们这样做很多次,我们就会得到二次方行为 - 这是非常糟糕的。
我认为这应该使用一种可以在O(1)或O(log-n)内找到空闲id的不同数据结构。
3条答案
按热度按时间tcbh2hod1#
@visibleForTesting
的意思并不是你所想的。它意味着这通常是一个私有方法,但我们将其暴露出来,以便在某些单元测试中使用。但是这个方法对于实际运行无障碍系统至关重要。是的,整个方案似乎可疑。一旦到达
2^16
,它将进行一次爬取。需要检查的是,是否有可能重新分配分割?为什么大多数ID被保留给引擎?真的是双方都创建了相同数量的无障碍节点(我怀疑)吗?hrysbysz2#
实际上,将ID范围分成两部分的做法是错误的。应该在
2^31
上进行分割,而不是在2^16
上进行分割,以将其分成两半...将会打开一些PRs7d7tgy0s3#
cc @chunhtai