【问题标题】:Get first (or any) value from HashSet从 HashSet 获取第一个(或任何)值
【发布时间】:2021-12-20 10:42:20
【问题描述】:

目前,我的代码如下所示:

    private List<Node> dirtyNodes = new List<Node> dirtyNodes();

    public void UpdateDirtyNodes()
    {
        while(dirtyNodes.Count > 0)
        {
            Node nodeToUpdate = dirtyNodes[0];
            nodeToUpdate.UpdateNode();
            dirtyNodes.Remove(nodeToUpdate);
        }
    }

    public void MarkNodeDirty(Node node)
    {
        if(!dirtyNodes.Contains(node))
        {
            dirtyNodes.Add(node);
        }
    }

    public void MarkNodeClean(Node node)
    {
        dirtyNodes.Remove(node);
    }

这是代码中对性能至关重要的部分,它比我想要的要慢,因为dirtyNodes.Contains 在大多数情况下必须遍历整个数组。我想用HashSet 替换List,因为它应该更快,但我不知道如何使用UpdateDirtyNodes()

困难在于UpdateNode() 可以随时从dirtyNodes 添加或删除节点,因此有点尴尬的while 循环。有没有办法从 HashSet 中获取“第一个”值?顺序无关紧要,我只需要停留在 while 循环中,直到 dirtyNodes 为空,然后更新接下来出现的任何节点。

我宁愿避免使用 Linq,因为此代码将成为库的一部分,我不想强​​迫它们包含 Linq。

我该怎么做?

【问题讨论】:

  • @Progman 不,我使用的是 C#。
  • dirtyNodes.First() 没有做你想做的事吗?
  • @41686d6564 我已经更新了这个问题。我不想使用 Linq,因为它将成为库的一部分,我不想强​​迫人们包含 Linq。或者也许我是偏执狂,Linq 很好,idk。
  • System.Linq 是 Microsoft 提供的框架部分。任何使用好的 C# 代码的地方都可以使用它
  • HashSet&lt;T&gt; 实现IEnumerable&lt;T&gt;,所以你可以迭代它:foreach (var node in dirtyNodes) { node.UpdateNode(); }

标签: c# hashset


【解决方案1】:

结果证明直接使用枚举器很容易:

    public void UpdateDirtyNodes()
    {
        while(dirtyNodes.Count > 0)
        {
            using(HashSet<Node>.Enumerator enumerator = dirtyNodes.GetEnumerator())
            {
                if(enumerator.MoveNext())
                {
                    Node nodeToUpdate = enumerator.Current;
                    nodeToUpdate.UpdateNode();
                    dirtyNodes.Remove(nodeToUpdate);
                }
            }
        }
    }

    public void MarkNodeDirty(Node node)
    {
        dirtyNodes.Add(node);
    }

我最初尝试过类似的方法,但不完全了解如何手动使用枚举器,但没有奏效。

它比List 快​​得多(总帧时间快约25-50%,具体取决于仅从那一次更改的情况)所以我很高兴。 (不要担心下面屏幕截图中的 30MB 分配 - 我正在努力。)

【讨论】:

    【解决方案2】:

    Node 类中添加一个bool dirty 字段。这是除了保留哈希集之外的。那么MarkNodeClean() 不需要从 HashSet 中删除节点,从而减少了一些 CPU 周期。

    如果你觉得在Node 类中添加一个字段太“脏”(双关语),那么只需创建一个HashSet&lt;(Node, bool)&gt; 而不是HashSet&lt;Node&gt;,但是你在分配和垃圾收集方面失去了额外的性能对象,这并不理想,因为您的代码对性能至关重要。

    UpdateDirtyNodes() 将一次获取一个节点,直到 HashSet 为空。取完每个节点后,它会查看布尔标志来判断该节点是否真的脏。

    附言 您应该从UpdateDirtyNodes() 中删除dirtyNodes.Clear();。这是一个竞争条件。如果在 while 循环发现dirtyNodes.Count 为0 之后添加了一个节点,则dirtyNodes.Clear(); 清除该节点而不进行处理。这是一个单独的错误,与您的问题无关。

    【讨论】:

    • 我对这个答案感到非常困惑。您是否建议通过向每个节点添加 dirty 标志,UpdateDirtyNodes 然后可以遍历每个节点?不幸的是,这并不容易,因为节点是八叉树的一部分,而且我没有一个数组来引用每个节点。我还相信,当我可能拥有数十万个节点但只需要更新几百或一千个节点时,这会导致性能下降。或者您是否建议当我将节点标记为干净时,我可以将 dirty 标志设置为 false?我想这可能行得通。
    • WRT 到 bug,我的代码都没有线程化,所以 dirtyNodes 在 while 循环检查计数为 0 的时间和数组被清除的时间之间不能更改。
    • 我应该提一下,您的回答帮助我意识到dirtyNodes.Clear() 毫无意义,什么也不做,因为只有在列表已被清除的情况下,while 循环才能退出。
    • 刚刚看到你的cmets。很高兴我能帮助您提出自己的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-09
    • 1970-01-01
    • 1970-01-01
    • 2021-10-05
    • 1970-01-01
    • 2017-02-08
    • 2016-02-01
    相关资源
    最近更新 更多