【问题标题】:Performance Issue using Vector3.Distance使用 Vector3.Distance 的性能问题
【发布时间】:2015-03-01 17:56:04
【问题描述】:

我正在开发一个系统,该系统将命令从 HashSet 分发到播放器。我想将一个命令分发给离命令最近的玩家。

void AssignCommand(Player player, HashSet<Command> commandList) {
    //Player assigned;
    float min = float.MaxValue;
    float dist;

    foreach(Command command in commandList) {
        dist = Vector3.Distance(command.Position, player.Position);
        if(dist < min) {
            //check if command already assigned to another player
            assigned = command.assigned;
            if(assigned != null) {
                //reassign when distance is smaller
                if(dist < command.Distance(assigned)) {
                    //mark previously  assigned command as unassigned
                    if(player.activeCommand != null) player.activeCommand.assigned = null;
                    player.activeCommand = command;
                    command.assigned = player;
                    min = dist;

                    assigned.activeCommand = null;
                    AssignCommand(assigned, commandList);
                }
            }
            else {
                if(player.activeCommand != null) player.activeCommand.assigned = null;
                player.activeCommand = command;
                command.assigned = player;
                min = dist;
            }
        }
    }
}

我对这段代码的问题是,如果 HashSet 中有很多命令,则需要很长时间,并且我的机器上的帧速率从 ~60 fps 下降到约 ~30 fps。这并不奇怪,因为 Vector3.Distance 方法只是针对 (every player) * (every command) 调用的,这太过分了。我现在正在寻找一种以某种方式减少调用次数以提高性能的方法。这里有什么想法吗?

我也尝试在不同的线程中运行这段代码,但我放弃了,因为这会改变并使用太多的线程不安全值。我最近的尝试让我检查assigned != null 是否抛出错误进行比较。

我将非常感谢任何提高此代码的整体速度或如何在 ThreadPool 中运行它的提示。如果需要,我还可以发布我为 Thread 尝试创建的 JobClass。

【问题讨论】:

  • 所以你有 x 个玩家,每个玩家有 y 个命令,对吧?并且您想按...与某个点(例如相机)的距离来订购?
  • 哦,看来我在这里不够清楚.. 我有 x 个玩家和 y 个命令。现在我只想为每个玩家分配一个命令。上面的代码只是将命令分配给一个玩家的部分。这在每个玩家的 foreach 中调用,并且一些不重要的条件检查是否还有命令和玩家要分配。播放器和命令都相互链接)。但是分配给命令的玩家应该是离它最近的玩家
  • 命令从何而来?您不能在创建命令后立即分发命令以保存在它们的大列表中吗?
  • 它们是从输入控制器创建的,当用户执行某些操作(鼠标单击)并被放入命令控制器的字典中时。该字典用于将命令分配给不同的优先级并作为队列工作。创建命令时无法分发命令,因为您一次最多可以创建 1000 个命令,并且只有 9 名玩家......该游戏是一个体素游戏,具有很多管理和战略影响.. 所以一个命令例如.可以挖某个区块
  • hmmm ok ...这个游戏听起来很酷:) ...所以你有一个带有位置的命令列表和一个带有位置的玩家列表,你需要以某种方式匹配它们.. . 有趣的问题... 他们是否必须在这一帧发生,或者您可以将工作分配给线程吗?

标签: c# performance unity3d


【解决方案1】:

所有线程解决方案和优化都很好,但您要记住的最重要的事情(对于这个和未来)是:永远不要为此使用Vector3.DistanceVector3.magnitude 他们效率低下。

改为使用 Vector3.sqrMagnitude,它是相同的东西(用于距离比较),没有 sqrt(最昂贵的部分)。

另一个优化是编写您自己的(平方)距离计算,如果您知道您不关心垂直距离,则丢弃 y 值。我的距离比较代码很慢,所以我非常仔细地测试了这个,发现这是最快的方法(特别是如果你不关心垂直位置):(编辑:这是 2015 年最快的。测试自己在现代上最快的代码团结。)

            tempPosition = enemy.transform.position; // declared outside the loop, but AFAIK that shouldn't make any difference
            float xD = targetPosition.x - tempPosition.x;
            float yD = targetPosition.y - tempPosition.y; // optional
            float zD = targetPosition.z - tempPosition.z;
            float dist2 = xD*xD + yD*yD + zD*zD; // or xD*xD + zD*zD

编辑:另一个优化(您可能已经在做)是仅在玩家移动时重新计算。我喜欢这个,因为它根本不会破坏数据。

【讨论】:

  • 我认为您的优化代码与 Vector2.SqrMagnitude() 完全相同,所以我更愿意在您的情况下使用那个,因为它已经预编译,因此应该会快一点。我也需要 y 值,但我明白你的观点,在我的情况下,平方幅度肯定更快,因为我不需要确切的距离,而只比较距离,这与幅度相同。谢谢答案 - 即使我没有看到使用 Threads 的巨大差异,我也相信存在差异!也许我恢复 repo 只是为了检查它未线程。
  • 我鼓励您自己进行测试,但我发布了该代码,因为在我的游戏中它明显快于VectorX.sqrMagnitude. 为什么?数学要么比您想象的要快,要么 Mono 的优化比您预期的要好。此外,Unity 优化文档指出,大多数 Unity 函数都是 C/C++,跨语言函数调用有性能成本(因此对于小逻辑,预编译函数实际上更慢)。请记住,在一个紧密的循环中,一切都很重要。
  • 哦,很高兴知道!我肯定会做那个测试..但是在我上次更新之后,我刚刚意识到我经常重新分配导致难以置信的高递归级别,我认为所有这些都只是微小的性能提升.. 然而当你总结它时为大量命令做好准备,一切都很重要!所以我会做测试,看看在我的情况下什么是最快的。是的,只有在队列中有命令且没有命令的玩家时,我才已经在运行该代码。
  • 那么(很明显)我希望你忽略已经有命令的玩家。除此之外,此时它可能开始变得更难优化。想要另一个可能的黑客?用几个同心对撞机(触发)球包围中心点,跟踪哪个玩家在哪个球内,只对最小占用球内的玩家进行距离检查。这样你就可以用你的数学换取物理系统了。我不知道它是否会有所帮助,但尝试一下会很有趣,不是吗?
  • 是的,我创建了一个包含所有玩家的 HashSet,然后删除那些确实分配了命令的玩家。然后我使用这个 HashSet 运行代码。除非集合中没有更多的玩家或队列中没有更多的命令,否则将为该 HashSet 的每个玩家调用 AssignCommand。在为集合中的每个玩家分配命令后,我从集合中删除所有分配的玩家并从队列中删除所有分配的命令(实际上不是队列,而是将优先级链接到包含命令的 HashSet 的字典)
【解决方案2】:

我为统一编写了自己的 System.Threading.Tasks 版本,并添加了类似这样的内容,用于根据与相机的距离对工作负载进行排序。

基本上每当需要一个任务(或者在你的情况下是命令)时,它都会将一个位置和任务传递给一个 TaskManager,然后每个框架都会对它拥有的项目进行排序并运行它们。

您可能可以执行相同/类似的操作,但不要像我使用 TaskManager 那样将命令传递给某种 CommmandManager,而是在创建时查找并将命令传递给最接近相关点的玩家。

如今,大多数人都将他们的场景图放入四叉树之类的东西中,这应该可以相当快地找到合适的玩家,然后每个玩家将负责执行自己的命令。

【讨论】:

    【解决方案3】:

    好吧,经过数小时的努力,我终于找到了一种解决方案,一方面可以提高性能,另一方面可以使其成为线程化的。我在线程方面遇到了问题,因为 player 是一个 Unity 对象。我没有在任务中使用 player 对象,而是只在 Start() 方法中获得它的位置。因此,我设法使它成为线程化的,虽然我觉得很奇怪,但我现在使用 Vector3 通过它的位置来代表玩家。

    通过添加另一个存储每个玩家已经计算的距离的字典确实可以提高性能。因此,在重新分配玩家时,不需要再次重新计算距离......我不确定这带来了多少性能,因为我与线程一起测试了它,但至少我摆脱了一些距离的调用并且我回到稳定的 60 fps!

    此外,我把递归搞砸了,所以我为每个玩家获得了多达 100.000 次递归。这不应该发生在 o.O.修复很容易。只需添加一个 minCommand 命令,我在 foreach 期间设置它,并且只分配命令并在 foreach 之后触摸 Sets。我敢打赌,即使没有线程,代码现在也会像糖一样运行...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-02-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-02
      • 2017-12-10
      • 2014-06-24
      相关资源
      最近更新 更多