在托管代码中,为了保持良好的性能,我应该注意哪些方面?

7
我最开始是一名本地的C++程序员,在C++中,你的程序中的每个进程都与你的代码绑定,也就是说,除非你想让它发生,否则什么都不会发生。并且,每一位内存都是根据你编写的内容进行分配(和释放)的。因此,性能完全由你负责,如果你做得好,就会获得出色的性能。
(注意:请不要抱怨像STL这样的自己没有写过的代码,毕竟它是一个未经管理的C++代码,这是重要的部分)。
但是在托管代码中,例如Java和C#中的代码,你不能控制每个进程,并且内存是“隐藏”的,或者在某种程度上不受你的控制。这使得性能成为相对未知的东西,大多数情况下你担心性能不佳。
因此,我的问题是:我应该注意哪些问题和粗体行以实现托管代码的良好性能?
我只能想到一些实践方法,例如:
- 注意装箱和拆箱。 - 选择最适合您需求并且具有最低操作成本的正确集合。
但是这些似乎永远不够,并且甚至令人信服!事实上,也许我不应该提到它们。
请注意,我不是在要求C++与C#(或Java)代码比较,我只是提到C++来解释问题。
6个回答

5
这里没有单一的答案。唯一的方法是:分析。尽早且经常地进行测量。瓶颈通常不在你预期的地方。优化那些实际上会造成问题的事情。我们使用mvc-mini-profiler进行这项工作,但任何类似的工具都可以使用。
你似乎关注垃圾回收;现在,这有时可能是一个问题,但通常只在特定情况下才会出现;对于大多数系统来说,分代GC效果很好。
显然,外部资源会很慢;缓存可能是至关重要的:在极端情况下,如果数据存活时间非常长,你可以使用结构体技巧来避免较长的GEN-2收集;序列化(文件、网络等)、材料化(ORM)或仅仅是糟糕的集合/算法选择可能是最大的问题——直到你进行测量,你才能知道。
两件事情:
1. 确保你理解IDisposable和“using”的含义 2. 不要在循环中连接字符串;大规模连接是StringBuilder的工作

4

在我的经验中,重复使用大型对象非常重要。

大对象堆上的对象是隐式的第二代对象,因此需要进行完全垃圾回收才能清理。这是很昂贵的。


1
我认为对于某些实现来说这是正确的(也许大多数实现都是如此);但这只是一个实现细节,尽管重要。 - Basile Starynkevitch
@BasileStarynkevitch 这是非常正确的。对于非分代GC来说,对象池可能会降低性能! - MattDavey

1

在使用托管语言时,需要记住的主要事项是您的代码可以在运行时更改结构以进行更好的优化。

例如,大多数人使用的默认JVM是Sun的Hotspot VM,它实际上会通过将程序的部分转换为本地代码、即时内联和其他优化(如CLR或其他托管运行时)来优化您的代码,这些优化是您使用C++永远无法获得的。此外,Hotspot还会检测您的代码中使用最多的部分并相应地进行优化。因此,可以看出,在托管系统上优化性能比在非托管系统上稍微困难一些,因为您有一个中间层可以在不需要您干预的情况下使代码更快。

我要引用过早优化定律,首先创建正确的解决方案,如果性能成为问题,请返回并测量实际上缓慢的内容,然后再尝试进行优化。


.NET总是将所有内容JIT编译为本机代码。我认为它不会进行推测性内联或重新编译。 - Ben Voigt
根据这个答案,.NET JIT编译器确实进行了许多优化,包括方法内联。我想说的是,托管语言确实提供了在运行时执行此类优化的能力,可以自适应地或在使用时进行。 - ahjmorton
当目标可以静态确定时,它会执行内联。预测内联是Hotspot执行而.NET不具备的更高级技术。 - Ben Voigt

0

我建议更好地了解垃圾回收算法。您可以找到关于这个问题的好书,例如The Garbage Collection Handbook(作者:Richard Jones、Antony Hosking、Eliot Moss)。

然后,您的问题实际上与特定的实现相关,甚至可能与特定版本相关。例如,Mono曾经使用(例如在2.4版本中)使用Boehm's垃圾收集器,但现在使用复制代际垃圾收集器。

不要忘记,一些GC技术可以非常高效。请记住A.Appel的旧论文Garbage Collection can be faster than stack allocation(但今天,缓存性能更加重要,因此细节有所不同)。

我认为了解装箱(&拆箱)和分配就足够了。一些编译器能够优化它们(通过避免其中的一些)。

不要忘记GC性能可能会有很大差异。有好的GC(适用于您的应用程序)和坏的GC。

而且,一些GC实现非常快。例如Ocaml内部的GC。

我不会那么麻烦:过早地进行优化是有害的。

(即使使用智能指针或引用计数器,C++内存管理也经常被视为一种贫民版的垃圾回收技术;而且您无法完全控制C++的操作 - 除非您使用特定于操作系统的系统调用重新实现::operator new,否则您无法预先知道其性能)


0

.NET的泛型不会在引用类型上进行特化,这严重限制了可以进行多少内联操作。在某些性能热点情况下,放弃通用容器类型而选择更好优化的具体实现可能是有意义的。(注意:这并不意味着要使用元素类型为object的.NET 1.x容器)。


-1

你必须使用大对象,这在我的经验中非常重要。

大对象堆上的对象隐式地属于第二代,因此需要进行完整的垃圾回收才能清理。而这是很昂贵的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接