打开包装! 快点!
发布日期: 3/15/2001
| 更新日期: 6/25/2004
Eric Gunnerson Microsoft Corporation 上个月,我们介绍了装箱和取消装箱的方法,以及什么时候会用到它们。这个月,我们将研究装箱对性能的影响,以及我们应当怎样将这种影响减少到最小。 ![]() 本页内容
装箱和性能由 于进行了装箱,所以 C# 中的对象模型非常简单明了。但是采用经过装箱的数值类型会导致性能的降低。在大多数情况下,对象模型的简化比性能的降低更为重要。对于一般的软件而言的确 是这样。节省开发和维护软件的时间是最需要进行优化的地方,同时正是这些优化措施能够最大程度地改善程序的性能。 最佳的解决方案可能是使用 一个通用的 ArrayList。这样我们可以声明一个 Arraylist<int>,该对象就可以不经过装箱直接存储 int 值。不幸的是,在 C# 的第一版本中没有通用数据类型(尽管人们正研究在后续版本中提供这种功能),所以这种解决方案于事无补。 有时候可 以用别的没有装箱损耗的对象来代替。除了利用 ArrayList 来保存 int 值以外,还可以(需要使用较少的额外代码来添加新的元素)使用 int[],或者还可以编写一个 ArrayListInt 来封装这些额外代码。但是,这并不是最简便的解决方案。 还有很多其他的选择。为了更好地对它们进行说明需要举一个具体的实例。 字数统计这是我以前编写的一个 C# 程序,它可以将一个测试文件分解成一个个的单词,再统计每个单词出现的次数。我写这个程序的目的是为了了解常用表达式类在框架中的工作方式,并将用 C# 编写的程序与用 Perl 编写的类似程序进行比较。 while ((line = stream.ReadLine()) != null) 在内循环中, 我们从“散列表”中获得某个关键字的当前值。如果该值不是空值,就将它转换成一个 int。随后重新将正确的值存储到“散列表”中。 确定基准在我们开始尝试不同的方法以前,我们需要知道这个 C# 程序的基准时间是多少。换句话说,读取文件的所有行,并将它们分解成一个个的单词而不进行字数统计,这个过程需要多长时间。这样做非常重要,因为只有这样我们才能只比较源代码中的统计部分所用的时间。 如果我们编写实现上述任务的代码,我们发现在大约 7 秒钟的时间内就完成了除了字数统计以外的所有任务。这意味着字数统计部分花费了几秒钟。 让程序运行得更快我 们的目标是取消整型数据的装箱和取消装箱过程。换句话说,我们希望能够不经过对整型数据进行解装箱以及随后再对其重新装箱的过程直接累加“散列表”中的 值。要做到这一点,一种显而易见的方法是使用一种引用类型,而不是数值类型。我们可以将整型统计值装箱在类中。这个类非常简单: class IntHolderClass 当为这个类创建一个新的实例时,统计值设为 1。统计值可以通过 Count 属性递增。主循环被修改成如下形式: foreach (string word in regexSplit.Split(line.ToLower())) 如果单词不存在,就创建一个新的装箱类实例,并将其放入“散列表”中。如果单词已存在,就累加其计数。 当我们运行此版本 时,我们发现所花时间不到一秒钟。这大约是采用了装箱的程序所花时间的 30%。刚开始我对这个结果有些惊讶,因为表面上看起使用类应当造成更多的损耗。但是经过仔细思考,我们知道,很显然,尽管创建一个被装箱的 int 与创建一个装箱类所造成的损耗是相同的,但是对于被装箱的 int,我们为每个单词(总共有 600,000个)而不是为每个唯一的单词(大约有 19,000 个)创建了一个装箱。 这种技术所带来的性能改善的程度取决于我们在数值保存在集合类中时所执行的操作的次数。如果我们只是找到该对象并将其取出,那么不会带来任何性能上的改善。例如,如果我用一个 ArrayList 来保存用于后续处理的整型值,那么采用这种技术对于性能将没有任何帮助。 当您编写代码时,最好使用尽可能简便的方法。在实现这种方法以后,如果需要更快的速度,则可以再考虑其他的方法来提高运行速度。 另一种技术还 有一种类似的方法可获得同样的结果。数值类型可以作为接口,但是因为接口都是引用类型,所以您只能使用一个引用某个被装箱的数值类型的接口。我们可以定义 一个具有 Increment() 成员的接口,并使用该数值类型实现它。利用这种方法,我们可以直接通过被装箱的数值类型获得接口,然后再调用 Increment() 函数,这一切都不需要取消装箱。接口和数值类型如下: interface IIncrement 我很希望宣布这是由我自己独立完成的,但实际上它是我在最近的一次会议上遇到的一个与会者编写的。这种实现的主循环的工作方式与采用装 箱类的那段程序非常相似。这个程序运行时所花费的时间大约是采用被装箱的 int 的程序所花时间的 32%。换句话说,它只比采用装箱类的程序稍慢一点。 全部结果以下内容是对全部结果的总结。 表 1《On Basilisk Station》的结果
表 2《战争与和平》的结果
各种实现和驱动程序代码可以在示例代码中找到。为了在归档时不至于太大,我在这里省去了这些文本文件。我还引用了一个 Perl 文件作为参考。如果您要运行这个 Perl 程序,您需要从 http://www.activestate.com 站点下载 ActivePerl 软件(这是一个免费软件)。 总结我希望您喜欢这篇关于装箱的介绍。通常,尽管装箱会导致性能上的一些损耗,但是并没有太大关系,因为相比之下简化更为重要。但是有时候,可能需要使用一个装箱类来减少由于装箱所带来的性能损耗。 网站摸彩袋在这个摸彩袋中有相当多的站点,所以我使用我的秘密的随机数生成器来挑出其中的五个。它们是:
再次需要指出的是,这些都是没有保证的站点。我所能保证的是当您点击上面的 URL 地址时会显示一个网页。
|
ps:在C#2.0中已经是用范型来解决这类问题