성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] 그냥 RSS Reader 기능과 약간의 UI 편의성 때문에 사용...
[이종효] 오래된 소프트웨어는 보안 위협이 되기도 합니다. 혹시 어떤 기능...
[정성태] @Keystroke IEEE의 문서를 소개해 주시다니... +_...
[손민수 (Keystroke)] 괜히 듀얼채널 구성할 때 한번에 같은 제품 사라고 하는 것이 아...
[정성태] 전각(Full-width)/반각(Half-width) 기능을 토...
[정성태] Vector에 대한 내용은 없습니다. Vector가 닷넷 BCL...
[orion] 글 읽고 찾아보니 디자인 타임에는 InitializeCompon...
[orion] 연휴 전에 재현 프로젝트 올리자 생각해 놓고 여의치 않아서 못 ...
[정성태] 아래의 글에 정리했으니 참고하세요. C# - Typed D...
[정성태] 간단한 재현 프로젝트라도 있을까요? 저런 식으로 설명만 해...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# 7.2 - Span<T></h1> <p> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 7.2 (1) - readonly 구조체 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11524'>http://www.sysnet.pe.kr/2/0/11524</a> C# 7.2 (2) - 메서드의 매개 변수에 in 변경자 추가 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11525'>http://www.sysnet.pe.kr/2/0/11525</a> C# 7.2 (3) - 메서드의 반환값 및 로컬 변수에 ref readonly 기능 추가 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11526'>http://www.sysnet.pe.kr/2/0/11526</a> C# 7.2 (4) - 3항 연산자에 ref 지원(conditional ref operator) ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11528'>http://www.sysnet.pe.kr/2/0/11528</a> C# 7.2 (5) - 스택에만 생성할 수 있는 값 타입 지원 - "ref struct" ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11530'>http://www.sysnet.pe.kr/2/0/11530</a> C# 7.2 (6) - Span<T> ; http://www.sysnet.pe.kr/2/0/11534 C# 7.2 (7) - private protected 접근자 추가 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11543'>http://www.sysnet.pe.kr/2/0/11543</a> C# 7.2 (8) - 숫자 리터럴의 선행 밑줄과 뒤에 오지 않는 명명된 인수 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11544'>http://www.sysnet.pe.kr/2/0/11544</a> 기타 - Microsoft Build 2018 - The future of C# 동영상 내용 정리 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11536'>http://www.sysnet.pe.kr/2/0/11536</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 엄밀히, Span<T> 타입은 C# 7.2의 기능은 아니고 C# 7.2의 "ref struct" 구문을 이용한 하나의 사례일 뿐입니다. 심지어, Span<T> 타입은 (2018-06-05 기준 가장 최신 버전인) .NET Framework 4.7.2에도 포함되어 있지 않고 .NET Core 쪽으로만 2.1 이후부터 제공되고 있습니다.<br /> <br /> 따라서 이 글의 내용을 .NET Framework에서 실습하고 싶다면 System.Memory 어셈블리를 NuGet으로부터 받아야만 합니다. (또는, .NET Core 2.1 용 프로젝트를 생성합니다.)<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.Memory ; <a target='tab' href='https://www.nuget.org/packages/System.Memory'>https://www.nuget.org/packages/System.Memory</a> </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Install-Package System.Memory -Version 4.5.0 </pre> <br /> 참고로, 다음의 글을 읽어보시는 것도 좋습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# - All About Span: Exploring a New .NET Mainstay ; <a target='tab' href='https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay'>https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay</a> C# 7 Series, Part 10: Span<T> and universal memory management ; <a target='tab' href='https://learn.microsoft.com/en-us/archive/blogs/mazhou/c-7-series-part-10-spant-and-universal-memory-management'>https://learn.microsoft.com/en-us/archive/blogs/mazhou/c-7-series-part-10-spant-and-universal-memory-management</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 지난 글에서 설명한 "ref struct"의,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C# 7.2 - 스택에만 생성할 수 있는 값 타입 지원 - "ref struct" ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11530'>http://www.sysnet.pe.kr/2/0/11530</a> </pre> <br /> 실제 적용 사례가 바로 Span<T> 타입입니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public readonly <span style='color: blue; font-weight: bold'>ref struct</span> Span<T> { private readonly <span style='color: blue; font-weight: bold'>ref T _pointer</span>; private readonly int _length; ... } </pre> <br /> 그로 인해 Span 타입은 반드시 스택에만 할당할 수 있으므로 ref struct가 아닌 타입의 필드로는 정의할 수 없습니다. 비록 Span 타입을 만들기 위해 ref struct가 나온 것으로 보이지만, Span은 내부에 관리 포인터를 - 그것도 제네릭 표현으로 가지고 있다는 면에서 C# 개발자들이 Span과 같은 타입을 만들 수 없다는 특별함이 있습니다. "<a target='tab' href='https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay'>C# - All About Span: Exploring a New .NET Mainstay</a>" 글에서는 이에 대해 다음과 같이 언급하고 있습니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> The concept of a ref T field may be strange at first-in fact, <span style='color: blue; font-weight: bold'>one can't actually declare a ref T field in C# or even in MSIL</span>. But <span style='color: blue; font-weight: bold'>Span<T> is actually written to use a special internal type in the runtime that's treated as a just-in-time (JIT) intrinsic</span>, with the JIT generating for it the equivalent of a ref T field. </div><br /> <br /> 따라서 Span 타입은 간단하게 "제네릭 관리 포인터를 가진 ref struct"라는 특별한 구조체입니다. 결국 관리 포인터를 포함하고 있기 때문에 힙에 할당되어서는 안 되며 이러한 요구 사항은 "ref struct"에 부합합니다. (참고로, 관리 포인터를 힙에 가질 수 있도록 참조 형의 필드로 추가하거나 어떤 식으로든 관리 힙에 전달하는 식의 코드가 있다면 JIT 컴파일 단계에서 IL Verification을 통과하지 못해 런타임 예외가 발생합니다.)<br /> <br /> 기능적인 면에서 Span 타입을 설명하면, "배열에 대한 참조 View"를 제공한다고 보면 됩니다. 따라서, C#에서 만드는 모든 배열에 대해 Span으로 가리킬 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 관리 메모리에 할당된 바이트 배열 // <a target='tab' href='https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay'>https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay</a> var arr = new byte[10]; Span<byte> bytes = arr; // Implicit cast from T[] to Span<T> </pre> <br /> 참조 View이기 때문에 Span 변수의 값을 바꾸면 원본 변수의 값도 바뀝니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var arr = new byte[10]; System.Span<byte> bytes = arr; <span style='color: blue; font-weight: bold'>bytes[2] = 5;</span> OutputArrary(arr); // 출력 결과: 0,0,<span style='color: blue; font-weight: bold'>5</span>,0,0,0,0,0,0,0, private static void OutputArrary(byte[] arr) { for (int i = 0; i < arr.Length; i++) { Console.Write(arr[i] + ","); } Console.WriteLine(); } </pre> <br /> 관리 힙에 있는 배열뿐만 아니라 스택에 있는 배열까지도 Span 타입으로 참조 뷰를 만들 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Span<byte> bytes = stackalloc byte[2]; // Using C# 7.2 stackalloc support for spans </pre> <br /> 관리 힙의 배열과는 다르게 stackalloc 배열을 간접적으로 받는 것은 안 되지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > { var arr = new byte[10]; System.Span<byte> bytes = arr; // OK } { var arr = stackalloc byte[10]; <span style='color: blue; font-weight: bold'>System.Span<byte> bytes = arr</span>; // 컴파일 오류 - CS0029 Cannot implicitly convert type 'byte*' to 'System.Span<byte>' } </pre> <br /> 생성자를 통해 스택 배열의 크기와 함께 전달하는 것은 가능합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > byte* arr = stackalloc byte[10]; <span style='color: blue; font-weight: bold'>Span<byte> bytes = new Span<byte>(arr, 10);</span> PassStackAllocBytes(bytes); Console.WriteLine(*(arr + 5)); // 출력: 5 Console.WriteLine(bytes[5]); // 출력: 5 private static void PassStackAllocBytes(Span<byte> bytes) { bytes[5] = 5; } </pre> <br /> 물론, 스택 배열을 메서드가 반환한 경우 스택 해제로 인한 범위를 벗어나기 때문에 그런 경우에는 반환값으로 사용할 수 없습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static Span<byte> GetStackAllocBytes() { Span<byte> buffer = <span style='color: blue; font-weight: bold'>stackalloc</span> byte[2]; <span style='color: blue; font-weight: bold'>return buffer</span>; // 컴파일 오류 - CS8352 Cannot use local 'buffer' in this context because it may expose referenced variables outside of their declaration scope } </pre> <br /> 관리 메모리, 스택 메모리에 이어 관리되지 않는(unmanaged) 메모리에도 참조 뷰를 생성할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > unsafe void UnmanagedView() { int size = 1000; IntPtr ptr = <span style='color: blue; font-weight: bold'>Marshal.AllocCoTaskMem</span>(size); try { <span style='color: blue; font-weight: bold'>Span<byte> bytes = new Span<byte>(ptr.ToPointer(), size);</span> bytes[10] = 6; int value = Marshal.ReadByte(ptr, 10); Console.WriteLine(value); // 출력: 6 } finally { Marshal.FreeCoTaskMem(ptr); } } </pre> <a name='managed_view'></a> <br /> 재미있는 것은, unmanaged 메모리에 대한 "관리 타입의 참조 뷰" 역할을 Span 타입이 해준다는 점입니다. 그동안 C#에서 비관리 메모리 영역을 접근할 때는 AV(Access Violation) 예외나 기타 예측할 수 없는 상황에 빠질 수 있었는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int size = 1000; IntPtr ptr = <span style='color: blue; font-weight: bold'>Marshal.AllocCoTaskMem</span>(size); try { Marshal.WriteByte(ptr, size * 1000, 25); <span style='color: blue; font-weight: bold'>// unpredictable</span> } catch (Exception ex) { // Never run Console.WriteLine(ex.ToString()); } </pre> <br /> 이제는 간단하게 Span의 도움을 받아 unmanaged 메모리에 대한 접근을 안전하고 성능 저하 없이 할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int size = 1000; IntPtr ptr = Marshal.AllocCoTaskMem(size); Span<byte> bytes = new Span<byte>(ptr.ToPointer(), size); try { bytes[size * 1000] = 25; <span style='color: blue; font-weight: bold'>// 런타임 시 예외 발생 - System.IndexOutOfRangeException </span> } catch (Exception ex) { Console.WriteLine("Catched: " + ex.ToString()); } </pre> <br /> 게다가 Span 타입이 제네릭이다 보니 비-관리 메모리에 대해 마치 관리 힙의 배열로 사용할 수 있도록 해줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > int size = 1000; IntPtr ptr = Marshal.AllocCoTaskMem(size * sizeof(int)); try { Span<int> bytes = new Span<int>(ptr.ToPointer(), size); for (int i = 0; i < size; i ++) { bytes[i] = i; } OutputArrary(bytes); } finally { Marshal.FreeCoTaskMem(ptr); } </pre> <br /> 이렇게 Span 타입은 힙, 스택, 비-관리 메모리에 대해 공통적인 메모리 접근을 할 수 있기 때문에 "<a target='tab' href='https://learn.microsoft.com/en-us/archive/blogs/mazhou/c-7-series-part-10-spant-and-universal-memory-management'>C# 7 Series, Part 10: Span<T> and universal memory management</a>" 글의 제목에 "universal"이라는 수식어가 붙게 된 것입니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> Span을 "참조 뷰"라고 소개했는데, "뷰"라는 관점으로 좀 더 설명해 보면 다음과 같은 기능이 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > var arr = new byte[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; System.Span<byte> bytes = arr; <span style='color: blue; font-weight: bold'>Span<byte> left = bytes.Slice(0, arr.Length / 2); Span<byte> right = bytes.Slice(arr.Length / 2); </span> for (int i = 0; i < arr.Length / 2; i ++) { left[i] = i * 10; right[i] = i * 10; } OutputArrary(left); OutputArrary(right); /* 출력 결과: 0,10,20,30,40, 0,10,20,30,40, */ </pre> <br /> 이렇게 부분 뷰에 대한 참조를 일컬어 "<a target='tab' href='https://blog.ndepend.com/managed-pointers-span-ref-struct-c11-ref-fields-and-the-scoped-keyword/'>interior pointer</a>"라고 하며 <a target='_blank' href='https://github.com/dotnet/roslyn/blob/master/docs/Language%20Feature%20Status.md'>Language Feature Status</a> 문서에서 C# 7.2의 기능으로 "<span style='color: blue; font-weight: bold'>interior pointer</span>/Span/ref struct"라고 소개할 때의 바로 그 단어입니다. <br /> <br /> 이를 활용하면, 특정 메모리의 영역만을 건드리도록 제약을 두고 다른 메서드에 쉽게 전달하는 것이 가능합니다. 과거에 이런 View 역할을 해주는 ArraySegment가 있었지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > System.IO.MemoryStream, ArraySegment<T>의 효율적인 사용법 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1483'>http://www.sysnet.pe.kr/2/0/1483</a> </pre> <br /> 이젠 Span<T>가 그 역할을 대신하게 될 것입니다. 참조 뷰를 잘 활용하면 기존 코드들의 성능을 높일 수 있는 시나리오가 제법 됩니다. "<a target='tab' href='https://learn.microsoft.com/en-us/archive/msdn-magazine/2018/january/csharp-all-about-span-exploring-a-new-net-mainstay'>C# - All About Span: Exploring a New .NET Mainstay</a>" 글에는 이에 대한 예제로 "123,456"과 같은 문자열로부터 숫자 데이터 2개를 구하는 기존 코드를 보여줍니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string input = ...; int commaPos = input.IndexOf(','); int first = int.Parse(input.Substring(0, commaPos)); // "123" 신규 문자열 할당 int second = int.Parse(input.Substring(commaPos + 1)); // "456" 신규 문자열 할당 </pre> <br /> 위와 같은 경우, string은 불변 객체이기 때문에 2번의 input.Substring은 개별적인 메모리 할당이 됩니다. 이것을 향후 .NET Framework의 BCL에 Span을 받는 메서드를 추가하는 것으로 다음과 같이 성능을 더 높일 수 있다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > string input = ...; ReadOnlySpan<char> inputSpan = input.AsSpan(); int commaPos = input.IndexOf(','); int first = int.Parse(<span style='color: blue; font-weight: bold'>inputSpan.Slice</span>(0, commaPos)); // 할당 없이 "123" 영역을 가리키는 Span int second = int.Parse(<span style='color: blue; font-weight: bold'>inputSpan.Slice</span>(commaPos + 1)); // 할당 없이 "456" 영역을 가리키는 Span </pre> <br /> <hr style='width: 50%' /><br /> <br /> Span<T>외에도 "readonly ref struct Span<T>"에 해당하는 "System.ReadOnlySpan<T>"이 있습니다. 대표적으로 string 조작을 할 때 (string이 immutable이므로) ReadOnlySpan으로 받는 것이 적당합니다.<br /> <a name='limit'></a> <br /> 그리고 이러한 Span 타입들이 모두 "ref struct"이기 때문에 heap에 생성할 수 없어 비동기 메서드 및 열거자와 같은 몇 가지 시나리오에서 사용할 수 없는 제약이 있습니다. 그래서, Span 타입과의 연동 및 그와 유사한 역할을 하도록 "readonly struct"로 정의한 System.Memory<T>가 있으며 다시 그것의 readonly인 ReadOnlyMemory<T>가 있습니다.<br /> <br /> 아마도 이를 모두 적용한 .NET Framework 신규 버전이 나오면 BCL 자체 내에서도 Span/Memory를 이용한 코드를 적용할 것이므로 어쩌면 프레임워크 버전을 올리는 것만으로도 기존 닷넷 응용 프로그램의 성능이 높아지는 결과가 발생할 수 있습니다. 또한 응용 프로그램에서 사용 중인 (JSON.NET과 같은) 기존 라이브러리들이 Span/Memory를 활용해 내놓은 새 버전을 쓰는 것만으로도 성능이 올라갈 수 있습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
9691
(왼쪽의 숫자를 입력해야 합니다.)