简体   繁体   English

如果我有 ac# readonly 结构,它有非只读结构作为成员,编译器会用 in 参数创建防御性副本

[英]If I have a c# readonly struct that has non readonly structs as members, will compiler create defensive copy with in parameter

so, if I have for example a struct PlayerData that has members of Vector3 structs defined in System.Numerics (not readonly structs)因此,如果我有一个结构体 PlayerData,它具有在 System.Numerics 中定义的 Vector3 结构体的成员(不是只读结构体)

public readonly struct PlayerData
{
   public readonly Vector3 SpawnPoint;
   public readonly Vector3 CurrentPosition;
   public readonly Vector3 Scale;    
  
   ...constructor that initializes fields
}

and I pass them to the method:我将它们传递给方法:

AddPlayerData(new PlayerData(Vector3.Zero, Vector3.UnitX, Vector3.One))

public void AddPlayerData(in PlayerData playerData)
{
  ...
}

Will c# create defensive copy because of Vector3 members that are not readonly structs but are readonly fields? c# 是否会因为 Vector3 成员不是只读结构而是只读字段而创建防御性副本? Existing libraries don't offer readonly versions of Vectors so am I forced to forget the whole in parameter optimization when passing structs that are bigger than intptr if I don't write my own versions for basic vectors?现有的库不提供向量的只读版本,所以如果我不为基本向量编写自己的版本,在传递大于 intptr 的结构时,我是否被迫忘记整个参数优化? Information about usage is not that clear when reading: https://docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code阅读时关于使用的信息不是很清楚: https : //docs.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code

Interesting question.有趣的问题。 Lets test it:让我们测试一下:

    public void AddPlayerData(in PlayerData pd)
    {
        pd.SpawnPoint.X = 42;
    }

Gives compiler error:给出编译器错误:

Members of readonly field 'PlayerData.SpawnPoint' cannot be modified (except in a constructor or a variable只读字段 'PlayerData.SpawnPoint' 的成员不能修改(构造函数或变量中除外)

From this I would assume the compiler would not create any defensive copy, since the non-readonly struct cannot be modified anyway.由此我假设编译器不会创建任何防御性副本,因为无论如何都不能修改非只读结构。 There might be some edge case that allow the struct to change, I'm not well versed enough in the language specification to determine that.可能存在一些允许结构更改的边缘情况,我对语言规范不够精通,无法确定这一点。

It is however difficult to discuss compiler optimizations since the compiler is usually free to do whatever it wants as long as the result is the same, so the behavior might very well change between compiler versions.然而,讨论编译器优化是很困难的,因为只要结果相同,编译器通常可以自由地做任何它想做的事情,因此编译器版本之间的行为很可能会发生变化。 As usual the recommendation is to do a benchmark to compare your alternatives.像往常一样,建议是做一个基准来比较你的替代品。

So lets do that:所以让我们这样做:

public readonly struct PlayerData1
{
    public readonly Vector3 A;
    public readonly Vector3 B;
    public readonly Vector3 C;
    public readonly Vector3 D;
    public readonly Vector3 E;
    public readonly Vector3 F;
    public readonly Vector3 G;
    public readonly Vector3 H;
}
public readonly struct PlayerData2
{
    public readonly ReadonlyVector3 A;
    public readonly ReadonlyVector3 B;
    public readonly ReadonlyVector3 C;
    public readonly ReadonlyVector3 D;
    public readonly ReadonlyVector3 E;
    public readonly ReadonlyVector3 F;
    public readonly ReadonlyVector3 G;
    public readonly ReadonlyVector3 H;
}

public readonly struct ReadonlyVector3
{
    public readonly float X;
    public readonly float Y;
    public readonly float Z;
}

    public static float Sum1(in PlayerData1 pd) => pd.A.X + pd.D.Y + pd.H.Z;
    public static float Sum2(in PlayerData2 pd) => pd.A.X + pd.D.Y + pd.H.Z;

    [Test]
    public void TestInParameterPerformance()
    {
        var pd1 = new PlayerData1();
        var pd2 = new PlayerData2();

        // Do warmup 
        Sum1(pd1);
        Sum2(pd2);
        float sum1 = 0;
        
        var sw1 = Stopwatch.StartNew();
        for (int i = 0; i < 1000000000; i++)
        {
            sum1 += Sum1(pd1);
        }


        float sum2 = 0;
        sw1.Stop();
        var sw2 = Stopwatch.StartNew();
        for (int i = 0; i < 1000000000; i++)
        {
            sum2 += Sum2(pd2);
        }
        sw2.Stop();

        Console.WriteLine("Sum1: " + sw1.ElapsedMilliseconds);
        Console.WriteLine("Sum2: " + sw2.ElapsedMilliseconds);
    }

For me using .Net framework 4.8 this gives对我来说,使用 .Net framework 4.8 这给

Sum1: 1035
Sum2: 1027

Ie well within measurement errors.即在测量误差范围内。 So I would not worry about it.所以我不会担心。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2025 STACKOOM.COM