so, if I have for example a struct PlayerData that has members of Vector3 structs defined in System.Numerics (not readonly structs)
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? 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? Information about usage is not that clear when reading: 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
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
Sum1: 1035
Sum2: 1027
Ie well within measurement errors. So I would not worry about it.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.