성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Roll A Lisp In C - Reading ; https...
[정성태] Java - How to use the Foreign Funct...
[정성태] 제가 큰 실수를 했군요. ^^; Delegate를 통한 Bein...
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
글쓰기
제목
이름
암호
전자우편
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# - .NET 7부터 추가된 Int128, UInt128</h1> <p> (<a target='tab' href='https://gcc.gnu.org/onlinedocs/gcc/_005f_005fint128.html'>gcc 등에서는 지원</a>하지만) Visual C++도 지원하지 않는 Int128을 닷넷 8부터 추가해 C# 언어에서도 사용할 수 있게 되었습니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Int128 Struct ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.int128'>https://learn.microsoft.com/en-us/dotnet/api/system.int128</a> </pre> <br /> 단지, 다른 타입과는 달리 C# 측에서 대응하는 alias가 없어 BigInteger를 사용하듯이 써야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > { Int128 value = long.MaxValue; value *= long.MaxValue; Console.WriteLine(value); // 85070591730234615847396907784232501249 } { Int128 value = Int128.Parse("85070591730234615847396907784232501249"); Console.WriteLine(value); } </pre> <br /> 개인적으로 이걸 보고 궁금했던 게, <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.threading.interlocked'>Interlocked</a> 측에서의 지원이 있느냐는 것이었습니다. 아쉽게도 여전히 (8바이트) long 형식까지만 지원하지만, 그래도 아예 불가능한 것은 아닙니다. 만들면 되니까요? ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이를 위해 우리는 inline asm 기법을 사용해야 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C++의 inline asm 사용을 .NET으로 포팅하는 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1267'>https://www.sysnet.pe.kr/2/0/1267</a> </pre> <br /> 게다가 InterlockedCompareExchange128 API를 구현한 소스 코드가 GitHub에 있으니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Execute a InterlockedCompareExchange128 natively from C# ; <a target='tab' href='https://gist.github.com/jduncanator/ab17e4e476300d3eb0b7c19f6f38429a'>https://gist.github.com/jduncanator/ab17e4e476300d3eb0b7c19f6f38429a</a> </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# 9.0 - (6) 함수 포인터(Function pointers) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12374'>https://www.sysnet.pe.kr/2/0/12374</a> </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;' > using System.Runtime.InteropServices; namespace int128sample; public unsafe class InterlockedExtension { // Execute a InterlockedCompareExchange128 natively from C# // ; <a target='tab' href='https://gist.github.com/jduncanator/ab17e4e476300d3eb0b7c19f6f38429a'>https://gist.github.com/jduncanator/ab17e4e476300d3eb0b7c19f6f38429a</a> static byte[] asmCmpXchg16b = new byte[] { 0x48, 0x89, 0x5C, 0x24, 0x08, // MOV [RSP+0x8], RBX 0x49, 0x8B, 0x01, // MOV RAX, [R9] 0x49, 0x89, 0xCA, // MOV R10, RCX 0x48, 0x89, 0xD1, // MOV RCX, RDX 0x4C, 0x89, 0xC3, // MOV RBX, R8 0x49, 0x8B, 0x51, 0x08, // MOV RDX, [R9+0x8] 0xF0, 0x49, 0x0F, 0xC7, 0x0A, // LOCK CMPXCHG16B [R10] 0x48, 0x8B, 0x5C, 0x24, 0x08, // MOV RBX, [RSP+0x8] 0x49, 0x89, 0x01, // MOV [R9], RAX 0x0F, 0x94, 0xC0, // SETE AL 0x49, 0x89, 0x51, 0x08, // MOV [R9+0x8], RDX 0xC2, 0x00, 0x00 // RET 0 }; <span style='color: blue; font-weight: bold'>public static delegate* unmanaged[Stdcall, SuppressGCTransition]<long*, long, long, long*, byte> _InterlockedCompareExchange128;</span> static GCHandle _InterlockedCompareExchange128Handle; static InterlockedExtension() { _InterlockedCompareExchange128Handle = GCHandle.Alloc(asmCmpXchg16b, GCHandleType.Pinned); nint pData = (nint)_InterlockedCompareExchange128Handle.AddrOfPinnedObject().ToPointer(); EnsureMemoryIsExecutable(pData, asmCmpXchg16b.Length); <span style='color: blue; font-weight: bold'>_InterlockedCompareExchange128 = (delegate* unmanaged[Stdcall, SuppressGCTransition]<long*, long, long, long*, byte>)pData;</span> } // ...[생략]... } </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;' > { Int128 value = 0; Int128 comparand = 0; InterlockedExtension._InterlockedCompareExchange128((long*)&value, 0, 1, (long*)&comparand); Console.WriteLine(value); // 출력 결과: 1 } </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그런데 실행해 보면, 지난 글에서 다룬 것과 동일한 aligned 문제가,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Visual C++ - InterlockedCompareExchange128 사용 방법 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/13472#align16'>https://www.sysnet.pe.kr/2/0/13472#align16</a> </pre> <br /> GC Heap 또는 스택에 할당된 Int128 변수에 적용되므로 이런 예외가 확률적으로 발생하게 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. at int128sample.Program.Main(System.String[]) </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;' > { long temporary = 0; // 8바이트 점유 Int128 value = 0; // 이전에 8바이트 정렬이었다면 (운이 따르는 경우) temporary 변수로 인해 16바이트 위치로 변경 // ...[생략]... } </pre> <br /> 16바이트 정렬 효과를 갖게 돼 정상적으로 실행될 것입니다. 물론, 이 방법을 (release 빌드에서는 없어지는 문제도 있고, 최적화 시 재정렬될 수도 있으므로) 업무 코드에서 사용할 수는 없습니다. 그렇다면, C/C++의 경우 전역 변수를 사용하면 16바이트 정렬이 되었는데, C#은 어떨까요?<br /> <br /> C#은 전역 변수라는 것이 없이, class 또는 struct 내에 static으로 흉내를 낼 수 있는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > internal unsafe class Program { <span style='color: blue; font-weight: bold'>static Int128 g_value = 0;</span> static void Main(string[] args) { fixed(Int128* ptr = &g_value) { Int128 value = 0; Int128 comparand = 0; InterlockedExtension._InterlockedCompareExchange128((long*)ptr, 0, 1, (long*)&comparand); Console.WriteLine(value); } } } </pre> <br /> 이것 역시 확률적으로 crash가 발생합니다. 이유는, g_value가 GC Heap(<a target='tab' href='https://www.sysnet.pe.kr/2/0/12387#14877'>HighFrequencyHeap</a>)에 할당이 될 텐데 그런 경우 16바이트 정렬된 위치에 할당이 되리라는 것을 보장할 수 없기 때문입니다.<br /> <br /> 그렇다고 C#에 C/C++과 같은 "__declspec(align(16))"이 있는 것도 아니고... 난감하군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <a name='aligned_alloc'></a> <br /> 자, 그렇다면 C#에서 해결해야 할 가장 큰 난제는 바로 정렬입니다. 이를 위해 StructLayout의 Pack이 있지만,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > [StructLayout(LayoutKind.Sequential, <span style='color: blue; font-weight: bold'>Pack = 16</span>)] public struct Data { byte __padding0; // (운에 따라) 이 위치가 0x0018이라면, public Int128 Value; // 이 위치는 16바이트를 건너 뛴 0x0028로 정렬 } </pre> <br /> 비록 alignment에 관여를 하긴 해도, 그것은 내부 멤버들의 정렬에 영향을 주는 것이기 때문에 애당초 저 구조체가 8바이트 정렬로 되는 것을 막을 수는 없습니다.<br /> <br /> 그렇다면 Win32 API를 interop 해야 할 것 같은데, 다행히 .NET 6부터 NativeMemory.AlignedAlloc이 제공되므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > NativeMemory.AlignedAlloc(UIntPtr, UIntPtr) Method ; <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativememory.alignedalloc'>https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativememory.alignedalloc</a> </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;' > public unsafe class NativeInt128 : IDisposable { nint _value; public NativeInt128() : this(0) { } public NativeInt128(long value) { <span style='color: blue; font-weight: bold'>_value = (nint)NativeMemory.AlignedAlloc(16, 16);</span> *(Int128*)_value = value; } public nint ValuePtr { get { return _value; } } public Int128 Value { get { return *(Int128*)_value; } set { *(Int128*)_value = value; } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~NativeInt128() { Dispose(false); } protected virtual void Dispose(bool disposing) { if (_value == 0) { return; } NativeMemory.Free(_value.ToPointer()); _value = 0; } } </pre> <br /> 그런 다음, 이렇게 써야 그나마 안전하게 C#에서 Int128에 대한 InterlockedCompareExchange128 코드를 사용할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > NativeInt128 data = new NativeInt128(); Int128 comparand = 0; InterlockedExtension._InterlockedCompareExchange128((long*)data.ValuePtr, 0, 1, (long*)&comparand); </pre> <br /> 자, 여기까지 모두 준비되었으면 이제 InterlockedCompareExchange128을 이용해 Interlocked Increment 기능도 구현할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > public void InterlockedIncrement() { Int128 comparand = *(Int128*)_value; long* ptrLow; long* ptrHigh; Int128 newValue; ptrLow = (long*)&newValue; ptrHigh = ptrLow + 1; do { newValue = comparand + 1; } while (InterlockedExtension._InterlockedCompareExchange128((long*)_value, *ptrHigh, *ptrLow, (long*)&comparand) == 0); } </pre> <br /> 결국 이렇게 해서 구현하긴 했지만, 사실 이 과정을 그냥 하나의 응용 사례라고만 보시고 실제 코드는 단순히 lock을 쓰는 것이 훨씬 더 효율적입니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > object _lockValue = new object(); public void InterlockedIncrement() { <span style='color: blue; font-weight: bold'>lock (_lockValue)</span> { this.Value ++; } } </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=2122&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 참고로, System.Text.Json에서의 Int128 직렬화는 .NET 8부터 추가되었다고 합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Built-in support for Half, Int128 and UInt128 numeric types ; <a target='tab' href='https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-7/#built-in-support-for-half-int128-and-uint128-numeric-types'>https://devblogs.microsoft.com/dotnet/announcing-dotnet-8-preview-7/#built-in-support-for-half-int128-and-uint128-numeric-types</a> </pre> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1364
(왼쪽의 숫자를 입력해야 합니다.)