.NET 对象创建,哪个更快?

发布于 2024-09-19 13:04:26 字数 204 浏览 6 评论 0原文

这两种对象创建方式有区别吗?

new MyClass() { Id = 1, Code = "Test" };

或者

MyClass c = new MyClass();
c.Id = 1;
c.Code = "Test";

什么更快?我假设2之间没有区别。

IS there a difference between those 2 ways of object creation?

new MyClass() { Id = 1, Code = "Test" };

or

MyClass c = new MyClass();
c.Id = 1;
c.Code = "Test";

What's faster? I am assuming there is no difference between the 2.

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

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

发布评论

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

评论(4

jJeQQOZ5 2024-09-26 13:04:26

第二个可能几乎肯定会快一点,因为从逻辑上讲,涉及的作业较少。在第一种情况下,代码实际上相当于:

MyClass tmp = new MyClass()
tmp.Id = 1;
tmp.Code = "Test";
MyClass c = tmp;

当您声明一个变量时,JIT编译器很可能会忽略这些 - 如果您是这样,它就无法这样做使用对象初始值设定项分配给现有变量。

编辑:我刚刚尝试过在启用和不启用优化的情况下进行编译,在这种“新变量”情况下,C# 编译器会忽略它正在优化的两个 if 。否则就不会(但 JIT 仍然可以)。在“重新分配”的情况下,它可能会产生明显的差异,所以我不期望有相同的优化。不过我还没有检查过。

我会非常惊讶地看到它实际上产生了显着差异的情况,所以我会选择更具可读性的选项,IMO 是第一个。

编辑:我认为人们可能对显示它产生影响的基准感兴趣。这是故意编写的可怕代码,目的是使隐藏的额外分配变慢 - 我创建了一个大的、可变的结构。呃。无论如何...

using System;
using System.Diagnostics;

struct BigStruct
{
    public int value;
    #pragma warning disable 0169
    decimal a1, a2, a3, a4, a5, a6, a7, a8;
    decimal b1, b2, b3, b4, b5, b6, b7, b8;
    decimal c1, c2, c3, c4, c5, c6, c7, c8;
    decimal d1, d2, d3, d4, d5, d6, d7, d8;
    #pragma warning restore 0169
}

class Test
{
    const int Iterations = 10000000;

    static void Main()
    {
        Time(NewVariableObjectInitializer);
        Time(ExistingVariableObjectInitializer);
        Time(NewVariableDirectSetting);
        Time(ExistingVariableDirectSetting);
    }

    static void Time(Func<int> action)
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
        action();
        stopwatch.Stop();
        Console.WriteLine("{0}: {1}ms",
                          action.Method.Name,
                          stopwatch.ElapsedMilliseconds);
    }

    static int NewVariableObjectInitializer()
    {
        int total = 0;
        for (int i = 0; i < Iterations; i++)
        {
            BigStruct b = new BigStruct { value = i };
            total += b.value;
        }
        return total;
    }

    static int ExistingVariableObjectInitializer()
    {
        int total = 0;
        BigStruct b;
        for (int i = 0; i < Iterations; i++)
        {
            b = new BigStruct { value = i };
            total += b.value;
        }
        return total;
    }

    static int NewVariableDirectSetting()
    {
        int total = 0;
        for (int i = 0; i < Iterations; i++)
        {
            BigStruct b = new BigStruct();
            b.value = i;
            total += b.value;
        }
        return total;
    }

    static int ExistingVariableDirectSetting()
    {
        int total = 0;
        BigStruct b;
        for (int i = 0; i < Iterations; i++)
        {
            b = new BigStruct();
            b.value = i;
            total += b.value;
        }
        return total;
    }
}

结果(使用 /o+ /debug-):

NewVariableObjectInitializer: 3328ms
ExistingVariableObjectInitializer: 3300ms
NewVariableDirectSetting: 1464ms
ExistingVariableDirectSetting: 1491ms

我有点惊讶 NewVariableObjectInitializer 版本比直接设置版本慢...看起来 C# 编译器没有以它的方式优化这种情况对于引用类型。我怀疑值类型有一些微妙之处阻止了它。

The second will possibly be almost certainly insignificantly faster, because there's one fewer assignment involved, logically. In the first case, the code is actually equivalent to:

MyClass tmp = new MyClass()
tmp.Id = 1;
tmp.Code = "Test";
MyClass c = tmp;

It's very possible that the JIT compiler will elide these as you're declaring a new variable - it wouldn't be able to do so if you were assigning to an existing variable with an object initializer.

EDIT: I've just tried compiling with and without optimizations turned on, and in this "new variable" case the C# compiler elides the two if it's optimizing. It doesn't otherwise (but the JIT still could). In the "reassignment" case it could make an observable difference, so I wouldn't expect the same optimization. I haven't checked though.

I would be very surprised to see a situation where it actually made a significant difference though, so I'd go with the more readable option, which IMO is the first.

EDIT: I thought folks might be interested in a benchmark showing it making a difference. This is deliberately horrible code to make the hidden extra assignment slow - I've created a big, mutable struct. Urgh. Anyway...

using System;
using System.Diagnostics;

struct BigStruct
{
    public int value;
    #pragma warning disable 0169
    decimal a1, a2, a3, a4, a5, a6, a7, a8;
    decimal b1, b2, b3, b4, b5, b6, b7, b8;
    decimal c1, c2, c3, c4, c5, c6, c7, c8;
    decimal d1, d2, d3, d4, d5, d6, d7, d8;
    #pragma warning restore 0169
}

class Test
{
    const int Iterations = 10000000;

    static void Main()
    {
        Time(NewVariableObjectInitializer);
        Time(ExistingVariableObjectInitializer);
        Time(NewVariableDirectSetting);
        Time(ExistingVariableDirectSetting);
    }

    static void Time(Func<int> action)
    {
        Stopwatch stopwatch = Stopwatch.StartNew();
        action();
        stopwatch.Stop();
        Console.WriteLine("{0}: {1}ms",
                          action.Method.Name,
                          stopwatch.ElapsedMilliseconds);
    }

    static int NewVariableObjectInitializer()
    {
        int total = 0;
        for (int i = 0; i < Iterations; i++)
        {
            BigStruct b = new BigStruct { value = i };
            total += b.value;
        }
        return total;
    }

    static int ExistingVariableObjectInitializer()
    {
        int total = 0;
        BigStruct b;
        for (int i = 0; i < Iterations; i++)
        {
            b = new BigStruct { value = i };
            total += b.value;
        }
        return total;
    }

    static int NewVariableDirectSetting()
    {
        int total = 0;
        for (int i = 0; i < Iterations; i++)
        {
            BigStruct b = new BigStruct();
            b.value = i;
            total += b.value;
        }
        return total;
    }

    static int ExistingVariableDirectSetting()
    {
        int total = 0;
        BigStruct b;
        for (int i = 0; i < Iterations; i++)
        {
            b = new BigStruct();
            b.value = i;
            total += b.value;
        }
        return total;
    }
}

Results (with /o+ /debug-):

NewVariableObjectInitializer: 3328ms
ExistingVariableObjectInitializer: 3300ms
NewVariableDirectSetting: 1464ms
ExistingVariableDirectSetting: 1491ms

I'm somewhat surprised that the NewVariableObjectInitializer version is slower than the direct setting ones... it looks like the C# compiler doesn't optimize this case in the way that it does for reference types. I suspect there's some subtlety around value types that prevents it.

千仐 2024-09-26 13:04:26

我通过创建 1 亿个对象进行了测试,每个对象都使用参数化构造函数、带初始化程序的无参数构造函数和带 setter 的无参数构造函数,并且根本没有可测量的差异。执行时间略有不同,但以不同的顺序运行测试会改变结果,因此差异只是由于垃圾收集器在不同的时间启动所致。

创建 1 亿个对象大约需要 1.5 秒,因此也没有太多理由尝试让它更快。

就我个人而言,我更喜欢参数化构造函数,因为这样我就可以将属性设置器设置为私有,这样我就可以使类不可变(如果我愿意):

class MyClass {

  public int Id { get; private set; }
  public string Code { get; private set; }

  public MyClass(int id, string code) {
    Id = id;
    Code = code;
  }

}

此外,这样您可以确保在创建对象时所有属性都已正确设置。

I tested by creating 100 million objects each using a parameterised constructor, a parameterless constructor with initialiser, and a parameterless constructor with setters, and there is no measurable difference at all. There was a slight difference in execution time, but running the tests in different order changed the results, so the differences are just due to the garbage collector kicking in at different times.

Creating 100 million objects took about 1.5 seconds, so there isn't much reason to try to make it faster either.

Personally I prefer a parameterised constructor, as I then can make the property setters private so that I can make the class immutable if I want to:

class MyClass {

  public int Id { get; private set; }
  public string Code { get; private set; }

  public MyClass(int id, string code) {
    Id = id;
    Code = code;
  }

}

Also, this way you can make sure that all properties have been set properly when the object is created.

昵称有卵用 2024-09-26 13:04:26

为了说明 M Skeet 的代码,这里是 IL(注意方法 #1 的附加 ldloc stloc)

  IL_0001:  newobj     instance void ConsoleApplication1.Program/MyClass::.ctor()
  IL_0006:  stloc.2
  IL_0007:  ldloc.2
  IL_0008:  ldc.i4.1
  IL_0009:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Id(int32)
  IL_000e:  nop
  IL_000f:  ldloc.2
  IL_0010:  ldstr      "Test"
  IL_0015:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Code(string)
  IL_001a:  nop
  IL_001b:  ldloc.2
  IL_001c:  stloc.0


  IL_001d:  newobj     instance void ConsoleApplication1.Program/MyClass::.ctor()
  IL_0022:  stloc.1
  IL_0023:  ldloc.1
  IL_0024:  ldc.i4.1
  IL_0025:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Id(int32)
  IL_002a:  nop
  IL_002b:  ldloc.1
  IL_002c:  ldstr      "Test"
  IL_0031:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Code(string)
  IL_0036:  nop

To illustrate M Skeet's code, here's the IL (note the additional ldloc stloc for method #1)

  IL_0001:  newobj     instance void ConsoleApplication1.Program/MyClass::.ctor()
  IL_0006:  stloc.2
  IL_0007:  ldloc.2
  IL_0008:  ldc.i4.1
  IL_0009:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Id(int32)
  IL_000e:  nop
  IL_000f:  ldloc.2
  IL_0010:  ldstr      "Test"
  IL_0015:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Code(string)
  IL_001a:  nop
  IL_001b:  ldloc.2
  IL_001c:  stloc.0


  IL_001d:  newobj     instance void ConsoleApplication1.Program/MyClass::.ctor()
  IL_0022:  stloc.1
  IL_0023:  ldloc.1
  IL_0024:  ldc.i4.1
  IL_0025:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Id(int32)
  IL_002a:  nop
  IL_002b:  ldloc.1
  IL_002c:  ldstr      "Test"
  IL_0031:  callvirt   instance void ConsoleApplication1.Program/MyClass::set_Code(string)
  IL_0036:  nop
乜一 2024-09-26 13:04:26

他们是一样的。但我们都更喜欢第一个,它更具可读性和更清晰,不是吗?

They are the same. But we all prefer the first one, it's more readable and clearer, isn't it?

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