일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- fast in ssms
- await
- IdentityServer4
- query
- esl
- slow in the application
- 쿼리 최적화
- ThreadPool
- SQLServer
- stored procedure
- SQL Server Optimizer
- task
- 느린 저장프로시저
- .net
- oauth2
- english
- async await
- C#
- 영어공부
- SSMS
- async
- validation
- 저장프로시저
- identityserver
- 실행계획 원리
- MSSQL
- TPL
- identityserver3
- Dataannotation
- execution plan
- Today
- Total
shyaway
C# > Span<T> 본문
Span<T>
Background
문제점 1 : 메모리 엑세스 패턴
- Managed Heap 메모리, Array 를 예로들 수 있다.
- Stack 메모리, stackalloc 에 의해 생성된 객체를 예로들 수 있다.
- Native 메모리, 네티이브 포인터 참조를 예로들 수 있다.
- Heap 메모리에 엑세스하기 위해서 fixed 포인터를 지원가능한 타입 ( string 같은 ) 에 사용하거나 해당 메모리에 접근 가능한 다른 .NET 타입, 예를 들면 Array 나 Buffer 같은 것을 사용하자.
- Stack 메모리에 엑세스하기 위해 stackalloc 을 사용한 포인터를 사용하자.
- Unmanaged 시스템 메모리에 접근하려면 마샬 API 를 이용한 포인터를 사용한다.
문제점 2 : 성능
- Trim() 이나 SubString() 은 항상 새로운 string 객체를 반환한다. 만약 부분적인 것만 잘라내거나 원본 string 의 일정 부분만 따로 저장하여 복사해둘 수 있다면, string 객체를 매번 반환할 필요가 전혀 없다.
- IsNullOrWhiteSpace() 는 메모리 복사가 필요한 string 객체를 받는다. ( string 객체는 불변이기 때문이다 )
- 특히 string 접합은 매우 비싼 비용이 드는 작업이다. N 개의 문자열 객체를 받아 N 개의 복사를 수행하고 N - 1 개의 임시 문자열 객체를 생성하여 최종 문자열 객체를 리턴하게 된다. N - 1 카피 또한 순차적 쓰기 및 반환된 string 메모리에 직접적으로 접근할 수 있다면, 전혀 필요 없는 요소이다.
Span<T>
// Use implicit operator Span<char>(char[]). Span<char> span1 = new char[] { 's', 'p', 'a', 'n' }; // Use stackalloc. Span<byte> span2 = stackalloc byte[50]; // Use constructor. IntPtr array = new IntPtr(); Span<int> span3 = new Span<int>(array.ToPointer(), 1);
Span<T> 객체를 보유하게 되면 지정한 인덱스로 값을 할당할 수 있고 해당 Span 의 일부를 리턴할 수도 있다.
// Create an instance. Span<char> span = new char[] { 's', 'p', 'a', 'n' }; // Access the reference of the first element. ref char first = ref span[0]; // Assign the reference with a new value. first = 'S'; // You get "Span". Console.WriteLine(span.ToArray()); // Return a new span with start index = 1 and end index = span.Length - 1. // You get "pan". Span<char> span2 = span.Slice(1); Console.WriteLine(span2.ToArray());
이제 Slice() 를 사용해서 보다 빠르게 Trim() 행위를 수행할 수 있다.
private static void Main(string[] args) { string test = " Hello, World! "; Console.WriteLine(Trim(test.ToCharArray()).ToArray()); } private static Span<char> Trim(Span<char> source) { if (source.IsEmpty) { return source; } int start = 0, end = source.Length - 1; char startChar = source[start], endChar = source[end]; while ((start < end) && (startChar == ' ' || endChar == ' ')) { if (startChar == ' ') { start++; } if (endChar == ' ') { end—; } startChar = source[start]; endChar = source[end]; } return source.Slice(start, end - start + 1); }
위 코드는 문자열 대상으로 카피를 하지 않고 새로운 문자열을 생성하지도 않는다. Slice() 를 통해 원본 문자열의 일부를 리턴할 뿐이다. Span<T> 는 참조 구조체이기 때문에 모든 참조 구조체에 적용이 가능하다. 따라서 Span<T> 를 필드, 프로퍼티, 반복문, async 함수에서 사용할 수는 없다.
Memory<T>
private static async Task Main(string[] args) { Memory<byte> memory = new Memory<byte>(new byte[50]); int count = await ReadFromUrlAsync("https://www.microsoft.com", memory).ConfigureAwait(false); Console.WriteLine("Bytes written: {0}", count); } private static async ValueTask<int> ReadFromUrlAsync(string url, Memory<byte> memory) { using (HttpClient client = new HttpClient()) { Stream stream = await client.GetStreamAsync(new Uri(url)).ConfigureAwait(false); return await stream.ReadAsync(memory).ConfigureAwait(false); } }
프레임워크 클래스 라이브러리와 코어 프레임워크 ( FCL/CoreFx) 가 Stream, 문자열, .NET Core 2.1 에서 사용 가능한 여러가지를 위한 Span 타입 기반의 API 를 추가 할 예정이다.
ReadOnlySpan<T> and ReadOnlyMemory<T>
private static void Main(string[] args) { // Implicit operator ReadOnlySpan(string). ReadOnlySpan<char> test = " Hello, World! "; Console.WriteLine(Trim(test).ToArray()); } private static ReadOnlySpan<char> Trim(ReadOnlySpan<char> source) { if (source.IsEmpty) { return source; } int start = 0, end = source.Length - 1; char startChar = source[start], endChar = source[end]; while ((start < end) && (startChar == ' ' || endChar == ' ')) { if (startChar == ' ') { start++; } if (endChar == ' ') { end—; } startChar = source[start]; endChar = source[end]; } return source.Slice(start, end - start + 1); }
함수 본문에서는 아무것도 변화된게 없다. 그저 Span<T> 를 ReadOnlySpan<T> 로 변경하고 암시적 연산자를 이용해 문자열을 ReadOnlySpan<char> 로 변경했을 뿐이다.
System.ReadOnlyMemory<T> 는 System.Memory<T> 의 읽기 전용 버전이다. 반복문과 async 함수에서 사용할 수 있으며 이 타입을 이용해 읽기 전용 메모리에 접근할 수 있다.
Memory Extensions
- AsSpan, AsMemory : 배열을 Span<T> 나 Memory<T> 또는 읽기 전용으로 변환한다.
- BinarySearch, IndexOf, LastIndexOf : 요소 및 인덱스를 찾는다.
- IsWhiteSpace, Trim, TrimStart, TrimEnd, ToUpper, ToUpperInvariant, ToLower, ToLowerInvariant : 문자열과 유사한 Span<char> 함수
Memory Marshal
private static void Main(string[] args) { string source = "span like types are awesome!"; // source.ToMemory() converts source from string to ReadOnlyMemory<char>, // and MemoryMarshal.AsMemory converts ReadOnlyMemory<char> to Memory<char> // so you can modify the elements. TitleCase(MemoryMarshal.AsMemory(source.AsMemory())); // You get "Span like types are awesome!"; Console.WriteLine(source); } private static void TitleCase(Memory<char> memory) { if (memory.IsEmpty) { return; } ref char first = ref memory.Span[0]; if (first >= 'a' && first <= 'z') { first = (char)(first - 32); } }
Conclusion
위 글과 그림은 https://blogs.msdn.microsoft.com/mazhou/2018/03/25/c-7-series-part-10-spant-and-universal-memory-management/ 에서 발췌, 번역하였습니다.
Span<T>
Background
Problem1 : Memory access pattern.
- Managed heap memory, such as an array;
- Stack memory, such as objects created by stackalloc;
- Native memory, such as a native pointer reference.
- To access heap memory, use the fixed (pinned) pointer on supported types (like string), or use other appropriate .NET types that have access to it, such as an array or a buffer;
- To access stack memory, use pointers with stackalloc;
- To access unmanaged system memory, use pointers with Marshal APIs.
Problem2 : Performance
- Trim() or SubString() returns a new string object that is part of the original string, this is unnecessary if there is a way to slice and return a portion of the original string to save one copy.
- IsNullOrWhiteSpace() takes a string object that needs a memory copy (because string is immutable.)
- Specifically, string concatenation is expensive, it takes n string objects, makes n copy, generate n - 1 temporary string objects, and return a final string object, the n – 1 copies can be eliminated if there is a way to get direct access to the return string memory and perform sequential writes.
Span<T>
// Use implicit operator Span<char>(char[]). Span<char> span1 = new char[] { 's', 'p', 'a', 'n' }; // Use stackalloc. Span<byte> span2 = stackalloc byte[50]; // Use constructor. IntPtr array = new IntPtr(); Span<int> span3 = new Span<int>(array.ToPointer(), 1);
Once you have a Span<T> object, you can set value with a specified index, or return a portion of the span:
// Create an instance. Span<char> span = new char[] { 's', 'p', 'a', 'n' }; // Access the reference of the first element. ref char first = ref span[0]; // Assign the reference with a new value. first = 'S'; // You get "Span". Console.WriteLine(span.ToArray()); // Return a new span with start index = 1 and end index = span.Length - 1. // You get "pan". Span<char> span2 = span.Slice(1); Console.WriteLine(span2.ToArray());
You can then use the Slice() method to write a high performance Trim() method:
private static void Main(string[] args) { string test = " Hello, World! "; Console.WriteLine(Trim(test.ToCharArray()).ToArray()); } private static Span<char> Trim(Span<char> source) { if (source.IsEmpty) { return source; } int start = 0, end = source.Length - 1; char startChar = source[start], endChar = source[end]; while ((start < end) && (startChar == ' ' || endChar == ' ')) { if (startChar == ' ') { start++; } if (endChar == ' ') { end—; } startChar = source[start]; endChar = source[end]; } return source.Slice(start, end - start + 1); }
The above code does not copy over strings, nor generate new strings, it returns a portion of the original string by calling the Slice(). Because Span<T> is a ref struct, all ref struct restrictions apply. i.e. you cannot use Span<T> in fields, properties, iterator and async methods.
Memory<T>
private static async Task Main(string[] args) { Memory<byte> memory = new Memory<byte>(new byte[50]); int count = await ReadFromUrlAsync("https://www.microsoft.com", memory).ConfigureAwait(false); Console.WriteLine("Bytes written: {0}", count); } private static async ValueTask<int> ReadFromUrlAsync(string url, Memory<byte> memory) { using (HttpClient client = new HttpClient()) { Stream stream = await client.GetStreamAsync(new Uri(url)).ConfigureAwait(false); return await stream.ReadAsync(memory).ConfigureAwait(false); } }
The Framework Class Library/Core Framework (FCL/CoreFx) will add APIs based on the span-like types for Streams, strings and more in .NET Core 2.1.
ReadOnlySpan<T> and ReadOnlyMemory<T>
private static void Main(string[] args) { // Implicit operator ReadOnlySpan(string). ReadOnlySpan<char> test = " Hello, World! "; Console.WriteLine(Trim(test).ToArray()); } private static ReadOnlySpan<char> Trim(ReadOnlySpan<char> source) { if (source.IsEmpty) { return source; } int start = 0, end = source.Length - 1; char startChar = source[start], endChar = source[end]; while ((start < end) && (startChar == ' ' || endChar == ' ')) { if (startChar == ' ') { start++; } if (endChar == ' ') { end—; } startChar = source[start]; endChar = source[end]; } return source.Slice(start, end - start + 1); }
As you can see, Nothing is changed in the method body; I just changed the parameter type from Span<T> to ReadOnlySpan<T>, and used the implicit operator to convert a string literal to ReadOnlySpan<char>.
System.ReadOnlyMemory<T> is the read-only version of System.Memory<T> struct where the Span property is a ReadOnlySpan<T>. When using this type, you get read-only access to the memory and you can use it with an iterator method or async method.
Memory Extensions
- AsSpan, AsMemory: Convert arrays into Span<T> or Memory<T> or their read-only counterparts.
- BinarySearch, IndexOf, LastIndexOf: Search elements and indexes.
- IsWhiteSpace, Trim, TrimStart, TrimEnd, ToUpper, ToUpperInvariant, ToLower, ToLowerInvariant: Span<char> operations similar to string.
Memory Marshal
private static void Main(string[] args) { string source = "span like types are awesome!"; // source.ToMemory() converts source from string to ReadOnlyMemory<char>, // and MemoryMarshal.AsMemory converts ReadOnlyMemory<char> to Memory<char> // so you can modify the elements. TitleCase(MemoryMarshal.AsMemory(source.AsMemory())); // You get "Span like types are awesome!"; Console.WriteLine(source); } private static void TitleCase(Memory<char> memory) { if (memory.IsEmpty) { return; } ref char first = ref memory.Span[0]; if (first >= 'a' && first <= 'z') { first = (char)(first - 32); } }
Conclusion
Articles and images are originally from https://blogs.msdn.microsoft.com/mazhou/2018/03/25/c-7-series-part-10-spant-and-universal-memory-management/
'.NET' 카테고리의 다른 글
IIS > IIS Express & IIS applicationhost.config path (0) | 2018.08.06 |
---|---|
IIS > IIS Express slow performance for initial requests. (0) | 2018.08.04 |
C# > Tuples (0) | 2018.07.31 |
Generics > Covariance and Contravariance (0) | 2018.07.14 |
IdentityServer > Storing an access token in IdentityServer3 (0) | 2018.07.08 |