17
17
18
18
#include "pydtrace.h"
19
19
20
- // Platform-specific includes for get_current_rss ().
20
+ // Platform-specific includes for get_process_mem_usage ().
21
21
#ifdef _WIN32
22
22
#include <windows.h>
23
23
#include <psapi.h> // For GetProcessMemoryInfo
24
24
#elif defined(__linux__ )
25
25
#include <unistd.h> // For sysconf, getpid
26
26
#elif defined(__APPLE__ )
27
27
#include <mach/mach.h>
28
+ #include <mach/task.h> // Required for TASK_VM_INFO
28
29
#include <unistd.h> // For sysconf, getpid
29
30
#elif defined(__FreeBSD__ )
30
31
#include <sys/types.h>
@@ -1901,13 +1902,14 @@ cleanup_worklist(struct worklist *worklist)
1901
1902
}
1902
1903
}
1903
1904
1904
- // Return the current resident set size ( RSS) of the process, in units of KB.
1905
- // Returns -1 if this operation is not supported or on failure.
1905
+ // Return the memory usage (typically RSS + swap ) of the process, in units of
1906
+ // KB. Returns -1 if this operation is not supported or on failure.
1906
1907
static Py_ssize_t
1907
- get_current_rss (void )
1908
+ get_process_mem_usage (void )
1908
1909
{
1909
1910
#ifdef _WIN32
1910
1911
// Windows implementation using GetProcessMemoryInfo
1912
+ // Returns WorkingSetSize + PagefileUsage
1911
1913
PROCESS_MEMORY_COUNTERS pmc ;
1912
1914
HANDLE hProcess = GetCurrentProcess ();
1913
1915
if (NULL == hProcess ) {
@@ -1917,55 +1919,58 @@ get_current_rss(void)
1917
1919
1918
1920
// GetProcessMemoryInfo returns non-zero on success
1919
1921
if (GetProcessMemoryInfo (hProcess , & pmc , sizeof (pmc ))) {
1920
- // pmc.WorkingSetSize is in bytes. Convert to KB.
1921
- return (Py_ssize_t )(pmc .WorkingSetSize / 1024 );
1922
+ // Values are in bytes, convert to KB.
1923
+ return (Py_ssize_t )(( pmc .WorkingSetSize + pmc . PagefileUsage ) / 1024 );
1922
1924
}
1923
1925
else {
1924
1926
return -1 ;
1925
1927
}
1926
1928
1927
1929
#elif __linux__
1928
- // Linux implementation using /proc/self/statm
1929
- long page_size_bytes = sysconf (_SC_PAGE_SIZE );
1930
- if (page_size_bytes <= 0 ) {
1931
- return -1 ;
1932
- }
1933
-
1934
- FILE * fp = fopen ("/proc/self/statm" , "r" );
1930
+ // Linux, use smaps_rollup (Kernel >= 4.4) for RSS + Swap
1931
+ FILE * fp = fopen ("/proc/self/smaps_rollup" , "r" );
1935
1932
if (fp == NULL ) {
1936
1933
return -1 ;
1937
1934
}
1938
1935
1939
- // Second number is resident size in pages
1940
- long rss_pages ;
1941
- if (fscanf (fp , "%*d %ld" , & rss_pages ) != 1 ) {
1942
- fclose (fp );
1943
- return -1 ;
1936
+ char line_buffer [256 ];
1937
+ long long rss_kb = -1 ;
1938
+ long long swap_kb = -1 ;
1939
+
1940
+ while (fgets (line_buffer , sizeof (line_buffer ), fp ) != NULL ) {
1941
+ if (rss_kb == -1 && strncmp (line_buffer , "Rss:" , 4 ) == 0 ) {
1942
+ sscanf (line_buffer + 4 , "%lld" , & rss_kb );
1943
+ }
1944
+ else if (swap_kb == -1 && strncmp (line_buffer , "Swap:" , 5 ) == 0 ) {
1945
+ sscanf (line_buffer + 5 , "%lld" , & swap_kb );
1946
+ }
1947
+ if (rss_kb != -1 && swap_kb != -1 ) {
1948
+ break ; // Found both
1949
+ }
1944
1950
}
1945
1951
fclose (fp );
1946
1952
1947
- // Sanity check
1948
- if (rss_pages < 0 || rss_pages > 1000000000 ) {
1949
- return -1 ;
1953
+ if (rss_kb != -1 && swap_kb != -1 ) {
1954
+ return (Py_ssize_t )(rss_kb + swap_kb );
1950
1955
}
1951
-
1952
- // Convert unit to KB
1953
- return (Py_ssize_t )rss_pages * (page_size_bytes / 1024 );
1956
+ return -1 ;
1954
1957
1955
1958
#elif defined(__APPLE__ )
1956
1959
// --- MacOS (Darwin) ---
1957
- mach_msg_type_number_t count = MACH_TASK_BASIC_INFO_COUNT ;
1958
- mach_task_basic_info_data_t info ;
1960
+ // Returns phys_footprint (RAM + compressed memory)
1961
+ task_vm_info_data_t vm_info ;
1962
+ mach_msg_type_number_t count = TASK_VM_INFO_COUNT ;
1959
1963
kern_return_t kerr ;
1960
1964
1961
- kerr = task_info (mach_task_self (), MACH_TASK_BASIC_INFO , (task_info_t )& info , & count );
1965
+ kerr = task_info (mach_task_self (), TASK_VM_INFO , (task_info_t )& vm_info , & count );
1962
1966
if (kerr != KERN_SUCCESS ) {
1963
1967
return -1 ;
1964
1968
}
1965
- // info.resident_size is in bytes. Convert to KB.
1966
- return (Py_ssize_t )(info . resident_size / 1024 );
1969
+ // phys_footprint is in bytes. Convert to KB.
1970
+ return (Py_ssize_t )(vm_info . phys_footprint / 1024 );
1967
1971
1968
1972
#elif defined(__FreeBSD__ )
1973
+ // NOTE: Returns RSS only. Per-process swap usage isn't readily available
1969
1974
long page_size_kb = sysconf (_SC_PAGESIZE ) / 1024 ;
1970
1975
if (page_size_kb <= 0 ) {
1971
1976
return -1 ;
@@ -2004,6 +2009,7 @@ get_current_rss(void)
2004
2009
return rss_kb ;
2005
2010
2006
2011
#elif defined(__OpenBSD__ )
2012
+ // NOTE: Returns RSS only. Per-process swap usage isn't readily available
2007
2013
long page_size_kb = sysconf (_SC_PAGESIZE ) / 1024 ;
2008
2014
if (page_size_kb <= 0 ) {
2009
2015
return -1 ;
@@ -2039,37 +2045,39 @@ get_current_rss(void)
2039
2045
}
2040
2046
2041
2047
static bool
2042
- gc_should_collect_rss (GCState * gcstate )
2048
+ gc_should_collect_mem_usage (GCState * gcstate )
2043
2049
{
2044
- Py_ssize_t rss = get_current_rss ();
2045
- if (rss < 0 ) {
2046
- // Reading RSS is not support or failed.
2050
+ Py_ssize_t mem = get_process_mem_usage ();
2051
+ if (mem < 0 ) {
2052
+ // Reading process memory usage is not support or failed.
2047
2053
return true;
2048
2054
}
2049
2055
int threshold = gcstate -> young .threshold ;
2050
2056
Py_ssize_t deferred = _Py_atomic_load_ssize_relaxed (& gcstate -> deferred_count );
2051
2057
if (deferred > threshold * 40 ) {
2052
- // Too many new container objects since last GC, even though RSS
2058
+ // Too many new container objects since last GC, even though memory use
2053
2059
// might not have increased much. This is intended to avoid resource
2054
2060
// exhaustion if some objects consume resources but don't result in a
2055
- // RSS increase. We use 40x as the factor here because older versions
2056
- // of Python would do full collections after roughly every 70,000 new
2057
- // container objects.
2061
+ // memory usage increase. We use 40x as the factor here because older
2062
+ // versions of Python would do full collections after roughly every
2063
+ // 70,000 new container objects.
2058
2064
return true;
2059
2065
}
2060
- Py_ssize_t last_rss = gcstate -> last_rss ;
2061
- Py_ssize_t rss_threshold = Py_MAX (last_rss / 10 , 128 );
2062
- if ((rss - last_rss ) > rss_threshold ) {
2063
- // The RSS has increased too much, do a collection.
2066
+ Py_ssize_t last_mem = gcstate -> last_mem ;
2067
+ Py_ssize_t mem_threshold = Py_MAX (last_mem / 10 , 128 );
2068
+ if ((mem - last_mem ) > mem_threshold ) {
2069
+ // The process memory usage has increased too much, do a collection.
2064
2070
return true;
2065
2071
}
2066
2072
else {
2067
- // The RSS has not increased enough, defer the collection and clear
2068
- // the young object count so we don't check RSS again on the next call
2069
- // to gc_should_collect().
2073
+ // The memory usage has not increased enough, defer the collection and
2074
+ // clear the young object count so we don't check memory usage again
2075
+ // on the next call to gc_should_collect().
2070
2076
PyMutex_Lock (& gcstate -> mutex );
2071
- gcstate -> deferred_count += gcstate -> young .count ;
2072
- gcstate -> young .count = 0 ;
2077
+ _Py_atomic_store_ssize_relaxed (& gcstate -> deferred_count ,
2078
+ gcstate -> deferred_count +
2079
+ gcstate -> young .count );
2080
+ _Py_atomic_store_int (& gcstate -> young .count , 0 );
2073
2081
PyMutex_Unlock (& gcstate -> mutex );
2074
2082
return false;
2075
2083
}
@@ -2094,7 +2102,7 @@ gc_should_collect(GCState *gcstate)
2094
2102
// objects.
2095
2103
return false;
2096
2104
}
2097
- return gc_should_collect_rss (gcstate );
2105
+ return gc_should_collect_mem_usage (gcstate );
2098
2106
}
2099
2107
2100
2108
static void
@@ -2237,8 +2245,9 @@ gc_collect_internal(PyInterpreterState *interp, struct collection_state *state,
2237
2245
// to be freed.
2238
2246
delete_garbage (state );
2239
2247
2240
- // Store the current RSS, possibly smaller now that we deleted garbage.
2241
- state -> gcstate -> last_rss = get_current_rss ();
2248
+ // Store the current memory usage, can be smaller now if breaking cycles
2249
+ // freed some memory.
2250
+ state -> gcstate -> last_mem = get_process_mem_usage ();
2242
2251
2243
2252
// Append objects with legacy finalizers to the "gc.garbage" list.
2244
2253
handle_legacy_finalizers (state );
0 commit comments