C# .NET中按值传递与按引用传递的性能比较

24

我创建了一个轻量级类,其构造函数需要约10个参数。该类不会更改参数值,它只是通过构造函数将这些值本地存储。

有些参数是引用类型(如字符串、类),而其他参数是值类型(如int、bool、枚举)。

我的问题是,除了类之外,是否应该通过引用传递所有参数,即使用关键字ref

我的考虑是性能方面的。


我不认为它有什么帮助。即使 ref 可以节省复制结构体的时间,你也不能将 ref 作为类成员,ref 只在方法内有用。 - Kobi
可能是C# 'ref'关键字的性能问题的重复。 - nawfal
7个回答

31

只有当方法需要更改参数并且这些更改需要传递给调用的代码时,才使用ref。如果您已经通过性能分析器运行过它,并确定瓶颈确实是CLR将方法参数复制到堆栈上,那么您才应该优化此操作。

请记住,CLR已经对带参数的方法进行了大量优化,因此我认为这不会是问题所在。


10

我发现对于较大的值类型,通过引用传递参数在高频函数调用中速度更快,略微地。如果你需要高效的函数调用并且有许多这样的调用,那么这可能是一个考虑因素。我也愿意接受其他证据。

public static void PassValue(decimal value)
{

}

public static void PassRef(ref decimal value)
{

}            

decimal passMe = 0.00010209230982047828903749827394729385792342352345m;

for (int x = 0; x < 20; x++)
{
    DateTime start = DateTime.UtcNow;
    TimeSpan taken = new TimeSpan();

    for (int i = 0; i < 50000000; i++)
    {
        PassValue(passMe);
    }

    taken = (DateTime.UtcNow - start);
    Console.WriteLine("Value : " + taken.TotalMilliseconds);

    start = DateTime.UtcNow;

    for (int i = 0; i < 50000000; i++)
    {
        PassRef(ref passMe);
    }

    taken = (DateTime.UtcNow - start);
    Console.WriteLine("Ref : " + taken.TotalMilliseconds);
}

结果:

Value : 150
Ref : 140
Value : 150
Ref : 143
Value : 151
Ref : 143
Value : 152
Ref : 144
Value : 152
Ref : 143
Value : 154
Ref : 144
Value : 152
Ref : 143
Value : 154
Ref : 143
Value : 157
Ref : 143
Value : 153
Ref : 144
Value : 154
Ref : 147
Value : 153
Ref : 144
Value : 153
Ref : 144
Value : 153
Ref : 146
Value : 152
Ref : 144
Value : 153
Ref : 143
Value : 153
Ref : 143
Value : 153
Ref : 144
Value : 153
Ref : 144
Value : 152
Ref : 143

10

不需要。对于引用类型,你已经传递了一个引用,除非你想改变引用指向的内容(例如分配新对象),否则没有必要通过引用来传递引用。 对于值类型,你可以通过引用传递,但是除非存在性能问题,否则我不建议这样做。特别是如果涉及到的类型很小(4个字节或更小),那么几乎没有性能增益,甚至可能会有性能损失。


1
据我所知,通过引用传递值类型时总会存在性能损失,因为值类型必须进行装箱。就我所了解的情况而言(来自同样出色的@JonSkeet所著的《C#深度剖析》),将值类型作为引用参数传递永远不会导致性能提高,相反它总是会导致性能下降。 - Ben H
1
@BenH: “当通过引用传递值类型时,总会存在性能损失,因为值类型必须被装箱”--不对。你似乎把按引用传递和传递一个引用混淆了。它们并不相同。在值类型上使用ref不会装箱该值。它传递了一个引用(指针)到持有该值的变量。 - Peter Duniho
@PeterDuniho:在.NET的内部,通过引用传递参数会传递一个“byref”。我很少看到这个术语在讨论中使用,但我认为如果更经常使用它,就会减少混淆,因为它听起来与“对象引用”相比,更不像“按引用传递”的短语。 - supercat

3
如果这个类只是保存参数,也许你应该使用结构体?也许这会引起你的兴趣?何时使用结构体?

2
据我所知,您有一个只包含字段和构造函数的类,并且构造函数将参数分配给这些字段,是吗?
如果是这种情况,我认为在构造函数中使用ref是不好的实践。如果您将参数分配给该类中的字段,则无论如何都会按值存储它。因此,如果您在构造函数中不更改该值,则没有必要通过引用来使用它。

-1
不。通过引用传递参数会增加开销,因此只会降低性能。

3
我习惯使用C++,所以我的理解可能完全错误,但您能否详细说明一下?比如说,如果我有一个非常大的对象,那么仅通过引用传递是否只会传递数据的地址而不必复制整个对象呢? - Sven van den Boogaart
@SvenvandenBoogaart,你并没有复制它。你是在方法内部创建了一个新的指针来保存对象值。但是改变指针的值不会修改用于传递该值到方法调用的外部指针。 - Candid Moon _Max_
2
结构体是按值传递的,因此使用 ref 可以提高性能。 - Orestis P.
只有当结构的大小明显大于引用的大小时才可以这样做,但是如果结构体的大小如此之大,那么它最好不要在第一时间实现为结构体。 - Guffa
@Guffa:结构体只需要超过32/64位(取决于架构)即可提高性能,这在结构体中非常常见。在问题的背景下,假设所有参数可以逻辑上分组,将它们包装在一个结构体中并作为引用传递将增加性能(或者使用C# 7中的'in')。另一个经常出现的例子是Vector3结构体(主要用于处理模拟),它由3个浮点数(12字节)组成。 - Orestis P.

-1

对于C#,我不确定,但对于C++/C,它取决于你传递的内容。如果你传递的是基本类型(int、float、double、char)……那么按值传递比按引用传递更快(因为函数调用针对此进行了优化)。 如果你传递的是更大的东西,比如一个大类、一个数组、一个长字符串……那么按引用传递要快得多,因为如果你正在处理一个int[100000],那么处理器将不得不分配一个100000 x 32/64(取决于架构)的块,然后复制所有的值,这需要很长时间。而通过引用只需传递一个指针。

C#抽象出了大部分内容,所以我不知道它做了什么,但我认为在效率方面适用于C++/C的内容通常也适用于C#。


这不是正确的。引用类型传递它们的引用。因此,一个大的类或数组仍然需要32/64位来将其传递到另一个堆栈。 - Orestis P.

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