Skip to content

Commit 96b1406

Browse files
committed
Do bulk free of collected native object references.
1 parent 99af87b commit 96b1406

File tree

8 files changed

+247
-39
lines changed

8 files changed

+247
-39
lines changed

graalpython/com.oracle.graal.python.cext/src/capi.c

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,26 @@ Py_ssize_t PyTruffle_SUBREF(PyObject* obj, Py_ssize_t value) {
411411
return new_value;
412412
}
413413

414+
/** to be used from Java code only; calls DECREF */
415+
Py_ssize_t PyTruffle_bulk_SUBREF(PyObject* ptrArray[], Py_ssize_t values[], int64_t len) {
416+
int64_t i;
417+
PyObject* obj;
418+
419+
for (i=0; i < len; i++) {
420+
obj = ptrArray[i];
421+
Py_ssize_t new_value = ((obj->ob_refcnt) -= values[i]);
422+
if (new_value == 0) {
423+
_Py_Dealloc(obj);
424+
}
425+
#ifdef Py_REF_DEBUG
426+
else if (new_value < 0) {
427+
_Py_NegativeRefcount(filename, lineno, op);
428+
}
429+
#endif
430+
}
431+
return 0;
432+
}
433+
414434
typedef struct PyObjectHandle {
415435
PyObject_HEAD
416436
} PyObjectHandle;

graalpython/com.oracle.graal.python.cext/src/obmalloc.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ typedef struct {
5858
/* Get the object given the GC head */
5959
#define FROM_MEM_HEAD(g) ((void *)(((mem_head_t *)g)+1))
6060

61+
typedef void (*trace_free_fun_t)(void *, size_t);
62+
UPCALL_TYPED_ID(PyTruffle_Trace_Free, trace_free_fun_t);
63+
6164
/* This is our version of 'PyObject_Free' which is also able to free Sulong handles. */
6265
MUST_INLINE static
6366
void _PyObject_Free(void* ptr) {
@@ -71,7 +74,7 @@ void _PyObject_Free(void* ptr) {
7174
}
7275
}
7376
mem_head_t* ptr_with_head = AS_MEM_HEAD(ptr);
74-
(void) polyglot_invoke(PY_TRUFFLE_CEXT, "PyTruffle_Trace_Free", ptr, ptr_with_head->size);
77+
_jls_PyTruffle_Trace_Free(ptr, ptr_with_head->size);
7578
free(ptr_with_head);
7679
}
7780

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

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3566,24 +3566,15 @@ abstract static class PyTruffleTraceFree extends PythonBinaryBuiltinNode {
35663566
private static final TruffleLogger LOGGER = PythonLanguage.getLogger(PyTruffleTraceFree.class);
35673567

35683568
@Specialization(limit = "2")
3569-
static int doNativeWrapper(Object ptr, Object sizeObject,
3570-
@Cached CastToJavaLongLossyNode castToJavaLongNode,
3569+
static int doNativeWrapperLong(Object ptr, long size,
35713570
@CachedLibrary("ptr") InteropLibrary lib,
35723571
@Cached GetCurrentFrameRef getCurrentFrameRef,
35733572
@CachedContext(PythonLanguage.class) PythonContext context) {
35743573

3575-
long size;
3576-
try {
3577-
size = castToJavaLongNode.execute(sizeObject);
3578-
} catch (CannotCastException e) {
3579-
CompilerDirectives.transferToInterpreterAndInvalidate();
3580-
throw new IllegalArgumentException("invalid type for second argument 'objectSize'");
3581-
}
3582-
35833574
CApiContext cApiContext = context.getCApiContext();
35843575
cApiContext.reduceMemoryPressure(size);
35853576

3586-
boolean isLoggable = LOGGER.isLoggable(Level.FINE);
3577+
boolean isLoggable = LOGGER.isLoggable(Level.FINER);
35873578
boolean traceNativeMemory = context.getOption(PythonOptions.TraceNativeMemory);
35883579
if ((isLoggable || traceNativeMemory) && !lib.isNull(ptr)) {
35893580
boolean traceNativeMemoryCalls = context.getOption(PythonOptions.TraceNativeMemoryCalls);
@@ -3594,7 +3585,7 @@ static int doNativeWrapper(Object ptr, Object sizeObject,
35943585
}
35953586
AllocInfo allocLocation = cApiContext.traceFree(CApiContext.asPointer(ptr, lib), ref, null);
35963587
if (allocLocation != null) {
3597-
LOGGER.fine(() -> String.format("Freeing pointer (size: %d): %s", allocLocation.size, CApiContext.asHex(ptr)));
3588+
LOGGER.finer(() -> String.format("Freeing pointer (size: %d): %s", allocLocation.size, CApiContext.asHex(ptr)));
35983589

35993590
if (traceNativeMemoryCalls) {
36003591
Reference left = allocLocation.allocationSite;
@@ -3605,17 +3596,34 @@ static int doNativeWrapper(Object ptr, Object sizeObject,
36053596
}
36063597
if (pyFrame != null) {
36073598
final PFrame f = pyFrame;
3608-
LOGGER.fine(() -> String.format("Free'd pointer was allocated at: %s", f.getTarget()));
3599+
LOGGER.finer(() -> String.format("Free'd pointer was allocated at: %s", f.getTarget()));
36093600
}
36103601
}
36113602
}
36123603
} else {
36133604
assert isLoggable;
3614-
LOGGER.fine(() -> String.format("Freeing pointer: %s", CApiContext.asHex(ptr)));
3605+
LOGGER.finer(() -> String.format("Freeing pointer: %s", CApiContext.asHex(ptr)));
36153606
}
36163607
}
36173608
return 0;
36183609
}
3610+
3611+
@Specialization(limit = "2", replaces = "doNativeWrapperLong")
3612+
static int doNativeWrapper(Object ptr, Object sizeObject,
3613+
@Cached CastToJavaLongLossyNode castToJavaLongNode,
3614+
@CachedLibrary("ptr") InteropLibrary lib,
3615+
@Cached GetCurrentFrameRef getCurrentFrameRef,
3616+
@CachedContext(PythonLanguage.class) PythonContext context) {
3617+
long size;
3618+
try {
3619+
size = castToJavaLongNode.execute(sizeObject);
3620+
} catch (CannotCastException e) {
3621+
CompilerDirectives.transferToInterpreterAndInvalidate();
3622+
throw new IllegalArgumentException("invalid type for second argument 'objectSize'");
3623+
}
3624+
return doNativeWrapperLong(ptr, size, lib, getCurrentFrameRef, context);
3625+
}
3626+
36193627
}
36203628

36213629
@Builtin(name = "PyTruffle_Trace_Type", minNumOfPositionalArgs = 2)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public abstract class NativeCAPISymbols {
103103
public static final String FUN_TRUFFLE_MANAGED_FROM_HANDLE = "truffle_managed_from_handle";
104104
public static final String FUN_TRUFFLE_CANNOT_BE_HANDLE = "truffle_cannot_be_handle";
105105
public static final String FUN_GET_LONG_BITS_PER_DIGIT = "get_long_bits_in_digit";
106+
public static final String FUN_BULK_SUBREF = "PyTruffle_bulk_SUBREF";
106107

107108
@CompilationFinal(dimensions = 1) private static final String[] values;
108109
static {

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

Lines changed: 88 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,21 @@
5252
import java.util.Objects;
5353
import java.util.logging.Level;
5454

55-
import com.oracle.graal.python.builtins.objects.cext.CExtNodes;
56-
import com.oracle.graal.python.builtins.objects.cext.NativeCAPISymbols;
5755
import org.graalvm.collections.EconomicMap;
5856

5957
import com.oracle.graal.python.PythonLanguage;
6058
import com.oracle.graal.python.builtins.objects.PNone;
6159
import com.oracle.graal.python.builtins.objects.cext.CAPIConversionNodeSupplier;
6260
import com.oracle.graal.python.builtins.objects.cext.CApiGuards;
61+
import com.oracle.graal.python.builtins.objects.cext.CExtNodes;
6362
import com.oracle.graal.python.builtins.objects.cext.CExtNodes.AddRefCntNode;
6463
import com.oracle.graal.python.builtins.objects.cext.CExtNodes.GetRefCntNode;
65-
import com.oracle.graal.python.builtins.objects.cext.CExtNodes.SubRefCntNode;
66-
import com.oracle.graal.python.builtins.objects.cext.CExtNodesFactory.SubRefCntNodeGen;
64+
import com.oracle.graal.python.builtins.objects.cext.CExtNodes.PCallCapiFunction;
6765
import com.oracle.graal.python.builtins.objects.cext.DynamicObjectNativeWrapper.PrimitiveNativeWrapper;
66+
import com.oracle.graal.python.builtins.objects.cext.NativeCAPISymbols;
6867
import com.oracle.graal.python.builtins.objects.cext.PythonAbstractNativeObject;
68+
import com.oracle.graal.python.builtins.objects.cext.capi.NativeObjectReferenceArrayWrapper.PointerArrayWrapper;
69+
import com.oracle.graal.python.builtins.objects.cext.capi.NativeObjectReferenceArrayWrapper.RefCountArrayWrapper;
6970
import com.oracle.graal.python.builtins.objects.cext.common.CExtContext;
7071
import com.oracle.graal.python.builtins.objects.frame.PFrame;
7172
import com.oracle.graal.python.builtins.objects.function.PArguments;
@@ -152,7 +153,7 @@ public CApiContext(PythonContext context, Object hpyLibrary) {
152153
Thread.currentThread().interrupt();
153154
}
154155

155-
ArrayDeque<NativeObjectReference> refs = new ArrayDeque<>();
156+
ArrayList<NativeObjectReference> refs = new ArrayList<>();
156157
do {
157158
if (reference instanceof NativeObjectReference) {
158159
refs.add((NativeObjectReference) reference);
@@ -162,7 +163,7 @@ public CApiContext(PythonContext context, Object hpyLibrary) {
162163
} while (reference != null);
163164

164165
if (!refs.isEmpty()) {
165-
return new CApiReferenceCleanerAction(refs);
166+
return new CApiReferenceCleanerAction(refs.toArray(new NativeObjectReference[0]));
166167
}
167168

168169
return null;
@@ -299,35 +300,76 @@ private static final class CApiReferenceCleanerRootNode extends PRootNode {
299300
private static final Signature SIGNATURE = new Signature(-1, false, -1, false, new String[]{"ptr", "managedRefCount"}, new String[0]);
300301
private static final TruffleLogger LOGGER = PythonLanguage.getLogger(CApiReferenceCleanerRootNode.class);
301302

302-
@Child private SubRefCntNode refCntNode;
303-
@Child private CalleeContext calleeContext = CalleeContext.create();
303+
@Child private CalleeContext calleeContext;
304+
@Child private InteropLibrary pointerObjectLib;
305+
@Child private PCallCapiFunction callBulkSubref;
304306

305307
private final ConditionProfile customLocalsProfile = ConditionProfile.createBinaryProfile();
306308
private final CApiContext cApiContext;
307309

308310
protected CApiReferenceCleanerRootNode(PythonContext context) {
309311
super(context.getLanguage());
310-
refCntNode = SubRefCntNodeGen.create();
311312
this.cApiContext = context.getCApiContext();
313+
this.calleeContext = CalleeContext.create();
314+
this.callBulkSubref = PCallCapiFunction.create();
312315
}
313316

314317
@Override
315-
@SuppressWarnings("unchecked")
316318
public Object execute(VirtualFrame frame) {
317319
CalleeContext.enter(frame, customLocalsProfile);
318320
try {
319-
ArrayDeque<NativeObjectReference> nativeObjectReferences = (ArrayDeque<NativeObjectReference>) PArguments.getArgument(frame, 0);
321+
NativeObjectReference[] nativeObjectReferences = (NativeObjectReference[]) PArguments.getArgument(frame, 0);
320322
NativeObjectReference nativeObjectReference;
321-
while ((nativeObjectReference = pollFirst(nativeObjectReferences)) != null) {
322-
if (!nativeObjectReference.resurrect) {
323-
TruffleObject pointerObject = nativeObjectReference.getPtrObject();
323+
int cleaned = 0;
324+
long allocatedNativeMem = cApiContext.allocatedMemory;
325+
long startTime = 0;
326+
long middleTime = 0;
327+
final int n = nativeObjectReferences.length;
328+
boolean loggable = LOGGER.isLoggable(Level.FINE);
329+
330+
if (loggable) {
331+
startTime = System.currentTimeMillis();
332+
}
333+
334+
if (LOGGER.isLoggable(Level.FINER)) {
335+
// it's not an OSR loop, so we do this before the loop
336+
if (n > 0 && pointerObjectLib == null) {
337+
CompilerDirectives.transferToInterpreterAndInvalidate();
338+
pointerObjectLib = insert(InteropLibrary.getFactory().create(nativeObjectReferences[0].ptrObject));
339+
}
324340

341+
for (int i = 0; i < n; i++) {
342+
nativeObjectReference = nativeObjectReferences[i];
325343
cApiContext.nativeObjectWrapperList.remove(nativeObjectReference.id);
326-
if (LOGGER.isLoggable(Level.FINE)) {
327-
LOGGER.fine(() -> "Cleaning native object reference to " + CApiContext.asHex(pointerObject));
344+
Object pointerObject = nativeObjectReference.ptrObject;
345+
if (!nativeObjectReference.resurrect && !pointerObjectLib.isNull(pointerObject)) {
346+
cApiContext.checkAccess(pointerObject, pointerObjectLib);
347+
LOGGER.finer(() -> "Cleaning native object reference to " + CApiContext.asHex(pointerObject));
348+
cleaned++;
328349
}
329-
refCntNode.execute(pointerObject, nativeObjectReference.managedRefCount);
330350
}
351+
} else {
352+
for (int i = 0; i < n; i++) {
353+
cApiContext.nativeObjectWrapperList.remove(nativeObjectReferences[i].id);
354+
}
355+
}
356+
357+
if (loggable) {
358+
middleTime = System.currentTimeMillis();
359+
}
360+
361+
callBulkSubref.call(NativeCAPISymbols.FUN_BULK_SUBREF, new PointerArrayWrapper(nativeObjectReferences), new RefCountArrayWrapper(nativeObjectReferences), (long) n);
362+
363+
if (loggable) {
364+
final long countDuration = middleTime - startTime;
365+
final long duration = System.currentTimeMillis() - middleTime;
366+
final int finalCleaned = cleaned;
367+
final long freedNativeMemory = allocatedNativeMem - cApiContext.allocatedMemory;
368+
LOGGER.fine(() -> "Total queued references: " + n);
369+
LOGGER.fine(() -> "Cleaned references: " + finalCleaned);
370+
LOGGER.fine(() -> "Free'd native memory: " + freedNativeMemory);
371+
LOGGER.fine(() -> "Count duration: " + countDuration);
372+
LOGGER.fine(() -> "Duration: " + duration);
331373
}
332374
} finally {
333375
calleeContext.exit(frame, this);
@@ -340,24 +382,50 @@ private static NativeObjectReference pollFirst(ArrayDeque<NativeObjectReference>
340382
return deque.pollFirst();
341383
}
342384

385+
@TruffleBoundary
386+
private static int size(ArrayList<NativeObjectReference> deque) {
387+
return deque.size();
388+
}
389+
390+
@TruffleBoundary
391+
private static NativeObjectReference get(ArrayList<NativeObjectReference> deque, int i) {
392+
return deque.get(i);
393+
}
394+
395+
@TruffleBoundary
396+
private static NativeObjectReference clear(ArrayList<NativeObjectReference> deque, int i) {
397+
return deque.set(i, null);
398+
}
399+
343400
@Override
344401
public Signature getSignature() {
345402
return SIGNATURE;
346403
}
347404

405+
@Override
406+
public String getName() {
407+
return "native_reference_cleaner";
408+
}
409+
410+
@Override
411+
public boolean isInternal() {
412+
return false;
413+
}
414+
348415
@Override
349416
public boolean isPythonInternal() {
350-
return true;
417+
return false;
351418
}
352419
}
353420

354421
/**
355422
* Reference cleaner action that will be executed by the {@link AsyncHandler}.
356423
*/
357424
private static final class CApiReferenceCleanerAction implements AsyncHandler.AsyncAction {
358-
private final ArrayDeque<NativeObjectReference> nativeObjectReferences;
359425

360-
public CApiReferenceCleanerAction(ArrayDeque<NativeObjectReference> nativeObjectReferences) {
426+
private final NativeObjectReference[] nativeObjectReferences;
427+
428+
public CApiReferenceCleanerAction(NativeObjectReference[] nativeObjectReferences) {
361429
this.nativeObjectReferences = nativeObjectReferences;
362430
}
363431

0 commit comments

Comments
 (0)