이 글에서는 프로그램 성능 향상을 위해 .Net Core에서 ref와 Span
1. 서문
실제로 ref에 옴, 많은 학생들 이미 익숙함 ref는 개발자에게 지역 변수 참조 및 값 참조를 반환하는 메커니즘을 제공하는 C# 7.0의 언어 기능입니다.
Span은 ref 구문을 기반으로 하는 복잡한 데이터 유형이기도 합니다. 기사 후반부에는 이를 사용하는 방법을 보여주는 예제가 있습니다.
2. Ref 키워드
ref든 out key든 이해하고 조작하기 어려운 언어 기능입니다. C 언어의 연산 포인터처럼 이런 고급 구문은 항상 부작용을 가져오지만. 나는 이것이 아무것도 아니라고 생각하며 모든 C# 개발자가 이러한 내부 작업 메커니즘을 깊이 이해할 필요는 없다고 생각합니다. 복잡성이 무엇이든 사람들에게 자유로운 선택과 위험을 제공할 뿐이며 유연성은 결코 호환되지 않습니다. .
참조와 포인터 간의 유사성을 설명하기 위해 몇 가지 예를 살펴보겠습니다. 물론 C# 7.0 이전에는 다음 사용 방법을 사용할 수 있습니다.
public static void IncrementByRef(ref int x) { x++; } public unsafe static void IncrementByPointer(int* x) { (*x)++; }
위의 두 함수는 각각 ref 및 안전하지 않은 포인터를 사용합니다. 매개변수 +1을 완성합니다.
int i = 30; IncrementByRef(ref i); // i = 31 unsafe{ IncrementByPointer(&i); } // i = 32
다음은 C# 7.0에서 제공하는 기능입니다.
1.ref locals (로컬 변수 참조)
int i = 42; ref var x = ref i; x = x + 1; // i = 43
이 예에서는 로컬 i의 참조 x입니다. 변수, x를 변경할 때 i 변수의 값도 변경되었습니다.
2.ref 반환(반환 값 참조)
ref 반환은 C# 7의 강력한 기능입니다. 다음 코드는 해당 기능을 가장 잘 반영합니다. 이 함수는 int 배열의 항목에 대한 참조를 제공합니다.
public static ref int GetArrayRef(int[] items, int index) => ref items[index];
아래 첨자를 통해 배열에 있는 항목의 참조를 가져옵니다. 참조 값이 변경되면 배열도 그에 따라 변경됩니다.
System.Span은 System.Memory.dll 어셈블리 아래에 있는 .Net Core 코어의 일부입니다. 현재 이 기능은 독립적이며 향후 CoreFx에 통합될 수 있습니다.
사용 방법은 무엇입니까? 다음 NuGet 패키지는 .Net Core 2.0 SDK로 생성된 프로젝트에서 참조됩니다.<ItemGroup> <PackageReference Include="System.Memory" Version="4.4.0-preview1-25305-02" /> <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.4.0-preview1-25305-02" /> </ItemGroup>
위에서 우리는 ref 키워드를 사용하여 제공할 수 있는 단일 값 개체를 작동하는 포인터(T*)와 유사한 방식을 보았습니다. . 기본적으로 작동 포인터는 .NET 시스템에서 좋은 것으로 간주되지 않습니다. 물론 .NET은 단일 값 참조를 안전하게 작동하기 위한 참조를 제공합니다. 그러나 단일 값은 포인터에 대한 "포인터"를 사용하기 위한 사용자 요구의 작은 부분일 뿐이며, 더 일반적인 상황은 연속 메모리 공간에서 일련의 "요소"를 작동할 때입니다.
•배열, 관리되지 않는 포인터, 스택 포인터, 고정 또는 고정된 관리 데이터, 내부 값 영역에 대한 참조를 포함한 모든 연속 메모리 공간의 유형 시스템을 추상화합니다 • CLR 표준 객체 유형 및 값 유형 지원
•제네릭 지원 •스스로 관리하고 해제해야 하는 포인터와 달리 GC 지원
구문적, 의미적으로 ref:
와 관련된 Span의 정의를 살펴보겠습니다.
public struct Span<T> { ref T _reference; int _length; public ref T this[int index] { get {...} } ... } public struct ReadOnlySpan<T> { ref T _reference; int _length; public T this[int index] { get {...} } ... }
다음에는 Span의 사용 시나리오를 설명하기 위해 직관적인 예를 사용하겠습니다. 문자 가로채기 및 문자 변환(정수로 변환)을 예로 들어 보겠습니다.
변환하려면 123을 변환합니다. 정수 유형인 경우 일반적인 접근 방식은 먼저 부분 문자열을 사용하여 숫자 문자와 관련이 없는 문자열을 자르는 것입니다. 변환 코드는 다음과 같습니다.
string content = "content-length:123",
string content = "content-length:123"; Stopwatch watch1 = new Stopwatch(); watch1.Start(); for (int j = 0; j < 100000; j++) { int.Parse(content.Substring(15)); } watch1.Stop(); Console.WriteLine("\tTime Elapsed:\t" + watch1.ElapsedMilliseconds.ToString("N0") + "ms");
이 예제를 사용하는 이유는 무엇입니까? . 모든 작업 String은 물론 Substring뿐만 아니라 새로운 문자열 개체를 생성합니다. int.Parse를 수행할 때 문자열 개체가 반복적으로 수행되면 GC에 부담이 됩니다.
string content = "content-length:123"; ReadOnlySpan<char> span = content.ToCharArray(); span.Slice(15).ParseToInt(); watch.Start(); for (int j = 0; j < 100000; j++) { int icb = span.Slice(15).ParseToInt(); } watch.Stop(); Console.WriteLine("\tTime Elapsed:\t" + watch.ElapsedMilliseconds.ToString("N0") + "ms");
문자열을 int로 변환하는 알고리즘은 ReadonlySpan을 사용하여 구현됩니다. 이는 또한 Span의 일반적인 사용 시나리오도 동일합니다. 재사용 작업. 연속 메모리 시나리오.
public static class ReadonlySpanxtension { public static int ParseToInt(this ReadOnlySpan<char> rspan) { Int16 sign = 1; int num = 0; UInt16 index = 0; if (rspan[0].Equals('-')){ sign = -1; index = 1; } for (int idx = index; idx < rspan.Length; idx++){ char c = rspan[idx]; num = (c - '0') + num * 10; } return num * sign; } }
위 두 코드를 100000번 호출하는 데 걸리는 시간은 다음과 같습니다.
String Substring Convert: Time Elapsed: 18ms ReadOnlySpan Convert: Time Elapsed: 4ms
현재 스팬의 관련 지원 충분합니다. 가장 기본적인 아키텍처일 뿐이며 CoreFx는 Span을 사용하여 많은 API를 재구성하고 구현합니다. 앞으로 .Net Core의 성능이 점점 더 강력해질 것임을 알 수 있습니다.
위 내용은 코드 분석: ref 및 Span