Skip to content

Commit 4703b87

Browse files
committed
[GR-25241] Try harder to free native memory.
PullRequest: graalpython/1165
2 parents 984c41e + ab879db commit 4703b87

File tree

4 files changed

+86
-74
lines changed

4 files changed

+86
-74
lines changed

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PythonCextBuiltins.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3366,13 +3366,13 @@ abstract static class PyTruffleTraceMallocTrack extends PythonBuiltinNode {
33663366

33673367
@Specialization(guards = {"domain == cachedDomain"}, limit = "3")
33683368
int doCachedDomainIdx(VirtualFrame frame, @SuppressWarnings("unused") long domain, Object pointerObject, long size,
3369+
@CachedContext(PythonLanguage.class) PythonContext context,
33693370
@Cached("domain") @SuppressWarnings("unused") long cachedDomain,
3370-
@Cached("lookupDomain(domain)") int cachedDomainIdx,
3371-
@Cached BranchProfile profile) {
3371+
@Cached("lookupDomain(domain)") int cachedDomainIdx) {
33723372

33733373
CApiContext cApiContext = getContext().getCApiContext();
33743374
cApiContext.getTraceMallocDomain(cachedDomainIdx).track(pointerObject, size);
3375-
cApiContext.increaseMemoryPressure(frame, size, profile);
3375+
cApiContext.increaseMemoryPressure(frame, context, this, size);
33763376
if (LOGGER.isLoggable(Level.FINE)) {
33773377
LOGGER.fine(() -> String.format("Tracking memory (size: %d): %s", size, CApiContext.asHex(pointerObject)));
33783378
}
@@ -3381,8 +3381,8 @@ int doCachedDomainIdx(VirtualFrame frame, @SuppressWarnings("unused") long domai
33813381

33823382
@Specialization(replaces = "doCachedDomainIdx")
33833383
int doGeneric(VirtualFrame frame, int domain, Object pointerObject, long size,
3384-
@Cached BranchProfile profile) {
3385-
return doCachedDomainIdx(frame, domain, pointerObject, size, domain, lookupDomain(domain), profile);
3384+
@CachedContext(PythonLanguage.class) PythonContext context) {
3385+
return doCachedDomainIdx(frame, domain, pointerObject, size, context, domain, lookupDomain(domain));
33863386
}
33873387

33883388
int lookupDomain(long domain) {

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/CApiContext.java

Lines changed: 31 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import java.util.Objects;
5252
import java.util.logging.Level;
5353

54+
import com.oracle.graal.python.util.PythonUtils;
5455
import org.graalvm.collections.EconomicMap;
5556

5657
import com.oracle.graal.python.PythonLanguage;
@@ -70,10 +71,12 @@
7071
import com.oracle.graal.python.builtins.objects.frame.PFrame;
7172
import com.oracle.graal.python.builtins.objects.function.PArguments;
7273
import com.oracle.graal.python.builtins.objects.function.Signature;
74+
import com.oracle.graal.python.nodes.IndirectCallNode;
7375
import com.oracle.graal.python.nodes.PRootNode;
7476
import com.oracle.graal.python.nodes.call.GenericInvokeNode;
7577
import com.oracle.graal.python.runtime.AsyncHandler;
7678
import com.oracle.graal.python.runtime.ExecutionContext.CalleeContext;
79+
import com.oracle.graal.python.runtime.ExecutionContext.IndirectCallContext;
7780
import com.oracle.graal.python.runtime.PythonContext;
7881
import com.oracle.graal.python.runtime.PythonOptions;
7982
import com.oracle.truffle.api.CompilerAsserts;
@@ -95,6 +98,8 @@ public final class CApiContext extends CExtContext {
9598

9699
public static final long REFERENCE_COUNT_BITS = Integer.SIZE;
97100
public static final long REFERENCE_COUNT_MARKER = (1L << REFERENCE_COUNT_BITS);
101+
/* a random number between 1 and 20 */
102+
private static final int MAX_COLLECTION_RETRIES = 17;
98103

99104
/** Total amount of allocated native memory (in bytes). */
100105
private long allocatedMemory = 0;
@@ -108,7 +113,6 @@ public final class CApiContext extends CExtContext {
108113
private Map<Object, AllocInfo> freedNativeMemory;
109114

110115
@CompilationFinal private RootCallTarget referenceCleanerCallTarget;
111-
@CompilationFinal private RootCallTarget triggerAsyncActionsCallTarget;
112116

113117
/**
114118
* This cache is used to cache native wrappers for frequently used primitives. This is strictly
@@ -208,14 +212,6 @@ private RootCallTarget getReferenceCleanerCallTarget() {
208212
return referenceCleanerCallTarget;
209213
}
210214

211-
RootCallTarget getTriggerAsyncActionsCallTarget() {
212-
if (triggerAsyncActionsCallTarget == null) {
213-
CompilerDirectives.transferToInterpreterAndInvalidate();
214-
triggerAsyncActionsCallTarget = Truffle.getRuntime().createCallTarget(new TriggerAsyncActionsRootNode(getContext()));
215-
}
216-
return triggerAsyncActionsCallTarget;
217-
}
218-
219215
public TraceMallocDomain getTraceMallocDomain(int domainIdx) {
220216
return traceMallocDomains[domainIdx];
221217
}
@@ -419,43 +415,6 @@ public void execute(PythonContext context) {
419415
}
420416
}
421417

422-
private static final class TriggerAsyncActionsRootNode extends PRootNode {
423-
private static final Signature SIGNATURE = new Signature(-1, false, -1, false, new String[0], new String[0]);
424-
425-
@Child private CalleeContext calleeContext = CalleeContext.create();
426-
427-
private final ConditionProfile customLocalsProfile = ConditionProfile.createBinaryProfile();
428-
private final BranchProfile asyncProfile = BranchProfile.create();
429-
private final PythonContext context;
430-
431-
protected TriggerAsyncActionsRootNode(PythonContext context) {
432-
super(context.getLanguage());
433-
this.context = context;
434-
}
435-
436-
@Override
437-
public Object execute(VirtualFrame frame) {
438-
CalleeContext.enter(frame, customLocalsProfile);
439-
try {
440-
doGc();
441-
context.triggerAsyncActions(frame, asyncProfile);
442-
} finally {
443-
calleeContext.exit(frame, this);
444-
}
445-
return 0;
446-
}
447-
448-
@Override
449-
public Signature getSignature() {
450-
return SIGNATURE;
451-
}
452-
453-
@Override
454-
public boolean isPythonInternal() {
455-
return true;
456-
}
457-
}
458-
459418
public NativeObjectReference lookupNativeObjectReference(int idx) {
460419
return nativeObjectWrapperList.get(idx);
461420
}
@@ -615,47 +574,53 @@ public boolean isAllocated(Object ptr) {
615574
return true;
616575
}
617576

618-
public void increaseMemoryPressure(GenericInvokeNode invokeNode, long size) {
577+
public void increaseMemoryPressure(long size) {
619578
if (allocatedMemory <= getContext().getOption(PythonOptions.MaxNativeMemory)) {
620579
allocatedMemory += size;
621580
return;
622581
}
623-
624-
// Triggering the GC and the async actions is hidden behind a call target to keep this
625-
// method slim.
626-
invokeNode.execute(getTriggerAsyncActionsCallTarget(), PArguments.create());
627-
628-
if (allocatedMemory + size > getContext().getOption(PythonOptions.MaxNativeMemory)) {
629-
throw new OutOfMemoryError("native memory");
630-
}
631-
allocatedMemory += size;
582+
triggerGC(size);
632583
}
633584

634-
public void increaseMemoryPressure(VirtualFrame frame, long size, BranchProfile asyncProfile) {
635-
if (allocatedMemory <= getContext().getOption(PythonOptions.MaxNativeMemory)) {
585+
public void increaseMemoryPressure(VirtualFrame frame, PythonContext context, IndirectCallNode caller, long size) {
586+
if (allocatedMemory + size <= getContext().getOption(PythonOptions.MaxNativeMemory)) {
636587
allocatedMemory += size;
637588
return;
638589
}
639590

640-
doGc();
641-
getContext().triggerAsyncActions(frame, asyncProfile);
591+
Object savedState = IndirectCallContext.enter(frame, context, caller);
592+
try {
593+
triggerGC(size);
594+
} finally {
595+
IndirectCallContext.exit(frame, context, savedState);
596+
}
597+
}
642598

643-
if (allocatedMemory + size > getContext().getOption(PythonOptions.MaxNativeMemory)) {
644-
throw new OutOfMemoryError("native memory");
599+
@TruffleBoundary
600+
private void triggerGC(long size) {
601+
long delay = 0;
602+
for (int retries = 0; retries < MAX_COLLECTION_RETRIES; retries++) {
603+
delay += 50;
604+
doGc(delay);
605+
getContext().triggerAsyncActions(null, BranchProfile.getUncached());
606+
if (allocatedMemory + size <= getContext().getOption(PythonOptions.MaxNativeMemory)) {
607+
allocatedMemory += size;
608+
return;
609+
}
645610
}
646-
allocatedMemory += size;
611+
throw new OutOfMemoryError("native memory");
647612
}
648613

649614
public void reduceMemoryPressure(long size) {
650615
allocatedMemory -= size;
651616
}
652617

653618
@TruffleBoundary
654-
private static void doGc() {
619+
private static void doGc(long millis) {
655620
LOGGER.fine("full GC due to native memory");
656-
System.gc();
621+
PythonUtils.forceFullGC();
657622
try {
658-
Thread.sleep(50);
623+
Thread.sleep(millis);
659624
} catch (InterruptedException x) {
660625
// Restore interrupt status
661626
Thread.currentThread().interrupt();

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/capi/PyTruffleObjectAlloc.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444

4545
import com.oracle.graal.python.PythonLanguage;
4646
import com.oracle.graal.python.builtins.objects.frame.PFrame;
47-
import com.oracle.graal.python.nodes.call.GenericInvokeNode;
4847
import com.oracle.graal.python.nodes.frame.GetCurrentFrameRef;
4948
import com.oracle.graal.python.nodes.util.CannotCastException;
5049
import com.oracle.graal.python.nodes.util.CastToJavaLongLossyNode;
@@ -77,7 +76,6 @@ Object execute(Object[] arguments,
7776
@Cached CastToJavaLongLossyNode castToJavaLongNode,
7877
@Cached GetCurrentFrameRef getCurrentFrameRef,
7978
@CachedContext(PythonLanguage.class) ContextReference<PythonContext> contextRef,
80-
@Cached GenericInvokeNode invokeNode,
8179
@CachedLibrary(limit = "3") InteropLibrary lib,
8280
@Cached(value = "getAllocationReporter(contextRef)", allowUncached = true) AllocationReporter reporter) throws ArityException {
8381
if (arguments.length != 2) {
@@ -98,7 +96,7 @@ Object execute(Object[] arguments,
9896
// memory management
9997
PythonContext context = contextRef.get();
10098
CApiContext cApiContext = context.getCApiContext();
101-
cApiContext.increaseMemoryPressure(invokeNode, objectSize);
99+
cApiContext.increaseMemoryPressure(objectSize);
102100

103101
boolean isLoggable = LOGGER.isLoggable(Level.FINER);
104102
boolean traceNativeMemory = context.getOption(PythonOptions.TraceNativeMemory);

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/util/PythonUtils.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@
4040
*/
4141
package com.oracle.graal.python.util;
4242

43+
import java.lang.management.ManagementFactory;
44+
45+
import javax.management.InstanceNotFoundException;
46+
import javax.management.MBeanException;
47+
import javax.management.MBeanServer;
48+
import javax.management.MalformedObjectNameException;
49+
import javax.management.ObjectName;
50+
import javax.management.ReflectionException;
51+
52+
import org.graalvm.nativeimage.ImageInfo;
53+
4354
import com.oracle.truffle.api.CompilerDirectives;
4455
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
4556

@@ -78,4 +89,42 @@ public static int multiplyExact(int x, int y) throws OverflowException {
7889
}
7990
return (int) r;
8091
}
92+
93+
private static final MBeanServer SERVER;
94+
private static final String OPERATION_NAME = "gcRun";
95+
private static final Object[] PARAMS = new Object[]{null};
96+
private static final String[] SIGNATURE = new String[]{String[].class.getName()};
97+
private static final ObjectName OBJECT_NAME;
98+
99+
static {
100+
if (ImageInfo.inImageCode()) {
101+
OBJECT_NAME = null;
102+
SERVER = null;
103+
} else {
104+
SERVER = ManagementFactory.getPlatformMBeanServer();
105+
ObjectName n;
106+
try {
107+
n = new ObjectName("com.sun.management:type=DiagnosticCommand");
108+
} catch (final MalformedObjectNameException e) {
109+
n = null;
110+
}
111+
OBJECT_NAME = n;
112+
}
113+
}
114+
115+
/**
116+
* {@link System#gc()} does not force a GC, but the DiagnosticCommand "gcRun" does.
117+
*/
118+
@TruffleBoundary
119+
public static void forceFullGC() {
120+
if (OBJECT_NAME != null && SERVER != null) {
121+
try {
122+
SERVER.invoke(OBJECT_NAME, OPERATION_NAME, PARAMS, SIGNATURE);
123+
} catch (InstanceNotFoundException | ReflectionException | MBeanException e) {
124+
// use fallback
125+
}
126+
}
127+
System.gc();
128+
Runtime.getRuntime().freeMemory();
129+
}
81130
}

0 commit comments

Comments
 (0)