C# - 지연 실행이 꼭 필요한 상황이 아니라면 singleton 패턴에서 DCLP보다는 static 초기화를 권장
전에도 이에 대해 다룬 적이 있지만,
C# Singleton 인스턴스 생성
; https://www.sysnet.pe.kr/2/0/896
이번엔 간단한 성능 테스트를 해보겠습니다. DCLP(Double Checked Locking Pattern)로는 이렇게 코딩하고,
public class DCLP
{
static DCLP _instance = null;
static object _lock = new ();
public int Add(int a, int b)
{
return a + b;
}
public static DCLP Instance
{
get
{
if (_instance == null)
{
lock (_lock)
{
if (_instance == null)
{
_instance = new();
}
}
}
return _instance;
}
}
}
static 초기화 처리는 이런 식으로 할 텐데요,
public class CCtor
{
static CCtor _instance = new CCtor();
public int Add(int a, int b)
{
return a + b;
}
public static CCtor Instance
{
get
{
return _instance;
}
}
}
DCLP의 경우 코드도 길기 때문에 그냥 봐도 속도 면에서 불리해 보입니다. 게다가 static 초기화의 경우
단순한 인스턴스 반환이므로 JIT 컴파일 시 최적화로 인해 in-line 처리를 하므로 사실상 메서드 호출이 아닌, 값을 직접 사용하는 식으로 처리하기 때문에 비교 불가의 성능을 보입니다.
아래는 그
테스트 결과입니다.
int count = 1;
action(1, "touch-JIT", DCLPLoop, count);
action(1, "touch-JIT", CCtorLoop, count);
count = 500;
action(100000, "DCLPLoop", DCLPLoop, count);
action(100000, "CCtorLoop", CCtorLoop, count);
/* 출력 결과
touch-JIT : 0
touch-JIT : 0
DCLPLoop : 658
CCtorLoop : 30
*/
(첨부 파일은 이 글의 예제 코드를 포함합니다.)
의미상으로 보면, DCLP의 경우에도 if 문 처리 정도의 부하만 있을 듯하지만, 인라인 시킬 수 없다는 단점으로 인해 메서드 호출의 prologue/epilogue 코드 실행을 동반하면서 적어도 다음의 굵은 폰트 영역에 해당하는 코드는 매번 실행되는 부하를 갖게 됩니다.
public static DCLP Instance
82: {
83: get
84: {
85: if (_instance == null)
00F00CA0 55 push ebp
00F00CA1 8B EC mov ebp,esp
00F00CA3 57 push edi
00F00CA4 83 EC 20 sub esp,20h
00F00CA7 8D 7D DC lea edi,[ebp-24h]
00F00CAA B9 07 00 00 00 mov ecx,7
00F00CAF 33 C0 xor eax,eax
00F00CB1 F3 AB rep stos dword ptr es:[edi]
00F00CB3 83 3D F0 42 DE 00 00 cmp dword ptr ds:[0DE42F0h],0
00F00CBA 74 05 je ConsoleApp2.DCLP.get_Instance()+021h (0F00CC1h)
00F00CBC E8 BF F0 7C 73 call 746CFD80
00F00CC1 33 D2 xor edx,edx
00F00CC3 89 55 E0 mov dword ptr [ebp-20h],edx
00F00CC6 83 3D 7C 35 A9 03 00 cmp dword ptr ds:[3A9357Ch],0
00F00CCD 75 6E jne ConsoleApp2.DCLP.get_Instance()+09Dh (0F00D3Dh)
86: {
87: lock (_lock)
00F00CCF A1 80 35 A9 03 mov eax,dword ptr ds:[03A93580h]
00F00CD4 89 45 E0 mov dword ptr [ebp-20h],eax
00F00CD7 33 D2 xor edx,edx
00F00CD9 89 55 E4 mov dword ptr [ebp-1Ch],edx
00F00CDC 8D 55 E4 lea edx,[ebp-1Ch]
00F00CDF 8B 4D E0 mov ecx,dword ptr [ebp-20h]
00F00CE2 E8 69 77 C2 71 call System.Threading.Monitor.Enter(System.Object, Boolean ByRef) (72B28450h)
88: {
89: if (_instance == null)
00F00CE7 83 3D 7C 35 A9 03 00 cmp dword ptr ds:[3A9357Ch],0
00F00CEE 75 24 jne ConsoleApp2.DCLP.get_Instance()+074h (0F00D14h)
90: {
91: _instance = new();
00F00CF0 B9 9C 61 DE 00 mov ecx,0DE619Ch
00F00CF5 E8 FA 23 ED FF call CORINFO_HELP_NEWSFAST (0DD30F4h)
00F00CFA 89 45 DC mov dword ptr [ebp-24h],eax
00F00CFD 8B 4D DC mov ecx,dword ptr [ebp-24h]
00F00D00 FF 15 DC 61 DE 00 call dword ptr [Pointer to: CLRStub[MethodDescPrestub]@cdbb715500f004e5 (0DE61DCh)]
00F00D06 8B 45 DC mov eax,dword ptr [ebp-24h]
00F00D09 8D 15 7C 35 A9 03 lea edx,ds:[3A9357Ch]
00F00D0F E8 EC DE 42 73 call 7432EC00
92: }
93: }
00F00D14 90 nop
00F00D15 C7 45 EC 00 00 00 00 mov dword ptr [ebp-14h],offset ConsoleApp2.DCLP.get_Instance()+078h (00h)
00F00D1C C7 45 F0 FC 00 00 00 mov dword ptr [ebp-10h],0FCh
00F00D23 68 48 0D F0 00 push offset ConsoleApp2.DCLP.get_Instance()+0A8h (0F00D48h)
00F00D28 EB 00 jmp ConsoleApp2.DCLP.get_Instance()+08Ah (0F00D2Ah)
00F00D2A 0F B6 45 E4 movzx eax,byte ptr [ebp-1Ch]
00F00D2E 85 C0 test eax,eax
00F00D30 74 08 je ConsoleApp2.DCLP.get_Instance()+09Ah (0F00D3Ah)
00F00D32 8B 4D E0 mov ecx,dword ptr [ebp-20h]
00F00D35 E8 6D DD 42 73 call 7432EAA7
00F00D3A 58 pop eax
00F00D3B FF E0 jmp eax
94: }
95:
96: return _instance;
00F00D3D A1 7C 35 A9 03 mov eax,dword ptr ds:[03A9357Ch]
00F00D42 8D 65 FC lea esp,[ebp-4]
00F00D45 5F pop edi
00F00D46 5D pop ebp
00F00D47 C3 ret
이런 면에서 볼 때, '지연 처리'가 절실하게 필요한 상황이 아니라면 static 초기화를 이용한 singleton 처리가 여러모로 장점을 갖습니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]