【问题标题】:Is it inefficient to highly frequently create short-lived new instances of a class?频繁地创建一个类的短期新实例是否效率低下?
【发布时间】:2026-01-10 17:25:01
【问题描述】:

我有一个 C# 程序来跟踪玩家在游戏中的位置。在这个程序中,我有一个名为 Waypoint (X, Y, Z) 的类,它表示游戏地图上的一个位置。在我生成的其中一个线程中,我不断检查玩家与某个目标航点的距离,在 while(true) 循环中彼此快速地相互连接。在 Waypoint 类中有一个名为 public double Distance(Waypoint wp) 的方法,它计算从当前航点到作为参数传递的航点的距离。

问题:每次我想检查玩家到目标航点的距离时,是否可以为玩家的位置创建一个新的航点?然后程序可能会在 while(true) 循环中一遍又一遍地创建这个玩家 Waypoint,只是为了计算距离。

PS:我的程序可能需要巧妙地使用资源,因为它运行多个线程并使用连续的 while 循环执行各种工作,例如将玩家的 X、Y、Z 位置发布到 UI。

非常感谢!

【问题讨论】:

  • 通过衡量你的表现来回答表现问题。设置一个预算:比如说,一个最大工作集,或者在集合中花费的最长时间。然后测量一下是否超出预算。如果你不这样做,那么你的预算不足,所以要担心其他事情。如果你这样做了,那么改变你的分配策略并衡量影响

标签: c# performance loops resources


【解决方案1】:

生命周期非常短的对象的实际创建是微不足道的。创建一个新对象几乎只涉及将堆指针增加对象的大小并将这些位清零。这不会是个问题。

至于这些对象的实际收集,当垃圾收集器执行收集时,它会获取所有仍然活着的对象并复制它们。此处的 GC 不会触及任何不“活动”的对象,因此它们不会向集合添加工作。如果您创建的对象在 GC 收集期间从未或很少是活动的,那么它们不会在那里增加任何成本。

他们可以做的一件事是减少当前可用内存的数量,这样 GC 需要比其他方式更频繁地执行收集。当 GC 需要更多内存时,它实际上会执行一次收集。如果您不断使用所有可用内存来创建这些短暂的对象,那么您可以提高程序中的收集率。

当然,需要很多个对象才能真正对收集的频率产生有意义的影响。如果您担心,您应该花一些时间测量您的程序在使用和不使用此代码块的情况下执行集合的频率,以查看它产生的影响。如果您确实导致收集发生的频率比其他情况高得多,并且您因此注意到性能问题,那么请考虑尝试解决该问题。

如果您发现收藏数量显着增加,您可以想到两种方法来解决此问题。您可以调查使用值类型而不是引用类型的可能性。这在概念上对您来说可能有意义,也可能没有意义,它可能会或可能不会真正帮助解决问题。它在很大程度上取决于未提及的细节,但至少需要研究一下。另一种可能性是尝试积极缓存对象,以便它们可以随着时间的推移重新使用。这也需要仔细研究,因为它会大大增加程序的复杂性,并使编写正确、可维护和易于推理的程序变得更加困难,但它可以如果使用得当,是重新引入内存的有效工具。

【讨论】:

  • 谢谢,这很有帮助。我意识到很多人说要把变量初始化放在尽可能接近使用点的地方。由于“int a = 100;”与“int a = new int(); a = 100”相同,我经常在循环内执行此操作并且从未遇到任何问题,我将尝试在此处执行相同操作(换句话说,将新指令保留在循环内) .
【解决方案2】:

其他答案的意思是:
- 也许您应该创建堆栈本地实例,因为它不应该花费太多,并且
- 也许您不应该这样做,因为内存分配成本很高。
这些都是猜测——有根据的猜测——但仍然是猜测。

你是唯一可以回答这个问题的人,通过实际找出(而不是猜测)这些新闻是否占用了足够多的挂钟时间而值得担心。

我(和许多其他人)用来回答这类问题的方法是random pausing

这个想法很简单。 假设这些消息,如果以某种方式消除,将节省 - 选择一个百分比,比如 20% - 时间。 这意味着如果你只是简单地点击暂停按钮并显示调用堆栈,你至少有 20% 的机会在行动中抓住它。 所以如果你这样做 20 次,你会看到它大约做了 4 次,给予或接受。

如果你这样做,你会看到时间的原因。
- 如果是新闻,你会看到的。
- 如果是别的东西,你会看到的。
您不会确切知道它要花多少钱,但您不需要知道。
你需要知道的是问题是什么,这就是它告诉你的。


添加:如果您愿意接受我解释这种性能调整如何进行,这里有一个假设情况的说明:

当你取堆栈样本时,你可能会发现许多可以改进的地方,其中之一可能是内存分配,它甚至可能不是很大,因为在这种情况下它是 (C) 只占 14% . 它告诉您其他事情需要更多时间,即 (A)。

因此,如果您修复 (A),您将获得 1.67 倍的加速因子。还不错。

现在如果你重复这个过程,它会告诉你 (B) 会为你节省很多时间。 因此,您修复它并(在此示例中)再获得 1.67 倍,整体加速为 2.78 倍。

现在你再做一次,你会发现你最初怀疑的内存分配确实占了很大一部分时间。 因此,您修复它并(在此示例中)获得另一个 1.67 倍,整体加速为 4.63 倍。 现在这是一个严重的加速。

所以重点是 1) 对要加速的内容保持开放的态度 - 让诊断告诉您要修复的内容,以及 2) 重复该过程,以进行多次加速。这就是你获得真正加速的方式,因为当你去掉其他东西时,一开始很小的事情变得更加重要。

【讨论】:

  • 我的回答是准确地解释为什么这是错误的,而且是在一个非直觉的庄园。创建对象的实际操作速度非常快,而且对于程序来说可能根本无法测量。您建议的基准测试方法将无效。然而,在完美风暴的情况下,它可能会间接地引起问题(特别是通过影响垃圾收集器)。我的回答是解释 that 是需要进行基准测试以评估是否需要进行更改。即使存在问题,您的解决方案也不会表明问题。
  • 您的编辑将继续添加更多信息,这些信息通常是良好且有用的性能基准测试建议,但恰好根本不适用于此特定问题 由于其不寻常的性质。
  • @Servy:你在这个网站上的时间已经够长了,我相信你已经看过thisthisthis,以及理论基础here。无论如何,创建对象的速度可能快得惊人,但无论多快,它都会占用总时间的一小部分,如果这部分时间很重要,那么不这样做会节省那部分时间。
  • 在你到达那个点之前,应用程序会出现严重的问题longlong。您在这里谈论的这些操作只是少数几个处理器周期。即使是低端、过时的机器也可以每秒执行相当多的十亿。然后将其与垃圾收集进行比较,如果程序设计不佳,可能会比每个分配的项目仅花费几个处理器周期长很多是瓶颈会出现的地方,所以他应该找麻烦。
  • 您正在积极引导用户远离向他展示他是否有问题的基准,而转向一个不会显示他是否有问题的基准一个问题,直到他几个数量级超出了他应该达到的水平。
最近更新 更多