코드 성능을 측정할 때 무엇을 써야 할지 고민되기 쉽습니다. Stopwatch는 코드 블록의 경과 시간 측정에 최적화되어 있고, PerformanceCounter는 Windows 시스템/프로세스 수준 지표 관찰에 좋습니다. 이 글에서는 두 도구의 핵심 차이, 정확도/오버헤드, 올바른 사용 시나리오와 예제를 간략히 정리합니다.
1. 핵심 요약
Stopwatch: 경과 시간(벽시계 시간) 측정에 특화된 경량 타이머입니다. 고해상도 타이머(QueryPerformanceCounter 등)를 사용하며, 마이크로벤치마크와 코드 경로 비교에 적합합니다.
PerformanceCounter: Windows 퍼포먼스 카운터를 읽어 시스템/프로세스 지표(CPU, 메모리 등)를 샘플링합니다. 코드 블록 하나의 소요 시간을 재기보다는, 실행 중인 앱의 리소스 사용 추이를 관찰할 때 적합합니다.
2. Stopwatch: 언제, 왜 쓰나
- 마이크로벤치마크, 특정 메서드의 경과 시간 비교에 사용합니다.
- Stopwatch.IsHighResolution와 Stopwatch.Frequency로 타이머 해상도를 확인할 수 있습니다.
- 장점: 경량, 정확도 높음(단일 스레드에서 모노토닉), 사용 간단. 단점: CPU 시간과 I/O 대기 시간을 구분하지 못함(벽시계 기준).
using System;
using System.Diagnostics;
using System.Threading;
class Program
{
static void Main()
{
// 타이머 해상도 확인
Console.WriteLine($"HighResolution: {Stopwatch.IsHighResolution}, Freq: {Stopwatch.Frequency} ticks/sec");
// 단순 측정
var sw = Stopwatch.StartNew();
DoWork();
sw.Stop();
Console.WriteLine($"Elapsed: {sw.Elapsed.TotalMilliseconds:F3} ms");
// 반복 측정(평균) - JIT 워밍업 후
DoWork();
int iterations = 1000;
sw.Restart();
for (int i = 0; i < iterations; i++) DoWork();
sw.Stop();
Console.WriteLine($"Avg: {sw.Elapsed.TotalMilliseconds / iterations:F6} ms/op");
}
static void DoWork()
{
// 측정 대상 코드(예시)
Thread.SpinWait(100_000);
}
}
3. PerformanceCounter: 언제, 왜 쓰나
- Windows 전용으로, OS가 제공하는 카운터를 샘플링해 CPU 사용률, 메모리, GC 등 런타임/프로세스 지표를 관찰합니다.
- 장점: 시스템/프로세스 수준 인사이트, 추세 관찰에 유리. 단점: 비교적 높은 오버헤드, 샘플 간격 필요, 인스턴스 이름 처리와 권한 이슈, Windows 전용.
- .NET 6+에서도 Windows에서만 지원되며, 컨테이너/일부 환경에서는 카운터가 없을 수 있습니다.
using System;
using System.Diagnostics;
using System.Threading;
class Program
{
static void Main()
{
if (!OperatingSystem.IsWindows())
{
Console.WriteLine("PerformanceCounter는 Windows에서만 지원됩니다.");
return;
}
string instance = GetProcessInstanceName(Process.GetCurrentProcess().Id);
using var cpu = new PerformanceCounter("Process", "% Processor Time", instance, true);
using var ws = new PerformanceCounter("Process", "Working Set - Private", instance, true);
// 첫 호출은 의미 없는 값일 수 있으므로 간격을 두고 두 번 읽습니다.
cpu.NextValue();
Thread.Sleep(500);
float cpuPct = cpu.NextValue() / Environment.ProcessorCount; // 논리 코어 수로 정규화
float privateMb = ws.NextValue() / (1024 * 1024);
Console.WriteLine($"CPU: {cpuPct:F1}% Private WS: {privateMb:F1} MB");
}
static string GetProcessInstanceName(int pid)
{
var cat = new PerformanceCounterCategory("Process");
foreach (var name in cat.GetInstanceNames())
{
using var cnt = new PerformanceCounter("Process", "ID Process", name, true);
if ((int)cnt.RawValue == pid) return name; // 같은 이름의 다중 인스턴스(#1 등) 대응
}
return Process.GetCurrentProcess().ProcessName;
}
}
4. 정확도와 오버헤드 비교
- 정확도: Stopwatch는 코드 블록의 경과 시간을 고해상도로 제공합니다. PerformanceCounter는 샘플링 기반이라 순간 측정보다 평균/추세 해석에 적합합니다.
- 오버헤드: Stopwatch는 매우 낮습니다. PerformanceCounter는 카테고리/카운터 조회 비용과 보안 컨텍스트에 따라 더 큽니다.
- 범위: Stopwatch는 코드 단위, PerformanceCounter는 프로세스/시스템 단위입니다.
5. CPU 시간과 벽시계 시간 함께 보기(대안)
코드가 실제로 CPU를 얼마나 썼는지 확인하려면 프로세스의 CPU 누적 시간을 함께 측정하면 유용합니다.
using System;
using System.Diagnostics;
class Program
{
static void Main()
{
var process = Process.GetCurrentProcess();
var cpuBefore = process.TotalProcessorTime;
var wall = Stopwatch.StartNew();
DoCpuBound();
wall.Stop();
process.Refresh();
var cpuAfter = process.TotalProcessorTime;
Console.WriteLine($"Wall: {wall.Elapsed.TotalMilliseconds:F3} ms");
Console.WriteLine($"CPU : {(cpuAfter - cpuBefore).TotalMilliseconds:F3} ms");
}
static void DoCpuBound()
{
double s = 0;
for (int i = 0; i < 10_000_000; i++) s += Math.Sqrt(i);
GC.KeepAlive(s);
}
}
6. 언제 무엇을 쓸까
- 코드 블록/알고리즘 비교: Stopwatch 권장.
- 운영 중 CPU/메모리 추세 관찰, 대시보드/경보: PerformanceCounter(Windows) 또는 EventCounters/dotnet-counters.
- 벽시계 vs CPU 사용량을 함께 보고 병목 파악: Stopwatch + Process.TotalProcessorTime 조합.
7. 흔한 함정과 팁
- DateTime.Now로 측정하지 마세요. 모노토닉 보장되지 않습니다. Stopwatch를 사용합니다.
- JIT/캐시 효과를 줄이려면 워밍업 후 반복 측정하고, Release 빌드/디버거 분리 상태에서 실행합니다.
- PerformanceCounter는 첫 읽기값 무시, 샘플 간격 확보가 필요합니다. 일부 카운터는 관리자 권한이나 카테고리 설치가 필요합니다.
- 동일한 프로세스 이름의 다중 인스턴스(#1 등) 처리에 유의하세요.
8. 추천 대안 도구
- 마이크로벤치: BenchmarkDotNet(강력한 통계/워크로드 격리).
- 런타임 지표: dotnet-counters, EventCounters, PerfView/Windows Performance Recorder.
9. 결론
Stopwatch는 가볍고 정밀한 코드 블록 경과 시간 측정에 최적입니다. PerformanceCounter는 Windows에서 시스템/프로세스 지표를 샘플링해 추세를 보는 데 유용합니다. 목적이 다른 도구이므로, 코드 경로 비교엔 Stopwatch, 운영 지표 관찰엔 PerformanceCounter(또는 현대적인 이벤트/메트릭 도구)를 선택하는 것이 좋습니다.
'C#' 카테고리의 다른 글
| C# 대리자 체인 관리와 예외 처리 (0) | 2026.06.19 |
|---|---|
| C# 추상 팩토리(Abstract Factory) 패턴 구현 (0) | 2026.06.18 |
| C# Custom Binding 구현으로 네트워크 통신 확장 (0) | 2026.06.17 |
| C# 순환 참조 방지 패턴 설계 (0) | 2026.06.17 |
| C# Garbage Collector 동작 원리와 세대별 수집 (0) | 2026.06.16 |