자습서: 안전성으로
.NET 애플리케이션에 대한 성능 튜닝에는 종종 두 가지 기술이 포함됩니다. 먼저 힙 할당의 수와 크기를 줄입니다. 둘째, 데이터 복사 빈도를 줄입니다. Visual Studio는 애플리케이션에서 메모리를 사용하는 방법을 분석하는 데 유용한 도구를 제공합니다. 앱에서 불필요한 할당을 수행하는 위치를 결정했으면 해당 할당을 최소화하기 위해 변경합니다.
class
형식을 struct
형식으로 변환합니다. 안전 ref
사용하여 의미 체계를 유지하고 추가 복사를 최소화합니다.
이 자습서에서 최상의 환경을 위해 Visual Studio 17.5 를 사용합니다. 메모리 사용량을 분석하는 데 사용되는 .NET 개체 할당 도구는 Visual Studio의 일부입니다. Visual Studio Code 및 명령줄을 사용하여 애플리케이션을 실행하고 모든 변경 작업을 수행할 수 있습니다. 그러나 변경 내용의 분석 결과를 볼 수는 없습니다.
사용할 애플리케이션은 침입자가 귀중품이 있는 비밀 갤러리에 들어갔는지 확인하기 위해 여러 센서를 모니터링하는 IoT 애플리케이션의 시뮬레이션입니다. IoT 센서는 공기 중 산소(O2)와 이산화탄소(CO2)의 혼합을 측정하는 데이터를 지속적으로 전송하고 있습니다. 또한 온도 및 상대 습도를 보고합니다. 이러한 각 값은 항상 약간씩 변동합니다. 그러나, 사람이 방에 들어갈 때, 조금 더, 그리고 항상 같은 방향으로 변경: 산소 감소, 이산화탄소 증가, 온도 증가, 상대 습도와 마찬가지로. 센서가 결합되어 증가하면 침입자 경보가 트리거됩니다.
이 자습서에서는 애플리케이션을 실행하고 메모리 할당을 측정한 다음 할당 수를 줄여 성능을 향상시킵니다. 소스 코드는 샘플 브라우저에서 사용할 수 있습니다.
시작 애플리케이션 살펴보기
애플리케이션을 다운로드하고 시작 샘플을 실행합니다. 시작 애플리케이션은 올바르게 작동하지만 각 측정 주기에 많은 작은 개체를 할당하기 때문에 시간이 지남에 따라 실행되므로 성능이 저하됩니다.
Press <return> to start simulation
Debounced measurements:
Temp: 67.332
Humidity: 41.077%
Oxygen: 21.097%
CO2 (ppm): 404.906
Average measurements:
Temp: 67.332
Humidity: 41.077%
Oxygen: 21.097%
CO2 (ppm): 404.906
Debounced measurements:
Temp: 67.349
Humidity: 46.605%
Oxygen: 20.998%
CO2 (ppm): 408.707
Average measurements:
Temp: 67.349
Humidity: 46.605%
Oxygen: 20.998%
CO2 (ppm): 408.707
많은 행이 제거되었습니다.
Debounced measurements:
Temp: 67.597
Humidity: 46.543%
Oxygen: 19.021%
CO2 (ppm): 429.149
Average measurements:
Temp: 67.568
Humidity: 45.684%
Oxygen: 19.631%
CO2 (ppm): 423.498
Current intruders: 3
Calculated intruder risk: High
Debounced measurements:
Temp: 67.602
Humidity: 46.835%
Oxygen: 19.003%
CO2 (ppm): 429.393
Average measurements:
Temp: 67.568
Humidity: 45.684%
Oxygen: 19.631%
CO2 (ppm): 423.498
Current intruders: 3
Calculated intruder risk: High
코드를 탐색하여 애플리케이션의 작동 방식을 알아볼 수 있습니다. 주 프로그램은 시뮬레이션을 실행합니다. 누른 <Enter>
후 회의실을 만들고 몇 가지 초기 기준 데이터를 수집합니다.
Console.WriteLine("Press <return> to start simulation");
Console.ReadLine();
var room = new Room("gallery");
var r = new Random();
int counter = 0;
room.TakeMeasurements(
m =>
{
Console.WriteLine(room.Debounce);
Console.WriteLine(room.Average);
Console.WriteLine();
counter++;
return counter < 20000;
});
기준 데이터가 설정되면, 시뮬레이션은 방에서 실행되며, 이때 난수 생성기가 침입자의 방 진입 여부를 결정합니다.
counter = 0;
room.TakeMeasurements(
m =>
{
Console.WriteLine(room.Debounce);
Console.WriteLine(room.Average);
room.Intruders += (room.Intruders, r.Next(5)) switch
{
( > 0, 0) => -1,
( < 3, 1) => 1,
_ => 0
};
Console.WriteLine($"Current intruders: {room.Intruders}");
Console.WriteLine($"Calculated intruder risk: {room.RiskStatus}");
Console.WriteLine();
counter++;
return counter < 200000;
});
다른 형식에는 측정값, 지난 50개 측정의 평균인 설명된 측정값 및 수행된 모든 측정의 평균이 포함됩니다.
다음으로, .NET 개체 할당 도구를 사용하여 애플리케이션을 실행합니다.
Release
빌드를 사용하고, Debug
빌드를 사용하지 않는지 확인하세요.
디버그 메뉴에서 성능 프로파일러를 엽니다.
.NET 개체 할당 추적 옵션을 선택하지만 그 외에는 아무것도 선택하지 않습니다. 애플리케이션을 실행하여 완료합니다. 프로파일러는 개체 할당을 측정하고 할당 및 가비지 수집 주기에 대한 보고서를 작성합니다. 다음 이미지와 유사한 그래프가 표시됩니다.
이전 그래프는 할당을 최소화하기 위해 작업하면 성능 이점이 제공된다는 것을 보여 줍니다. 활성 객체 그래프에 톱니 패턴이 표시됩니다. 즉, 빠르게 가비지가 되는 수많은 개체가 생성된다는 것을 알 수 있습니다. 개체 델타 그래프에 표시된 대로, 그것들은 나중에 수집됩니다. 아래쪽 빨간색 막대는 가비지 수집 주기를 나타냅니다.
다음으로 그래프 아래의 할당 탭을 살펴봅니다. 이 표는 가장 많이 할당된 형식을 보여줍니다.
유형은 System.String 가장 많은 할당을 차지합니다. 가장 중요한 작업은 문자열 할당 빈도를 최소화하는 것입니다. 이 애플리케이션은 다양한 형식의 출력을 콘솔에 지속적으로 출력합니다. 이 시뮬레이션에서는 메시지를 유지하려고 합니다. 그러므로 SensorMeasurement
형식과 IntruderRisk
형식의 다음 두 행에 집중하겠습니다.
SensorMeasurement
줄을 두 번 클릭합니다. 메서드 static
에서 모든 할당이 SensorMeasurement.TakeMeasurement
수행되는 것을 볼 수 있습니다. 다음 코드 조각에서 메서드를 볼 수 있습니다.
public static SensorMeasurement TakeMeasurement(string room, int intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 * generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 * generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 * generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 * generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}
측정할 때마다 새 SensorMeasurement
개체가 할당되며, 이는 class
유형입니다. 생성된 모든 SensorMeasurement
경우 힙 할당이 발생합니다.
클래스를 구조체로 변경
다음 코드는 SensorMeasurement
의 초기 선언을 보여줍니다.
public class SensorMeasurement
{
private static readonly Random generator = new Random();
public static SensorMeasurement TakeMeasurement(string room, int intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 * generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 * generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 * generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 * generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}
private const double CO2Concentration = 409.8; // increases with people.
private const double O2Concentration = 0.2100; // decreases
private const double TemperatureSetting = 67.5; // increases
private const double HumiditySetting = 0.4500; // increases
public required double CO2 { get; init; }
public required double O2 { get; init; }
public required double Temperature { get; init; }
public required double Humidity { get; init; }
public required string Room { get; init; }
public required DateTime TimeRecorded { get; init; }
public override string ToString() => $"""
Room: {Room} at {TimeRecorded}:
Temp: {Temperature:F3}
Humidity: {Humidity:P3}
Oxygen: {O2:P3}
CO2 (ppm): {CO2:F3}
""";
}
이 형식은 원래 class
로 생성된 이유는 수많은 double
측정값을 포함하기 때문입니다. 성능이 중요한 경로에서 복사하기에는 너무 큽니다. 그러나 이 결정은 많은 수의 할당을 의미했습니다. 형식을 class
에서 struct
으로 변경합니다.
class
에서 struct
로 변경하면 원래 코드에서 null
참조 검사를 사용했던 몇 군데 때문에 몇 가지 컴파일러 오류가 발생합니다. 첫 번째는 DebounceMeasurement
클래스의 AddMeasurement
메서드에 있습니다.
public void AddMeasurement(SensorMeasurement datum)
{
int index = totalMeasurements % debounceSize;
recentMeasurements[index] = datum;
totalMeasurements++;
double sumCO2 = 0;
double sumO2 = 0;
double sumTemp = 0;
double sumHumidity = 0;
for (int i = 0; i < debounceSize; i++)
{
if (recentMeasurements[i] is not null)
{
sumCO2 += recentMeasurements[i].CO2;
sumO2+= recentMeasurements[i].O2;
sumTemp+= recentMeasurements[i].Temperature;
sumHumidity += recentMeasurements[i].Humidity;
}
}
O2 = sumO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
CO2 = sumCO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Temperature = sumTemp / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Humidity = sumHumidity / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
}
이 형식에는 DebounceMeasurement
50개의 측정값 배열이 포함되어 있습니다. 센서에 대한 판독값은 마지막 50개 측정값의 평균으로 보고됩니다. 이는 판독값의 노이즈를 줄입니다. 전체 50개의 판독값을 가져오기 전에 이러한 값은 다음과 같습니다 null
. 코드는 시스템 시작 시 올바른 평균을 보고하기 위한 null
참조를 확인합니다. 형식을 SensorMeasurement
구조체로 변경한 후에는 다른 테스트를 사용해야 합니다. 이 SensorMeasurement
유형은 방 식별자를 위한 string
를 포함하므로, 대신 그 테스트를 사용할 수 있습니다.
if (recentMeasurements[i].Room is not null)
나머지 세 컴파일러 오류는 모두 회의실에서 반복적으로 측정을 수행하는 메서드에 있습니다.
public void TakeMeasurements(Func<SensorMeasurement, bool> MeasurementHandler)
{
SensorMeasurement? measure = default;
do {
measure = SensorMeasurement.TakeMeasurement(Name, Intruders);
Average.AddMeasurement(measure);
Debounce.AddMeasurement(measure);
} while (MeasurementHandler(measure));
}
시작 메서드에서 해당 지역 변수 SensorMeasurement
는 nullable 참조입니다.
SensorMeasurement? measure = default;
SensorMeasurement
이제 nullable이 struct
아닌 class
nullable 값 형식입니다. 선언을 값 형식으로 변경하여 나머지 컴파일러 오류를 수정할 수 있습니다.
SensorMeasurement measure = default;
컴파일러 오류가 해결되었으므로 코드를 검사하여 의미 체계가 변경되지 않았는지 확인해야 합니다.
struct
형식은 값으로 전달되므로 메서드가 반환된 후에는 메서드 매개 변수를 수정한 내용이 표시되지 않습니다.
중요합니다
class
형식을 struct
형식으로 변경하면 프로그램의 의미 체계가 변경될 수 있습니다. 형식이 class
메서드에 전달되면 메서드에서 생성된 모든 변형이 인수에 적용됩니다. 형식이 struct
메서드에 전달될 때, 메서드 내에서 발생한 변형은 인수의 복사본에 적용됩니다. 즉, 인수 형식을 ref
에서 class
로 변경한 경우, 인수를 의도적으로 수정하는 모든 메서드는 해당 인수 형식에 struct
한정자를 사용하도록 업데이트되어야 합니다.
형식에는 SensorMeasurement
상태를 변경하는 메서드가 포함되지 않으므로 이 샘플에서는 문제가 되지 않습니다. 구조체에 readonly
한정자를 SensorMeasurement
추가하여 이를 증명할 수 있습니다.
public readonly struct SensorMeasurement
컴파일러는 readonly
SensorMeasurement
구조체의 특성을 강제합니다. 코드를 검사할 때 상태를 수정한 일부 메서드가 누락된 경우 컴파일러에서 알려 줍니다. 앱은 여전히 오류 없이 빌드되므로 이 유형은 다음과 같습니다 readonly
.
readonly
한정자를 추가하면 형식을 class
에서 struct
로 변경할 때 struct
상태를 수정하는 멤버를 찾는 데 도움이 될 수 있습니다.
복사본 만들기 방지
앱에서 많은 수의 불필요한 할당을 제거했습니다.
SensorMeasurement
형식이 테이블 어디에도 나타나지 않습니다.
이제 매개 변수 또는 반환 값으로 사용될 때마다 SensorMeasurement
구조를 복사하는 추가 작업을 수행하고 있습니다. 구조체 안에는 SensorMeasurement
4개의 더블, DateTime 그리고 string
가 포함되어 있습니다. 해당 구조는 참조보다 훨씬 큽합니다. 형식이 ref
사용되는 위치에 in
또는 SensorMeasurement
한정자를 추가해 보겠습니다.
다음 단계는 측정값을 반환하는 메서드를 찾거나 측정값을 인수로 사용하고 가능한 경우 참조를 사용하는 것입니다.
SensorMeasurement
구조체에서 시작하세요. 정적 TakeMeasurement
메서드는 새 SensorMeasurement
를 생성하여 반환합니다.
public static SensorMeasurement TakeMeasurement(string room, int intruders)
{
return new SensorMeasurement
{
CO2 = (CO2Concentration + intruders * 10) + (20 * generator.NextDouble() - 10.0),
O2 = (O2Concentration - intruders * 0.01) + (0.005 * generator.NextDouble() - 0.0025),
Temperature = (TemperatureSetting + intruders * 0.05) + (0.5 * generator.NextDouble() - 0.25),
Humidity = (HumiditySetting + intruders * 0.005) + (0.20 * generator.NextDouble() - 0.10),
Room = room,
TimeRecorded = DateTime.Now
};
}
이 항목은 그대로 두고, 값으로 반환합니다. 반환 ref
하려고 하면 컴파일러 오류가 발생합니다. 메서드에서 로컬로 생성된 새 구조체에 ref
를 반환할 수 없습니다. 변경할 수 없는 구조체의 디자인은 생성 시 측정값만 설정할 수 있습니다. 이 메서드는 새 측정 구조체를 만들어야 합니다.
DebounceMeasurement.AddMeasurement
을 다시 살펴보겠습니다. 매개 변수에 in
한정자를 추가해야 합니다.measurement
public void AddMeasurement(in SensorMeasurement datum)
{
int index = totalMeasurements % debounceSize;
recentMeasurements[index] = datum;
totalMeasurements++;
double sumCO2 = 0;
double sumO2 = 0;
double sumTemp = 0;
double sumHumidity = 0;
for (int i = 0; i < debounceSize; i++)
{
if (recentMeasurements[i].Room is not null)
{
sumCO2 += recentMeasurements[i].CO2;
sumO2+= recentMeasurements[i].O2;
sumTemp+= recentMeasurements[i].Temperature;
sumHumidity += recentMeasurements[i].Humidity;
}
}
O2 = sumO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
CO2 = sumCO2 / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Temperature = sumTemp / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
Humidity = sumHumidity / ((totalMeasurements > debounceSize) ? debounceSize : totalMeasurements);
}
이를 통해 하나의 복사 작업이 저장됩니다.
in
매개 변수는 호출자가 이미 만든 복사본에 대한 참조입니다.
TakeMeasurement
메서드를 Room
형식에서 사용하여 복사본을 저장할 수도 있습니다. 이 메서드는 인수 ref
를 전달할 때 컴파일러가 보안을 제공하는 방법을 보여 줍니다. 형식의 TakeMeasurement
초기 Room
메서드는 인수를 Func<SensorMeasurement, bool>
사용합니다. 해당 선언에 in
한 ref
정자를 추가하려고 하면 컴파일러에서 오류를 보고합니다. 인수를 ref
람다 식에 전달할 수 없습니다. 컴파일러는 호출된 식이 참조를 복사하지 않도록 보장할 수 없습니다. 람다 식이 참조 를 캡처 하는 경우 참조가 참조하는 값보다 더 긴 수명을 가질 수 있습니다.
참조 안전 컨텍스트 외부에서 액세스하면 메모리가 손상됩니다.
ref
안전 규칙은 이를 허용하지 않습니다.
ref 안전 기능의 개요에서 자세히 알아볼 수 있습니다.
의미 체계 유지
형식이 핫 경로에 생성되지 않으므로 최종 변경 집합은 이 애플리케이션의 성능에 큰 영향을 미치지 않습니다. 이러한 변경 내용은 성능 튜닝에 사용할 다른 몇 가지 기술을 보여 줍니다. 초기 Room
클래스를 살펴보겠습니다.
public class Room
{
public AverageMeasurement Average { get; } = new ();
public DebounceMeasurement Debounce { get; } = new ();
public string Name { get; }
public IntruderRisk RiskStatus
{
get
{
var CO2Variance = (Debounce.CO2 - Average.CO2) > 10.0 / 4;
var O2Variance = (Average.O2 - Debounce.O2) > 0.005 / 4.0;
var TempVariance = (Debounce.Temperature - Average.Temperature) > 0.05 / 4.0;
var HumidityVariance = (Debounce.Humidity - Average.Humidity) > 0.20 / 4;
IntruderRisk risk = IntruderRisk.None;
if (CO2Variance) { risk++; }
if (O2Variance) { risk++; }
if (TempVariance) { risk++; }
if (HumidityVariance) { risk++; }
return risk;
}
}
public int Intruders { get; set; }
public Room(string name)
{
Name = name;
}
public void TakeMeasurements(Func<SensorMeasurement, bool> MeasurementHandler)
{
SensorMeasurement? measure = default;
do {
measure = SensorMeasurement.TakeMeasurement(Name, Intruders);
Average.AddMeasurement(measure);
Debounce.AddMeasurement(measure);
} while (MeasurementHandler(measure));
}
}
이 형식에는 여러 속성이 포함되어 있습니다. 일부는 class
형식입니다.
Room
개체를 생성하는 경우 여러 할당이 필요합니다. 하나는 Room
자체를 위한 것이고, 하나는 포함된 형식의 class
각 멤버에 대한 것입니다.
class
형식을 struct
형식으로 변환할 수 있는 속성 두 가지는 DebounceMeasurement
와 AverageMeasurement
입니다. 두 형식을 모두 사용하여 해당 변환을 살펴보겠습니다.
DebounceMeasurement
형식을 class
에서 struct
로 변경합니다. 컴파일러 오류가 CS8983: A 'struct' with field initializers must include an explicitly declared constructor
발생합니다. 매개 변수가 없는 빈 생성자를 추가하여 이 문제를 해결할 수 있습니다.
public DebounceMeasurement() { }
구조체에 대한 언어 참조 문서에서 이 요구 사항에 대해 자세히 알아볼 수 있습니다.
재정의는 Object.ToString() 구조체의 값을 수정하지 않습니다. 해당 메서드 선언에 readonly
한정자를 추가할 수 있습니다.
DebounceMeasurement
형식이 변경 가능하므로 수정이 삭제된 복사본에 영향을 주지 않도록 주의해야 합니다. 메서드는 AddMeasurement
개체의 상태를 수정합니다.
Room
클래스의 TakeMeasurements
메서드에서 호출됩니다. 메서드를 호출한 후 이러한 변경 내용을 유지하려고 합니다.
Room.Debounce
속성을 변경하여 단일 형식의 인스턴스에 대한 DebounceMeasurement
를 반환할 수 있습니다.
private DebounceMeasurement debounce = new();
public ref readonly DebounceMeasurement Debounce { get { return ref debounce; } }
이전 예제에는 몇 가지 변경 내용이 있습니다. 먼저 이 속성 은 이 룸이 소유한 인스턴스에 대한 읽기 전용 참조를 반환하는 읽기 전용 속성입니다. 이제 Room
개체가 인스턴스화될 때 초기화되는 필드가 선언되어 이를 지원합니다. 이러한 변경을 수행한 후 메서드 구현 AddMeasurement
을 업데이트합니다. 읽기 전용 속성debounce
이 아닌 프라이빗 지원 필드를 Debounce
사용합니다. 이렇게 하면 초기화 중에 만든 단일 인스턴스에서 변경 내용이 수행됩니다.
동일한 기술이 Average
속성에서도 작동합니다. 먼저 형식을 AverageMeasurement
a에서 a class
struct
로 수정하고 메서드에 readonly
ToString
한정자를 추가합니다.
namespace IntruderAlert;
public struct AverageMeasurement
{
private double sumCO2 = 0;
private double sumO2 = 0;
private double sumTemperature = 0;
private double sumHumidity = 0;
private int totalMeasurements = 0;
public AverageMeasurement() { }
public readonly double CO2 => sumCO2 / totalMeasurements;
public readonly double O2 => sumO2 / totalMeasurements;
public readonly double Temperature => sumTemperature / totalMeasurements;
public readonly double Humidity => sumHumidity / totalMeasurements;
public void AddMeasurement(in SensorMeasurement datum)
{
totalMeasurements++;
sumCO2 += datum.CO2;
sumO2 += datum.O2;
sumTemperature += datum.Temperature;
sumHumidity+= datum.Humidity;
}
public readonly override string ToString() => $"""
Average measurements:
Temp: {Temperature:F3}
Humidity: {Humidity:P3}
Oxygen: {O2:P3}
CO2 (ppm): {CO2:F3}
""";
}
그런 다음 Room
클래스를 수정할 때 Debounce
속성에 사용한 동일한 기술을 따릅니다. 이 Average
속성은 평균 측정값에 대한 비공개 필드의 readonly ref
를 반환합니다. 메서드는 AddMeasurement
내부 필드를 수정합니다.
private AverageMeasurement average = new();
public ref readonly AverageMeasurement Average { get { return ref average; } }
권투 방지
성능을 향상시키기 위한 마지막 변경 사항이 하나 있습니다. 주요 프로그램은 위험 평가를 포함하여 방에 대한 통계를 인쇄하는 것입니다.
Console.WriteLine($"Current intruders: {room.Intruders}");
Console.WriteLine($"Calculated intruder risk: {room.RiskStatus}");
생성된 ToString
상자에 대한 호출은 열거형 값을 반환합니다. 예상 위험 값에 따라 문자열의 형식을 지정하는 Room
클래스에 오버라이드를 작성하여 이를 방지할 수 있습니다.
public override string ToString() =>
$"Calculated intruder risk: {RiskStatus switch
{
IntruderRisk.None => "None",
IntruderRisk.Low => "Low",
IntruderRisk.Medium => "Medium",
IntruderRisk.High => "High",
IntruderRisk.Extreme => "Extreme",
_ => "Error!"
}}, Current intruders: {Intruders.ToString()}";
그런 다음, 이 새 ToString
메서드를 호출하도록 주 프로그램의 코드를 수정합니다.
Console.WriteLine(room.ToString());
프로파일러를 사용하여 앱을 실행하고 업데이트된 테이블에서 할당을 확인합니다.
수많은 할당을 제거하고 앱에 성능 향상을 제공했습니다.
애플리케이션에서 레퍼런스 안전성 사용
이러한 기술은 낮은 수준의 성능 튜닝입니다. 핫 경로에 적용할 때와 변경 전후에 미치는 영향을 측정한 경우 애플리케이션의 성능을 높일 수 있습니다. 대부분의 경우 따라야 할 주기는 다음과 같습니다.
- 할당 측정: 어떤 형식이 가장 많이 할당되는지 그리고 힙 할당을 언제 줄일 수 있는지 결정합니다.
-
클래스를 구조체로 변환: 형식을 여러 번
class
에서struct
로 변환할 수 있습니다. 앱은 힙 할당을 만드는 대신 스택 공간을 사용합니다. -
의미 보존:
class
을struct
로 변환하면 매개 변수 및 반환 값의 의미 체계에 영향을 미칠 수 있습니다. 해당 매개 변수를 수정하는 모든 메서드는 이제 해당 매개 변수를ref
한정자와 함께 표시해야 합니다. 이를 통해 수정 사항이 올바른 개체에 적용되도록 보장합니다. 마찬가지로 호출자가 속성 또는 메서드 반환 값을 수정해야 하는 경우 해당 반환은 한정자를 사용하여ref
표시되어야 합니다. -
복사 방지: 큰 구조체를 매개 변수로 전달하면 매개 변수를 한정자로 표시할
in
수 있습니다. 참조를 더 적은 바이트로 전달하고 메서드가 원래 값을 수정하지 않는지 확인할 수 있습니다.readonly ref
를 사용하여 수정할 수 없는 참조를 반환함으로써 값을 반환할 수도 있습니다.
이러한 기술을 사용하면 코드의 핫 경로에서 성능을 향상시킬 수 있습니다.
.NET