Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일
(연관된 글이 3개 있습니다.)
(시리즈 글이 12개 있습니다.)
Windows: 103. 작업 관리자에서의 "Commit size"가 가리키는 메모리의 의미
; https://www.sysnet.pe.kr/2/0/1850

.NET Framework: 492. .NET CLR Memory 성능 카운터의 의미
; https://www.sysnet.pe.kr/2/0/1852

.NET Framework: 626. Commit 메모리가 낮은 상황에서도 메모리 부족(Out-of-memory) 예외 발생
; https://www.sysnet.pe.kr/2/0/11110

VC++: 107. VirtualAlloc, HeapAlloc, GlobalAlloc, LocalAlloc, malloc, new의 차이점
; https://www.sysnet.pe.kr/2/0/11152

Windows: 136. Memory-mapped File은 Private Bytes 크기에 포함될까요?
; https://www.sysnet.pe.kr/2/0/11159

.NET Framework: 845. C# - 윈도우 작업 관리자와 리소스 모니터의 메모리 값을 구하는 방법
; https://www.sysnet.pe.kr/2/0/11950

Windows: 211. Windows - (commit이 아닌) reserved 메모리 사용량 확인 방법
; https://www.sysnet.pe.kr/2/0/13133

.NET Framework: 2073. C# - VMMap처럼 스택 메모리의 reserve/guard/commit 상태 출력
; https://www.sysnet.pe.kr/2/0/13174

.NET Framework: 2074. C# - 스택 메모리에 대한 여유 공간 확인하는 방법
; https://www.sysnet.pe.kr/2/0/13180

Linux: 57. C# - 리눅스 프로세스 메모리 정보
; https://www.sysnet.pe.kr/2/0/13221

닷넷: 2322. C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(성능 카운터, WMI)
; https://www.sysnet.pe.kr/2/0/13889

닷넷: 2323. C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(Win32 API)
; https://www.sysnet.pe.kr/2/0/13890




C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(성능 카운터, WMI)

Working Set 메모리(리눅스의 경우 Resident Set Size)는 예전에도 한번 설명했습니다.

작업 관리자에서의 "Commit size"가 가리키는 메모리의 의미
; https://www.sysnet.pe.kr/2/0/1850#working_set

즉, 해당 프로세스로 인해 소비 중인 물리 메모리의 양을 의미하는데요, 그에 더해 Private Working Set(이하, PWS)이라고 하면 다른 프로세스와 공유되지 않는 "Working Set"을 의미합니다.

관련해서 아래와 같은 질문이 하나 있는데요,

PerformanceCounter가 느린 이유?
; https://forum.dotnetdev.kr/t/performancecounter/12592

그 값을 구하기 위해 PerformanceCounter를 사용했지만 너무 느리다고 합니다. 실제로 위에 실린 코드를 좀 정리해서 그대로 실행해 보면,

using System.Diagnostics;

[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows")]

public static class Program
{
    static Dictionary<int, string> processId2MemoryProcessName = new Dictionary<int, string>();

    static void Main(string[] args)
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();
        CheckUserMemory();
        sw.Stop();
        Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");

        Console.ReadLine();
    }

    static void CheckUserMemory()
    {
        long totalMemory = 0L;

        Process[] processes = Process.GetProcesses();
        foreach (Process p in processes)
        {
            totalMemory += CurrentMemoryUsage(p);
        }
    }
    
    static long CurrentMemoryUsage(Process proc)
    {
        long currentMemoryUsage = 0L;
        var nameToUseForMemory = GetNameToUseForMemory(proc);

        using (var procPerfCounter = new PerformanceCounter("Process", "Working Set - Private", nameToUseForMemory))
        {
            currentMemoryUsage = procPerfCounter.RawValue;
        }

        return currentMemoryUsage;
    }

    static string GetNameToUseForMemory(Process proc)
    {
        if (processId2MemoryProcessName.ContainsKey(proc.Id))
            return processId2MemoryProcessName[proc.Id];

        var nameToUseForMemory = string.Empty;
        var category = new PerformanceCounterCategory("Process");
        var instanceNames = category.GetInstanceNames().Where(x => x.Contains(proc.ProcessName));

        foreach (var instancename in instanceNames)
        {
            using (var procPerfCounter = new PerformanceCounter("Process", "ID Process", instancename, true))
            {
                if (procPerfCounter.RawValue != proc.Id) continue;

                nameToUseForMemory = instancename;
                break;
            }
        }

        if (!processId2MemoryProcessName.ContainsKey(proc.Id))
        {
            processId2MemoryProcessName.Add(proc.Id, nameToUseForMemory);
        }

        return nameToUseForMemory;
    }
}

제 컴퓨터에서 (프로세스 수는 470개 정도였는데) 처음 한 번이 262초 정도 걸리고 이후 반복 시키면 10 ~ 13초씩 걸렸습니다.

같은 이름의 프로세스가 많을수록 CurrentMemoryUsage 메서드의 실행 속도가 늘었는데요, 가령 svchost.exe에 대해서는 290 ~ 2598ms 정도로 걸리기도 하고 단일 프로세스라면 70 ~ 100ms 정도로 걸렸습니다.

결국, 이 정도 실행 속도면 도저히 쓸 수 없는 상황입니다.




실행 속도는 3개 지점에서 느려지는데요, 모두 성능 카운터를 사용하는 부분입니다. 가령 단일 프로세스만 있는 smss.exe의 경우,

1) PerformanceCounterCategory.GetInstanceNames: 25ms
2) PerformanceCounter("Process", "ID Process", instancename, true)로 찾는 코드: 25ms
3) PerformanceCounter("Process", "Working Set - Private", ...) 코드: 24ms

이런 식으로 걸렸고 svchost.exe 중의 하나는 이렇게 걸렸습니다.

1) 27ms
2) 1250ms
3) 25ms

사실 위에서 1번과 2번 단계는 성능 카운터의 "InstanceName"을 결정하기 위해 필요한 단계인데, 이 부분의 필요성은 전에 언급한 적이 있습니다.

PerformanceCounter의 InstanceName 지정 시 주의 사항
; https://www.sysnet.pe.kr/2/0/10898

그럼 저 단계를 없애면 속도가 좀 빨라질까요? 이를 위해 InstanceName을 구하지 않아도 결정할 수 있는 "Process V2" 성능 카운터를 사용할 수 있을 것입니다.

이것을 이용하면 이제 소스코드는 다음과 같이 간단해집니다.

using System.Diagnostics;

[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows")]

public static class Program
{
    static void Main(string[] args)
    {
        CheckUserMemory();
        Console.ReadLine();
    }

    static void CheckUserMemory()
    {
        long totalMemory = 0L;

        Process[] processes = Process.GetProcesses();
        foreach (Process p in processes)
        {
            totalMemory += CurrentMemoryUsage(p);
        }
    }
    
    static long CurrentMemoryUsage(Process proc)
    {
        long currentMemoryUsage = 0L;
        var nameToUseForMemory = $"{proc.ProcessName}:{proc.Id}";

        try
        {
            using (var procPerfCounter = new PerformanceCounter("Process V2", "Working Set - Private", nameToUseForMemory))
            {
                currentMemoryUsage = procPerfCounter.RawValue;
            }
        } catch { }

        return currentMemoryUsage;
    }
}

"Process"와 달리 "Process V2"는 성능 카운터를 구하는 속도도 빨라졌는데요, 전에는 25ms 정도 걸리던 것이 이제는 그 부분이 11ms로 줄었습니다. 하지만, 프로세스 수는 470개 정도인데다 최초 성능 카운터를 구하는 시점에 지연되는 시간까지 더해져 여전히 총 수행 시간이 7439ms 정도까지 걸렸습니다. (두 번째부터는 5.5초 정도에 끝납니다.)




일단, 전체 프로세스를 대상으로 일일이 PWS를 구하면서 총 사용량을 구하는 것은 저렇게 오래 걸리지만, 이건 개별 프로세스에 대한 정보가 필요한 경우 그런 것이고 만약 전체 사용량만 필요하다면 좀 더 간단하게 끝낼 수 있습니다.

왜냐하면, 성능 카운터는 "_total"이라는 항목을 제공하기 때문인데요, 그래서 다음과 같이 간단하게 구할 수 있고,

using System.Diagnostics;

[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows")]

public static class Program
{
    static void Main(string[] args)
    {
        long result = CurrentMemoryUsage();
        Console.WriteLine($"{result}");
        Console.ReadLine();
    }

    static long CurrentMemoryUsage()
    {
        long currentMemoryUsage = 0L;

        try
        {
            using (var procPerfCounter = new PerformanceCounter("Process V2", "Working Set - Private", "_total"))
            {
                currentMemoryUsage = procPerfCounter.RawValue;
            }

        } catch { }

        return currentMemoryUsage;
    }
}

실행 속도는 최초 한 번만 (성능 카운터 관련 초기화로 인해) 1,654ms 정도가 걸릴 뿐, 이후로는 11ms 정도만 소요되므로 충분히 현실적인 수준으로 빨라집니다.




그런데 만약 개별 프로세스에 대한 사용량을 구하고 싶다면 어떻게 해야 할까요? 일단, 위의 결과로 봤을 때 성능 카운터는 개별 프로세스당 11ms가 걸리므로 답이 아님을 알 수 있습니다.

그렇다면 이제 Win32 API로 눈을 돌려야 하는데요, 아래의 공식 문서에 보면,

Memory Performance Information
; https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/aa965225(v=vs.85)

"Working Set - Private"에 대해서는 PROCESS_MEMORY_COUNTERS_EX 구조체에서 값을 담고 있지 않습니다. (심지어 성능 카운터에서는 그 값을 Windows XP/2003 시절에는 아예 제공하지도 않았다고 합니다.)

(덧글 참고하세요.) 따라서 Win32 API는 해결책에서 제외해야 합니다. ^^;

혹시 그럼 다른 방법이 있을까요? 검색해 보면, PowerShell에 기록이 있습니다.

How to get memory ( private working set ) of a process in powershell?
; https://stackoverflow.com/questions/50735404/how-to-get-memory-private-working-set-of-a-process-in-powershell

관련해서 명령어를 실행하면,

// task.ps1 (C#에서 실행하고 싶다면? C# - PowerShell과 연동하는 방법)

$StopWatch = new-object system.diagnostics.stopwatch
$StopWatch.Start()

(Get-Counter "\Process(*)\Working Set - Private").CounterSamples

$StopWatch.Stop()
Write-Host $StopWatch.Elapsed // 출력 결과: 00:00:01.2386532

상당히 빠르게 결과를 얻을 수 있는데요, 속도 측정을 해보면 1238ms 내에 모든 프로세스의 PWS 값을 구할 수 있습니다. 7초 넘게 걸리던 것이 1.2초 정도로 줄어들었다면 어느 정도는 만족할 수 있는 결과입니다.

재미있는 건, 이것 역시 성능 카운터이므로 "_total"로도 구할 수 있는데요,

// PowerShell

(Get-Counter "\Process(_total)\Working Set - Private").CounterSamples

전체를 구했을 때는 1.2초 정도 걸리던 것이 "_total" 단일 값에 대해서도 1,068ms가 소요됐습니다. 아마도 이것 역시 성능 카운터의 최초 호출에 대한 초기화로 인해 1초 정도가 걸린 것이고 나머지 값은 상당히 빠른 속도로 구한 것이 아닌가 예상됩니다.




어쨌든 중요한 것은, PowerShell의 실행 속도를 통해 성능 카운터가 아닌 다른 방법으로 개별 PWS 값을 빠르게 구할 수 있는 "어떤 방법"이 있다는 것을 확인해 볼 수 있었습니다.

자, 그럼 좀 더 검색해 볼까요? ^^

Calculating Private Working set memory from WMI class methods
; https://stackoverflow.com/questions/14773457/calculating-private-working-set-memory-from-wmi-class-methods

Win32_PerfRawData_PerfProc_Process class (Article 02/20/2014)
; https://learn.microsoft.com/en-us/previous-versions//aa394323(v=vs.85)

아하~~~ WMI로도 메모리 관련 정보를 구할 수 있다고 하는데요, Win32_PerfRawData_PerfProc_Process 클래스에 "Working Set - Private" 값이 있다고 합니다.

// wbemtest.exe로 구한 MOF 정보

[dynamic: ToInstance, provider("WmiPerfInst"), GenericPerfCtr: ToInstance, HiPerf: ToInstance, Locale(1033): ToInstance, RegistryKey("PerfProc"): ToInstance, DisplayName("Process"): ToInstance, DisplayName009("Process"): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DetailLevel(100): ToInstance]
class Win32_PerfRawData_PerfProc_Process : Win32_PerfRawData
{
	[key] string Name;
	[DisplayName("% Privileged Time"): ToInstance, DisplayName009("% Privileged Time"): ToInstance, CounterType(542180608): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 PercentPrivilegedTime;
	[DisplayName("% Processor Time"): ToInstance, DisplayName009("% Processor Time"): ToInstance, CounterType(542180608): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance, PerfDefault: ToInstance] uint64 PercentProcessorTime;
	[DisplayName("% User Time"): ToInstance, DisplayName009("% User Time"): ToInstance, CounterType(542180608): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 PercentUserTime;
	[DisplayName("Creating Process ID"): ToInstance, DisplayName009("Creating Process ID"): ToInstance, CounterType(65536): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-1): ToInstance, DetailLevel(100): ToInstance] uint32 CreatingProcessID;
	[DisplayName("Elapsed Time"): ToInstance, DisplayName009("Elapsed Time"): ToInstance, CounterType(807666944): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-4): ToInstance, DetailLevel(100): ToInstance] uint64 ElapsedTime;
	[DisplayName("Handle Count"): ToInstance, DisplayName009("Handle Count"): ToInstance, CounterType(65536): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint32 HandleCount;
	[DisplayName("ID Process"): ToInstance, DisplayName009("ID Process"): ToInstance, CounterType(65536): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-1): ToInstance, DetailLevel(100): ToInstance] uint32 IDProcess;
	[DisplayName("IO Data Bytes/sec"): ToInstance, DisplayName009("IO Data Bytes/sec"): ToInstance, CounterType(272696576): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 IODataBytesPersec;
	[DisplayName("IO Data Operations/sec"): ToInstance, DisplayName009("IO Data Operations/sec"): ToInstance, CounterType(272696576): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 IODataOperationsPersec;
	[DisplayName("IO Other Bytes/sec"): ToInstance, DisplayName009("IO Other Bytes/sec"): ToInstance, CounterType(272696576): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 IOOtherBytesPersec;
	[DisplayName("IO Other Operations/sec"): ToInstance, DisplayName009("IO Other Operations/sec"): ToInstance, CounterType(272696576): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 IOOtherOperationsPersec;
	[DisplayName("IO Read Bytes/sec"): ToInstance, DisplayName009("IO Read Bytes/sec"): ToInstance, CounterType(272696576): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 IOReadBytesPersec;
	[DisplayName("IO Read Operations/sec"): ToInstance, DisplayName009("IO Read Operations/sec"): ToInstance, CounterType(272696576): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 IOReadOperationsPersec;
	[DisplayName("IO Write Bytes/sec"): ToInstance, DisplayName009("IO Write Bytes/sec"): ToInstance, CounterType(272696576): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 IOWriteBytesPersec;
	[DisplayName("IO Write Operations/sec"): ToInstance, DisplayName009("IO Write Operations/sec"): ToInstance, CounterType(272696576): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint64 IOWriteOperationsPersec;
	[DisplayName("Page Faults/sec"): ToInstance, DisplayName009("Page Faults/sec"): ToInstance, CounterType(272696320): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-1): ToInstance, DetailLevel(100): ToInstance] uint32 PageFaultsPersec;
	[DisplayName("Page File Bytes"): ToInstance, DisplayName009("Page File Bytes"): ToInstance, CounterType(65792): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-6): ToInstance, DetailLevel(100): ToInstance] uint64 PageFileBytes;
	[DisplayName("Page File Bytes Peak"): ToInstance, DisplayName009("Page File Bytes Peak"): ToInstance, CounterType(65792): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-6): ToInstance, DetailLevel(100): ToInstance] uint64 PageFileBytesPeak;
	[DisplayName("Pool Nonpaged Bytes"): ToInstance, DisplayName009("Pool Nonpaged Bytes"): ToInstance, CounterType(65536): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-5): ToInstance, DetailLevel(100): ToInstance] uint32 PoolNonpagedBytes;
	[DisplayName("Pool Paged Bytes"): ToInstance, DisplayName009("Pool Paged Bytes"): ToInstance, CounterType(65536): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-5): ToInstance, DetailLevel(100): ToInstance] uint32 PoolPagedBytes;
	[DisplayName("Priority Base"): ToInstance, DisplayName009("Priority Base"): ToInstance, CounterType(65536): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint32 PriorityBase;
	[DisplayName("Private Bytes"): ToInstance, DisplayName009("Private Bytes"): ToInstance, CounterType(65792): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-5): ToInstance, DetailLevel(100): ToInstance] uint64 PrivateBytes;
	[DisplayName("Thread Count"): ToInstance, DisplayName009("Thread Count"): ToInstance, CounterType(65536): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(0): ToInstance, DetailLevel(100): ToInstance] uint32 ThreadCount;
	[DisplayName("Virtual Bytes"): ToInstance, DisplayName009("Virtual Bytes"): ToInstance, CounterType(65792): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-6): ToInstance, DetailLevel(100): ToInstance] uint64 VirtualBytes;
	[DisplayName("Virtual Bytes Peak"): ToInstance, DisplayName009("Virtual Bytes Peak"): ToInstance, CounterType(65792): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-6): ToInstance, DetailLevel(100): ToInstance] uint64 VirtualBytesPeak;
	[DisplayName("Working Set"): ToInstance, DisplayName009("Working Set"): ToInstance, CounterType(65792): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-5): ToInstance, DetailLevel(100): ToInstance] uint64 WorkingSet;
	[DisplayName("Working Set - Private"): ToInstance, DisplayName009("Working Set - Private"): ToInstance, CounterType(65792): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-5): ToInstance, DetailLevel(100): ToInstance] uint64 WorkingSetPrivate;
	[DisplayName("Working Set Peak"): ToInstance, DisplayName009("Working Set Peak"): ToInstance, CounterType(65792): ToInstance, PerfIndex(0): ToInstance, HelpIndex(0): ToInstance, DefaultScale(-5): ToInstance, DetailLevel(100): ToInstance] uint64 WorkingSetPeak;
};

이를 기반으로 다음과 같이 C# 코드를 작성하고,

using System.Diagnostics;
using System.Management;

[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows")]

namespace ConsoleApp2;

internal class Program
{
    static void Main(string[] args)
    {
        while (true)
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            Console.Write($"{GetPrivateWorkingSet()}: ");
            sw.Stop();
            Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
        }
    }

    private static ulong GetPrivateWorkingSet()
    {
        ulong totalSize = 0;

        using (ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_PerfRawData_PerfProc_Process"))
        using (var data = searcher.Get())
        using (var e = data.GetEnumerator())
        {
            while (e.MoveNext())
            {
                var mo = e.Current;
                totalSize += (ulong)mo.Properties["WorkingSetPrivate"].Value;
            }
        }

        return totalSize;
    }
}

/*
// wmic로 명령행에서 실습
c:\temp> wmic /NAMESPACE:\\root\CIMV2 path Win32_PerfRawData_PerfProc_Process get WorkingSetPrivate

// WMI 구조체 검색
Get-WmiObject -Query "SELECT * FROM meta_class WHERE __class = 'Win32_PerfRawData_PerfProc_Process'" 
*/

실행해 보면, 전체 프로세스를 열거하는데 120 ~ 180ms 정도 걸리는데요, 확실히 성능 카운터를 사용하는 것보다는 빨라졌고, 게다가 이번에는 최초 호출 시의 지연 시간도 발생하지 않았습니다.

뭐 이 정도면, 그런대로 만족할 만한 수준이 되었으니 마무리하겠습니다. ^^

(첨부 파일은 이 글의 예제 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 2/24/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2025-02-21 12시16분
참고로, Windows 10부터 _PROCESS_MEMORY_COUNTERS_EX2 구조체를 제공하는데 여기에는 PrivateWorkingSetSize가 있습니다. 따라서 운영체제 조건만 만족한다면 Win32 API로 구하는 것이 가장 간편한 방법입니다.

PROCESS_MEMORY_COUNTERS_EX2 structure (psapi.h)
; https://learn.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-process_memory_counters_ex2

---------------------------------------

C# - 프로세스 메모리 중 Private Working Set 크기를 구하는 방법(Win32 API)
; https://www.sysnet.pe.kr/2/0/13890
정성태

... 46  47  48  [49]  50  51  52  53  54  55  56  57  58  59  60  ...
NoWriterDateCnt.TitleFile(s)
12710정성태7/15/202118734개발 환경 구성: 577. MQTT - emqx.io 서비스 소개
12709정성태7/14/202114266Linux: 42. 실행 중인 docker 컨테이너에 대한 구동 시점의 docker run 명령어를 확인하는 방법
12708정성태7/14/202118453Linux: 41. 리눅스 환경에서 디스크 용량 부족 시 원인 분석 방법
12707정성태7/14/202185714오류 유형: 734. MySQL - Authentication method 'caching_sha2_password' not supported by any of the available plugins.
12706정성태7/14/202116895.NET Framework: 1076. C# - AsyncLocal 기능을 CallContext만으로 구현하는 방법 [2]파일 다운로드1
12705정성태7/13/202117267VS.NET IDE: 168. x64 DLL 프로젝트의 컨트롤이 Visual Studio의 Designer에서 보이지 않는 문제 - 두 번째 이야기
12704정성태7/12/202116066개발 환경 구성: 576. Azure VM의 서비스를 Azure Web App Service에서만 접근하도록 NSG 설정을 제한하는 방법
12703정성태7/11/202121452개발 환경 구성: 575. Azure VM에 (ICMP) ping을 허용하는 방법
12702정성태7/11/202117155오류 유형: 733. TaskScheduler에 등록된 wacs.exe의 Let's Encrypt 인증서 업데이트 문제
12701정성태7/9/202116699.NET Framework: 1075. C# - ThreadPool의 스레드는 반환 시 ThreadStatic과 AsyncLocal 값이 초기화 될까요?파일 다운로드1
12700정성태7/8/202117141.NET Framework: 1074. RuntimeType의 메모리 누수? [1]
12699정성태7/8/202115680VS.NET IDE: 167. Visual Studio 디버깅 중 GC Heap 상태를 보여주는 "Show Diagnostic Tools" 메뉴 사용법
12698정성태7/7/202119926오류 유형: 732. Windows 11 업데이트 시 3% 또는 0%에서 다운로드가 멈춘 경우
12697정성태7/7/202114997개발 환경 구성: 574. Windows 11 (Insider Preview) 설치하는 방법
12696정성태7/6/202115951VC++: 146. 운영체제의 스레드 문맥 교환(Context Switch)을 유사하게 구현하는 방법파일 다운로드2
12695정성태7/3/202115977VC++: 145. C 언어의 setjmp/longjmp 기능을 Thread Context를 이용해 유사하게 구현하는 방법파일 다운로드1
12694정성태7/2/202117910Java: 24. Azure - Spring Boot 앱을 Java SE(Embedded Web Server)로 호스팅 시 로그 파일 남기는 방법 [1]
12693정성태6/30/202114862오류 유형: 731. Azure Web App Site Extension - Failed to install web app extension [...]. {1}
12692정성태6/30/202115332디버깅 기술: 180. Azure - Web App의 비정상 종료 시 남겨지는 로그 확인
12691정성태6/30/202115601개발 환경 구성: 573. 테스트 용도이지만 테스트에 적합하지 않은 Azure D1 공유(shared) 요금제
12690정성태6/28/202116621Java: 23. Azure - 자바(Java)로 만드는 Web App Service - Tomcat 호스팅
12689정성태6/25/202118314오류 유형: 730. Windows Forms 디자이너 - The class Form1 can be designed, but is not the first class in the file. [1]
12688정성태6/24/202117639.NET Framework: 1073. C# - JSON 역/직렬화 시 리플렉션 손실을 없애는 JsonSrcGen [2]파일 다운로드1
12687정성태6/22/202114958오류 유형: 729. Invalid data: Invalid artifact, java se app service only supports .jar artifact
12686정성태6/21/202116931Java: 22. Azure - 자바(Java)로 만드는 Web App Service - Java SE (Embedded Web Server) 호스팅
12685정성태6/21/202118122Java: 21. Azure Web App Service에 배포된 Java 프로세스의 메모리 및 힙(Heap) 덤프 뜨는 방법
... 46  47  48  [49]  50  51  52  53  54  55  56  57  58  59  60  ...