使用不可变对象的程序的性能

发布于 2025-01-13 21:00:45 字数 171 浏览 0 评论 0原文

使用不可变对象编写程序会导致性能问题吗?如果给定的对象是不可变的,并且我们需要以某种方式更改其状态,则必须将其映射到状态稍有更改的新对象。因此,我们会发现自己处于这样一种情况:创建了很多对象,这些对象堵塞了内存,并且据我了解,这可能会给垃圾收集器带来问题。我所描述的情况是否正在发生,或者是否存在我不知道的关于该主题的某些方面?

Does writing programs with immutable objects cause performance problems? If a given object is immutable and we need to somehow change its state, we have to map it to a new object with a slightly changed state. Thus, we can find ourselves in a situation where we create a lot of objects which congest memory and, as I understand it, can be problematic for the garbage collector. Is what I have described taking place or is there some aspect that I am not aware of on this topic?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

妄断弥空 2025-01-20 21:00:45

当您重复修改可变对象时,它可能会比重复构造新的不可变对象来表示中间状态产生更少的垃圾。但是,有几个原因可以解释为什么使用不可变对象仍然不一定会带来性能问题:

  • 在典型的应用程序中,与不可变对象不会受到影响甚至最终获胜的其他用途相比,这种情况只是偶尔发生。最值得注意的是:

    • getters 在返回不可变对象时不需要创建防御性副本
    • 如果不可变,setter 可以通过引用存储传入参数
    • 验证方法可以用简单(或幼稚)的方式实现,而不必处理先检查后执行的问题,因为不可变对象在检查和后续使用之间无法更改
    • 不可变对象可以安全地共享,以实际减少创建的对象或使用的内存量
  • 垃圾收集对性能的影响常常被高估。我们通常使用不可变类型java.lang.String,受益于上述优点。字符串也是最常用的哈希映射键之一。

    对于重复字符串操作的场景,有一个可变伴生类 StringBuilder,但它的主要优点是分配的对象数量无关。字符串构造的问题在于每个对象都必须创建所包含字符的副本,因此在每一步构造新字符串时,重复连接字符等操作会导致二次时间复杂度。 StringBuilder 仍然会在必要时在后台重新分配其缓冲区,但会进行非线性增长,从而为重复串联产生摊销的线性时间复杂度。

    正如这个答案中所解释的,垃圾收集的成本主要取决于仍然存在的对象,因此临时对象通常除非您创建过多的垃圾收集,否则不会对垃圾收集产生太大影响。但即使是后一种情况,也只有在您遇到实际性能问题并且公正的分析工具证明特定分配站点确实是罪魁祸首时才应解决。

  • 复杂的应用程序可能必须处理撤消/重做或其他版本控制功能,无论如何都需要保留替代应用程序状态的副本。此时,使用不可变对象实际上可能成为一种优势,因为您不需要复制在应用程序状态的两个版本之间未更改的对象。更改后的应用程序状态可能包含 99% 与先前状态相同的对象。

  • 该主题再次流行的一个重要原因是不可变对象提供了实现高效且正确的并行处理的最简单方法。无需获取锁来防止不一致的修改。如上所述,无需担心先检查后行动的问题。此外,当应用程序状态可以表示为对复合不可变对象的单个引用时,对并发更新的检查减少为简单的引用比较。另请与此答案进行比较。

因此,不可变对象有很多性能优势,可以弥补缺点(如果有的话)。同时,可以通过临时处理操作的可变对象同时保持整体不可变行为的既定解决方案来修复潜在的瓶颈。

When you repeatedly modify a mutable object, it will likely produce less garbage than repeatedly constructing new immutable objects to represent the intermediate state. But there are several reasons why using immutable objects still is not necessarily imposing a performance problem:

  • In typical applications, this scenario occurs only occasionally, compared to other uses where immutable objects do not suffer or even turn out to win. Most notably:

    • getters do not need to create a defensive copy when returning an immutable object
    • setters can store the incoming argument by reference if immutable
    • verification methods can be implemented the simple (or naïve) way, without having to deal with the check-then-act problem, as immutable objects can not change between the check and the subsequent use
    • immutable objects can be safely shared, to actually reduce the amount of created objects or used memory
  • The impact of garbage collection to the performance is often overestimated. We routinely use the immutable type java.lang.String, benefitting from the advantages mentioned above. Strings are also one of the most often used hash map keys.

    There’s the mutable companion class StringBuilder for the scenario of repeated string manipulations, but its main advantage is not about the number of allocated objects. The problem with string construction is that each object has to create a copy of the contained characters, so operations like repeatedly concatenating characters lead to a quadratic time complexity when constructing a new string on each step. The StringBuilder still reallocates its buffer under the hood when necessary but with a nonlinear growth, which yields an amortized linear time complexity for repeated concatenation.

    As explained in this answer, the costs of garbage collection mainly depend on the still existing objects, so temporary objects usually do not impact the garbage collection much unless you create an excessive amount of them. But even the latter scenario should only be addressed if you have an actual performance problem and an impartial profiling tool proves that a particular allocation site truly is the culprit.

  • Sophisticated applications may have to deal with undo/redo or other versioning features, which require keeping copies of the alternative application state anyway. At this point, using immutable objects may actually become an advantage, as you do not need to copy objects which did not change between two versions of the application state. A changed application state may consist 99% of the same objects as the previous state.

  • One of the big reasons why the topic has become popular (again) is that immutable objects provide the easiest way to implement efficient and correct parallel processing. There is no need to acquire a lock to prevent inconsistent modifications. As said above, there’s no need to worry about the check-then-act problem. Further, when the application state can be expressed as a single reference to a compound immutable object, the check for a concurrent update reduces to a simple reference comparison. Compare also with this answer.

So there are a lot of performance advantages with immutable objects which may compensate the disadvantages, if there are any. At the same time, potential bottlenecks can be fixed with the established solution of temporarily dealing with mutable objects for an operation while keeping the overall immutable behavior.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文