Skip to content

Commit f41aed4

Browse files
committed
Reduce overhead for allocating handle reference
1 parent 8367688 commit f41aed4

File tree

1 file changed

+53
-103
lines changed
  • graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/cext/hpy

1 file changed

+53
-103
lines changed

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

Lines changed: 53 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@
5656
import static com.oracle.graal.python.builtins.objects.cext.hpy.GraalHPyNativeSymbol.GRAAL_HPY_CONTEXT_TO_NATIVE;
5757

5858
import java.io.IOException;
59-
import java.lang.ref.PhantomReference;
6059
import java.lang.ref.Reference;
6160
import java.lang.ref.ReferenceQueue;
61+
import java.lang.ref.WeakReference;
6262
import java.nio.file.Paths;
6363
import java.util.ArrayList;
6464
import java.util.Arrays;
@@ -748,20 +748,15 @@ public static GraalHPyNativeSymbol getGetterFunctionName(LLVMType llvmType) {
748748

749749
/**
750750
* The global reference queue is a list consisting of {@link GraalHPyHandleReference} objects.
751-
* It is used to keep those objects (which are phantom refs) alive until they are enqueued in
752-
* the corresponding reference queue. The list instance referenced by this variable is
753-
* exclusively owned by the main thread (i.e. the main thread may operate on the list without
751+
* It is used to keep those objects (which are weak refs) alive until they are enqueued in the
752+
* corresponding reference queue. The list instance referenced by this variable is exclusively
753+
* owned by the main thread (i.e. the main thread may operate on the list without
754754
* synchronization). The HPy reference cleaner thread (see
755755
* {@link GraalHPyReferenceCleanerRunnable}) will consume this instance using an atomic
756756
* {@code getAndSet} operation. At this point, the ownership is transferred to the cleaner
757-
* thread. In order to avoid that the list is consumed by the cleaner thread while the main
758-
* thread is mutating it, the main thread will temporarily set this variable to {@code null}
759-
* (see
760-
* {@link #createHandleReference(com.oracle.graal.python.builtins.objects.object.PythonObject, Object, Object)}
761-
* . Except of this situation, this variable will never be {@code null}. If the cleaner thread
762-
* tries to consume while it is {@code null}, it will spin until an instance is again available.
757+
* thread.
763758
*/
764-
public final AtomicReference<GraalHPyHandleReferenceList> references = new AtomicReference<>(new GraalHPyHandleReferenceList());
759+
public final AtomicReference<GraalHPyHandleReference> references = new AtomicReference<>(null);
765760
private ReferenceQueue<Object> nativeSpaceReferenceQueue;
766761
@CompilationFinal private RootCallTarget referenceCleanerCallTarget;
767762
private Thread hpyReferenceCleanerThread;
@@ -813,7 +808,7 @@ private RootCallTarget getReferenceCleanerCallTarget() {
813808
static final class GraalHPyReferenceCleanerRunnable implements Runnable {
814809
private static final TruffleLogger LOGGER = PythonLanguage.getLogger(GraalHPyReferenceCleanerRunnable.class);
815810
private final ReferenceQueue<?> referenceQueue;
816-
private GraalHPyHandleReferenceList cleanerList;
811+
private GraalHPyHandleReference cleanerList;
817812

818813
GraalHPyReferenceCleanerRunnable(ReferenceQueue<?> referenceQueue) {
819814
this.referenceQueue = referenceQueue;
@@ -860,36 +855,26 @@ public void run() {
860855
* will be replaced by an empty list (which will then be owned by the main
861856
* thread).
862857
*/
863-
GraalHPyHandleReferenceList refList;
864-
GraalHPyHandleReferenceList emptyRefList = new GraalHPyHandleReferenceList();
858+
GraalHPyHandleReference refList;
865859
do {
866860
/*
867861
* If 'refList' is null then the main is currently updating it. So, we need
868862
* to repeat until we get something. The written empty list will just be
869863
* lost.
870864
*/
871-
refList = hPyContext.references.getAndSet(emptyRefList);
865+
refList = hPyContext.references.getAndSet(null);
872866
} while (refList == null);
873867

874-
/*
875-
* Merge the received reference list into the existing one or just take it if
876-
* there wasn't one before.
877-
*/
878-
if (cleanerList == null) {
879-
cleanerList = refList;
880-
} else {
881-
cleanerList.append(refList);
882-
}
883-
884868
if (!refs.isEmpty()) {
885869
try {
886-
Object[] arguments = PArguments.create(2);
870+
Object[] arguments = PArguments.create(3);
887871
PArguments.setGlobals(arguments, dummyGlobals);
888872
PArguments.setException(arguments, PException.NO_EXCEPTION);
889873
PArguments.setCallerFrameInfo(arguments, PFrame.Reference.EMPTY);
890874
PArguments.setArgument(arguments, 0, refs.toArray(new GraalHPyHandleReference[0]));
891-
PArguments.setArgument(arguments, 1, cleanerList);
892-
CallTargetInvokeNode.invokeUncached(callTarget, arguments);
875+
PArguments.setArgument(arguments, 1, refList);
876+
PArguments.setArgument(arguments, 2, cleanerList);
877+
cleanerList = (GraalHPyHandleReference) CallTargetInvokeNode.invokeUncached(callTarget, arguments);
893878
} catch (PException e) {
894879
/*
895880
* Since the cleaner thread is not running any Python code, we should
@@ -937,7 +922,8 @@ public Object execute(VirtualFrame frame) {
937922
*/
938923

939924
GraalHPyHandleReference[] handleReferences = (GraalHPyHandleReference[]) PArguments.getArgument(frame, 0);
940-
GraalHPyHandleReferenceList refList = (GraalHPyHandleReferenceList) PArguments.getArgument(frame, 1);
925+
GraalHPyHandleReference refList = (GraalHPyHandleReference) PArguments.getArgument(frame, 1);
926+
GraalHPyHandleReference oldRefList = (GraalHPyHandleReference) PArguments.getArgument(frame, 2);
941927
long startTime = 0;
942928
long middleTime = 0;
943929
final int n = handleReferences.length;
@@ -952,9 +938,36 @@ public Object execute(VirtualFrame frame) {
952938
if (CompilerDirectives.inInterpreter()) {
953939
com.oracle.truffle.api.nodes.LoopNode.reportLoopCount(this, n);
954940
}
955-
// remove references from the global reference list such that they can die
941+
942+
// mark queued references as cleaned
956943
for (int i = 0; i < n; i++) {
957-
refList.remove(handleReferences[i]);
944+
handleReferences[i].cleaned = true;
945+
}
946+
947+
// remove marked references from the global reference list such that they can die
948+
GraalHPyHandleReference prev = null;
949+
for (GraalHPyHandleReference cur = refList; cur != null; cur = cur.next) {
950+
if (cur.cleaned) {
951+
if (prev != null) {
952+
prev.next = cur.next;
953+
} else {
954+
// new head
955+
refList = cur.next;
956+
}
957+
} else {
958+
prev = cur;
959+
}
960+
}
961+
962+
/*
963+
* Merge the received reference list into the existing one or just take it if there
964+
* wasn't one before.
965+
*/
966+
if (prev != null) {
967+
// if prev exists, it now points to the tail
968+
prev.next = oldRefList;
969+
} else {
970+
refList = oldRefList;
958971
}
959972

960973
if (loggable) {
@@ -975,7 +988,7 @@ public Object execute(VirtualFrame frame) {
975988
LOGGER.fine(() -> "Count duration: " + countDuration);
976989
LOGGER.fine(() -> "Duration: " + duration);
977990
}
978-
return PNone.NONE;
991+
return refList;
979992
}
980993

981994
@Override
@@ -2197,68 +2210,15 @@ int pop() {
21972210
}
21982211

21992212
/**
2200-
* A simple doubly-linked list consisting of {@link GraalHPyHandleReference} objects which are
2201-
* also the nodes of this list.<br>
2202-
* For a description on how this list is used, see {@link #references}.
2203-
*/
2204-
static final class GraalHPyHandleReferenceList {
2205-
GraalHPyHandleReference head;
2206-
GraalHPyHandleReference tail;
2207-
2208-
GraalHPyHandleReferenceList() {
2209-
}
2210-
2211-
void insert(GraalHPyHandleReference ref) {
2212-
if (tail == null) {
2213-
assert head == null;
2214-
tail = ref;
2215-
}
2216-
if (head != null) {
2217-
ref.next = head;
2218-
head.prev = ref;
2219-
}
2220-
head = ref;
2221-
}
2222-
2223-
void remove(GraalHPyHandleReference ref) {
2224-
if (ref.next != null) {
2225-
ref.next.prev = ref.prev;
2226-
} else {
2227-
tail = ref.prev;
2228-
}
2229-
if (ref.prev != null) {
2230-
ref.prev.next = ref.next;
2231-
} else {
2232-
head = ref.next;
2233-
}
2234-
}
2235-
2236-
void append(GraalHPyHandleReferenceList other) {
2237-
if (other.head != null) {
2238-
assert other.tail != null;
2239-
if (head == null) {
2240-
head = other.head;
2241-
tail = other.tail;
2242-
} else {
2243-
assert tail != null;
2244-
tail.next = other.head;
2245-
other.head.prev = tail;
2246-
tail = other.tail;
2247-
}
2248-
}
2249-
}
2250-
}
2251-
2252-
/**
2253-
* A phantom reference to an object that has an associated HPy native space (
2213+
* A weak reference to an object that has an associated HPy native space (
22542214
* {@link PythonHPyObject}).
22552215
*/
2256-
static final class GraalHPyHandleReference extends PhantomReference<Object> {
2216+
static final class GraalHPyHandleReference extends WeakReference<Object> {
22572217

22582218
private final Object nativeSpace;
22592219
private final Object destroyFunc;
22602220

2261-
private GraalHPyHandleReference prev;
2221+
boolean cleaned;
22622222
private GraalHPyHandleReference next;
22632223

22642224
public GraalHPyHandleReference(Object referent, ReferenceQueue<Object> q, Object nativeSpace, Object destroyFunc) {
@@ -2288,7 +2248,7 @@ public void setNext(GraalHPyHandleReference next) {
22882248
* Registers an HPy native space of a Python object.<br/>
22892249
* Use this method to register a native memory that is associated with a Python object in order
22902250
* to ensure that the native memory will be free'd when the owning Python object dies.<br/>
2291-
* This works by creating a phantom reference to the Python object, using a thread that
2251+
* This works by creating a weak reference to the Python object, using a thread that
22922252
* concurrently polls the reference queue. If threading is allowed, cleaning will be done fully
22932253
* concurrent on a cleaner thread. If not, an async action will be scheduled to free the native
22942254
* memory. Hence, the destroy function could also be executed on the cleaner thread.
@@ -2301,20 +2261,10 @@ public void setNext(GraalHPyHandleReference next) {
23012261
@TruffleBoundary
23022262
final void createHandleReference(PythonObject pythonObject, Object dataPtr, Object destroyFunc) {
23032263
GraalHPyHandleReference newHead = new GraalHPyHandleReference(pythonObject, ensureReferenceQueue(), dataPtr, destroyFunc);
2304-
2305-
/*
2306-
* Get the current list and set null such that the cleaner thread cannot consume it while
2307-
* the main thread is updating the list.
2308-
*/
2309-
GraalHPyHandleReferenceList refList = references.getAndSet(null);
2310-
refList.insert(newHead);
2311-
2312-
/*
2313-
* Restore list, i.e., make it available to reference cleaner thread for consumption. The
2314-
* cleaner thread may have updated the value in the meantime but only with an empty list.
2315-
* So, this can be ignored and the cleaner thread is able to deal with that.
2316-
*/
2317-
references.set(refList);
2264+
references.getAndAccumulate(newHead, (prev, x) -> {
2265+
x.next = prev;
2266+
return x;
2267+
});
23182268
}
23192269

23202270
private ReferenceQueue<Object> ensureReferenceQueue() {

0 commit comments

Comments
 (0)