#c# #optimization #struct #c#-7.2 #defensive-copy
#c# #оптимизация #struct #c # -7.2 #defensive-copy
Вопрос:
итак, если у меня есть, например, структура PlayerData, в которой элементы структур Vector3 определены в System.Числовые значения (не только для чтения)
public readonly struct PlayerData
{
public readonly Vector3 SpawnPoint;
public readonly Vector3 CurrentPosition;
public readonly Vector3 Scale;
...constructor that initializes fields
}
и я передаю их методу:
AddPlayerData(new PlayerData(Vector3.Zero, Vector3.UnitX, Vector3.One))
public void AddPlayerData(in PlayerData playerData)
{
...
}
Будет ли c # создавать защитную копию из-за элементов Vector3, которые не являются структурами только для чтения, а являются полями только для чтения? Существующие библиотеки не предлагают версии векторов только для чтения, поэтому я вынужден забывать все в оптимизации параметров при передаче структур, которые больше intptr, если я не пишу свои собственные версии для базовых векторов? Информация об использовании не так понятна при чтении: https://learn.microsoft.com/en-us/dotnet/csharp/write-safe-efficient-code
Комментарии:
1.«Чтобы убедиться, что значение параметра остается неизменным, компилятор создает защитную копию параметра каждый раз, когда используется метод / свойство. Если структура доступна только для чтения, компилятор удаляет защитную копию так же, как и для полей, доступных только для чтения. « Ссылка
Ответ №1:
Интересный вопрос. Давайте протестируем ее:
public void AddPlayerData(in PlayerData pd)
{
pd.SpawnPoint.X = 42;
}
Выдает ошибку компилятора:
Члены поля только для чтения ‘PlayerData.SpawnPoint’ не могут быть изменены (кроме как в конструкторе или переменной
Исходя из этого, я бы предположил, что компилятор не будет создавать защитную копию, поскольку структура, доступная только для чтения, все равно не может быть изменена. Может быть какой-то крайний случай, который позволяет изменять структуру, я недостаточно хорошо разбираюсь в спецификации языка, чтобы определить это.
Однако сложно обсуждать оптимизацию компилятора, поскольку компилятор обычно волен делать все, что захочет, пока результат один и тот же, поэтому поведение вполне может меняться между версиями компилятора. Как обычно, рекомендуется выполнить тест для сравнения ваших альтернатив.
Итак, давайте сделаем это:
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);
}
Для меня, использующего .Net framework 4.8 это дает
Sum1: 1035
Sum2: 1027
Т.е. в пределах ошибок измерения. Так что я бы не стал беспокоиться об этом.
Комментарии:
1. Для микробенчмарков, подобных этим, подходы с
Stopwatch
являются грубыми и вводящими в заблуждение, даже при щедрых условиях цикла — вы никогда не можете быть полностью уверены, если вместо этого вы не измеряете какие-то несвязанные накладные расходы. BenchmarkDotNet специально написан, чтобы избежать таких ошибок.2. @Jeroen Mostert Да, я в курсе Benchmark.Net , но настройка занимает значительно больше времени. И я бы сказал, что секундомера достаточно для такой простой демонстрации. Но если вы хотите реплицировать в Benchmark.Net пожалуйста, сделай это. Мне любопытно посмотреть, есть ли какая-либо существенная разница.