From 9905b94359dd00c232dfe6ad4d45753ce353ea7f Mon Sep 17 00:00:00 2001 From: Francesco Nigro Date: Mon, 12 Feb 2024 14:21:58 +0100 Subject: [PATCH] Speedup ContextInstances if there are null instances --- .../processor/ContextInstancesGenerator.java | 207 +++++++++++------- 1 file changed, 123 insertions(+), 84 deletions(-) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java index 1a6f4a0700855..0c6adc2cb0921 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ContextInstancesGenerator.java @@ -3,6 +3,7 @@ import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_STATIC; import static org.objectweb.asm.Opcodes.ACC_VOLATILE; import java.util.ArrayList; @@ -12,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; @@ -66,8 +68,9 @@ Collection generate(DotName scope) { // Add ContextInstanceHandle and Lock fields for every bean // The name of the field is a generated index // For example: + // private static final AtomicReferenceFieldUpdater LAZY_1L_UPDATER; // private volatile ContextInstanceHandle 1; - // private final Lock 1l = new ReentrantLock(); + // private volatile Lock 1l; Map idToFields = new HashMap<>(); int fieldIndex = 0; for (BeanInfo bean : beans) { @@ -75,53 +78,106 @@ Collection generate(DotName scope) { FieldCreator handleField = contextInstances.getFieldCreator(beanIdx, ContextInstanceHandle.class) .setModifiers(ACC_PRIVATE | ACC_VOLATILE); FieldCreator lockField = contextInstances.getFieldCreator(beanIdx + "l", Lock.class) - .setModifiers(ACC_PRIVATE | ACC_FINAL); + .setModifiers(ACC_PRIVATE | ACC_VOLATILE); + FieldCreator atomicLockField = contextInstances.getFieldCreator("LAZY_" + beanIdx + "L_UPDATER", + AtomicReferenceFieldUpdater.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL | ACC_STATIC); idToFields.put(bean.getIdentifier(), - new InstanceAndLock(handleField.getFieldDescriptor(), lockField.getFieldDescriptor())); + new InstanceAndLock(handleField.getFieldDescriptor(), lockField.getFieldDescriptor(), + atomicLockField.getFieldDescriptor())); } + implementStaticConstructor(contextInstances, idToFields); + Map lazyLocks = implementLazyLocks(contextInstances, idToFields); + MethodCreator constructor = contextInstances.getMethodCreator(MethodDescriptor.INIT, "V"); constructor.invokeSpecialMethod(MethodDescriptors.OBJECT_CONSTRUCTOR, constructor.getThis()); - for (InstanceAndLock fields : idToFields.values()) { - constructor.writeInstanceField(fields.lock, constructor.getThis(), - constructor.newInstance(MethodDescriptor.ofConstructor(ReentrantLock.class))); - } constructor.returnVoid(); - implementComputeIfAbsent(contextInstances, beans, idToFields); + implementComputeIfAbsent(contextInstances, beans, idToFields, lazyLocks); implementGetIfPresent(contextInstances, beans, idToFields); - implementRemove(contextInstances, beans, idToFields); + List remove = implementRemove(contextInstances, beans, idToFields, lazyLocks); implementGetAllPresent(contextInstances, idToFields); - implementRemoveEach(contextInstances, idToFields); - - // These methods are needed to significantly reduce the size of the stack map table for getAllPresent() and removeEach() - implementLockAll(contextInstances, idToFields); - implementUnlockAll(contextInstances, idToFields); + implementRemoveEach(contextInstances, remove); contextInstances.close(); return classOutput.getResources(); } + private static void implementStaticConstructor(ClassCreator contextInstances, Map idToFields) { + // Add a static initializer to initialize the AtomicReferenceFieldUpdater fields + // static { + // LAZY_1L_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ContextInstances.class, Lock.class, "1l"); + // } + MethodCreator staticConstructor = contextInstances.getMethodCreator(MethodDescriptor.CLINIT, void.class) + .setModifiers(ACC_STATIC); + + MethodDescriptor newLockUpdater = MethodDescriptor.ofMethod(AtomicReferenceFieldUpdater.class, "newUpdater", + AtomicReferenceFieldUpdater.class, Class.class, Class.class, String.class); + + for (InstanceAndLock fields : idToFields.values()) { + ResultHandle updater = staticConstructor.invokeStaticMethod(newLockUpdater, + staticConstructor.loadClass(contextInstances.getClassName()), + staticConstructor.loadClass(Lock.class), + staticConstructor.load(fields.lock.getName())); + staticConstructor.writeStaticField(fields.lockUpdater, updater); + } + staticConstructor.returnVoid(); + } + + private static Map implementLazyLocks(ClassCreator contextInstances, + Map idToFields) { + MethodDescriptor atomicReferenceFieldUpdaterCompareAndSet = MethodDescriptor.ofMethod( + AtomicReferenceFieldUpdater.class, "compareAndSet", + boolean.class, Object.class, Object.class, Object.class); + // generated lazy lock initializers methods + // private Lock lazy1l() { + // Lock lock = this.1l; + // if (lock != null) { + // return lock; + // } + // lock = new ReentrantLock(); + // if (LAZY_1L_UPDATER.compareAndSet(this, null, lock)) { + // return lock; + // } + // return this.1l; + // } + Map lazyLockMethods = new HashMap<>(idToFields.size()); + for (var namedFields : idToFields.entrySet()) { + var fields = namedFields.getValue(); + MethodCreator lazyLockMethod = contextInstances.getMethodCreator("lazy" + fields.lock.getName(), Lock.class) + .setModifiers(ACC_PRIVATE); + ResultHandle lock = lazyLockMethod.readInstanceField(fields.lock, lazyLockMethod.getThis()); + lazyLockMethod + .ifNotNull(lock).trueBranch() + .returnValue(lock); + ResultHandle newLock = lazyLockMethod.newInstance(MethodDescriptor.ofConstructor(ReentrantLock.class)); + ResultHandle updated = lazyLockMethod.invokeVirtualMethod(atomicReferenceFieldUpdaterCompareAndSet, + lazyLockMethod.readStaticField(fields.lockUpdater), lazyLockMethod.getThis(), + lazyLockMethod.loadNull(), newLock); + lazyLockMethod + .ifTrue(updated).trueBranch() + .returnValue(newLock); + lazyLockMethod.returnValue(lazyLockMethod.readInstanceField(fields.lock, lazyLockMethod.getThis())); + lazyLockMethods.put(namedFields.getKey(), lazyLockMethod.getMethodDescriptor()); + } + return lazyLockMethods; + } + private void implementGetAllPresent(ClassCreator contextInstances, Map idToFields) { MethodCreator getAllPresent = contextInstances.getMethodCreator("getAllPresent", Set.class) .setModifiers(ACC_PUBLIC); - // this.lockAll(); // ContextInstanceHandle copy1 = this.1; - // this.unlockAll(); // Set> ret = new HashSet<>(); // if (copy1 != null) { // ret.add(copy1); // } // return ret; - getAllPresent.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "lockAll", void.class), - getAllPresent.getThis()); List results = new ArrayList<>(idToFields.size()); for (InstanceAndLock fields : idToFields.values()) { results.add(getAllPresent.readInstanceField(fields.instance, getAllPresent.getThis())); } - getAllPresent.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "unlockAll", void.class), - getAllPresent.getThis()); ResultHandle ret = getAllPresent.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); for (ResultHandle result : results) { getAllPresent.ifNotNull(result).trueBranch().invokeInterfaceMethod(MethodDescriptors.SET_ADD, ret, result); @@ -129,51 +185,21 @@ private void implementGetAllPresent(ClassCreator contextInstances, Map idToFields) { - MethodCreator lockAll = contextInstances.getMethodCreator("lockAll", void.class) - .setModifiers(ACC_PRIVATE); - for (InstanceAndLock fields : idToFields.values()) { - ResultHandle lock = lockAll.readInstanceField(fields.lock, lockAll.getThis()); - lockAll.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); - } - lockAll.returnVoid(); - } - - private void implementUnlockAll(ClassCreator contextInstances, Map idToFields) { - MethodCreator unlockAll = contextInstances.getMethodCreator("unlockAll", void.class) - .setModifiers(ACC_PRIVATE); - for (InstanceAndLock fields : idToFields.values()) { - ResultHandle lock = unlockAll.readInstanceField(fields.lock, unlockAll.getThis()); - unlockAll.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); - } - unlockAll.returnVoid(); - } - - private void implementRemoveEach(ClassCreator contextInstances, Map idToFields) { + private void implementRemoveEach(ClassCreator contextInstances, List removeInstances) { MethodCreator removeEach = contextInstances.getMethodCreator("removeEach", void.class, Consumer.class) .setModifiers(ACC_PUBLIC); - // this.lockAll(); - // ContextInstanceHandle copy1 = this.1; - // if (copy1 != null) { - // this.1 = null; - // } - // this.unlockAll(); + // ContextInstanceHandle copy1 = remove1(); // if (action != null) // if (copy1 != null) { // consumer.accept(copy1); // } // } - removeEach.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "lockAll", void.class), - removeEach.getThis()); - List results = new ArrayList<>(idToFields.size()); - for (InstanceAndLock fields : idToFields.values()) { - ResultHandle copy = removeEach.readInstanceField(fields.instance, removeEach.getThis()); - results.add(copy); - BytecodeCreator isNotNull = removeEach.ifNotNull(copy).trueBranch(); - isNotNull.writeInstanceField(fields.instance, isNotNull.getThis(), isNotNull.loadNull()); + + List results = new ArrayList<>(removeInstances.size()); + for (MethodDescriptor removeInstance : removeInstances) { + // invoke remove method for every instance handle field + results.add(removeEach.invokeVirtualMethod(removeInstance, removeEach.getThis())); } - removeEach.invokeVirtualMethod(MethodDescriptor.ofMethod(contextInstances.getClassName(), "unlockAll", void.class), - removeEach.getThis()); BytecodeCreator actionIsNotNull = removeEach.ifNotNull(removeEach.getMethodParam(0)).trueBranch(); for (ResultHandle result : results) { BytecodeCreator isNotNull = actionIsNotNull.ifNotNull(result).trueBranch(); @@ -182,8 +208,8 @@ private void implementRemoveEach(ClassCreator contextInstances, Map beans, - Map idToFields) { + private List implementRemove(ClassCreator contextInstances, List beans, + Map idToFields, Map lazyLocks) { MethodCreator remove = contextInstances .getMethodCreator("remove", ContextInstanceHandle.class, String.class) .setModifiers(ACC_PUBLIC); @@ -191,6 +217,7 @@ private void implementRemove(ClassCreator contextInstances, List beans StringSwitch strSwitch = remove.stringSwitch(remove.getMethodParam(0)); // https://github.com/quarkusio/gizmo/issues/164 strSwitch.fallThrough(); + List removeMethods = new ArrayList<>(beans.size()); for (BeanInfo bean : beans) { InstanceAndLock fields = idToFields.get(bean.getIdentifier()); FieldDescriptor instanceField = fields.instance; @@ -198,26 +225,32 @@ private void implementRemove(ClassCreator contextInstances, List beans // To eliminate large stack map table in the bytecode MethodCreator removeHandle = contextInstances.getMethodCreator("r" + instanceField.getName(), ContextInstanceHandle.class).setModifiers(ACC_PRIVATE); - // this.1l.lock(); // ContextInstanceHandle copy = this.1; - // if (copy != null) { - // this.1 = null; + // if (copy == null) { + // return null; // } - // this.1l.unlock(); + // Lock lock = lazy1l(); + // lock.lock(); + // copy = this.1; + // this.1 = null; + // lock.unlock(); // return copy; - ResultHandle lock = removeHandle.readInstanceField(fields.lock, removeHandle.getThis()); - removeHandle.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); ResultHandle copy = removeHandle.readInstanceField(instanceField, removeHandle.getThis()); - BytecodeCreator isNotNull = removeHandle.ifNotNull(copy).trueBranch(); - isNotNull.writeInstanceField(instanceField, isNotNull.getThis(), isNotNull.loadNull()); + removeHandle.ifNull(copy).trueBranch() + .returnValue(removeHandle.loadNull()); + ResultHandle lock = removeHandle.invokeVirtualMethod(lazyLocks.get(bean.getIdentifier()), removeHandle.getThis()); + removeHandle.invokeInterfaceMethod(MethodDescriptors.LOCK_LOCK, lock); + copy = removeHandle.readInstanceField(instanceField, removeHandle.getThis()); + removeHandle.writeInstanceField(instanceField, removeHandle.getThis(), removeHandle.loadNull()); removeHandle.invokeInterfaceMethod(MethodDescriptors.LOCK_UNLOCK, lock); removeHandle.returnValue(copy); - + removeMethods.add(removeHandle.getMethodDescriptor()); strSwitch.caseOf(bean.getIdentifier(), bc -> { bc.returnValue(bc.invokeVirtualMethod(removeHandle.getMethodDescriptor(), bc.getThis())); }); } strSwitch.defaultCase(bc -> bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); + return removeMethods; } private void implementGetIfPresent(ClassCreator contextInstances, List beans, @@ -238,7 +271,7 @@ private void implementGetIfPresent(ClassCreator contextInstances, List } private void implementComputeIfAbsent(ClassCreator contextInstances, List beans, - Map idToFields) { + Map idToFields, Map lazyLocks) { MethodCreator computeIfAbsent = contextInstances .getMethodCreator("computeIfAbsent", ContextInstanceHandle.class, String.class, Supplier.class) .setModifiers(ACC_PUBLIC); @@ -255,32 +288,38 @@ private void implementComputeIfAbsent(ClassCreator contextInstances, List { bc.returnValue(bc.invokeVirtualMethod(compute.getMethodDescriptor(), bc.getThis(), bc.getMethodParam(1))); @@ -289,7 +328,7 @@ private void implementComputeIfAbsent(ClassCreator contextInstances, List bc.throwException(IllegalArgumentException.class, "Unknown bean identifier")); } - record InstanceAndLock(FieldDescriptor instance, FieldDescriptor lock) { + record InstanceAndLock(FieldDescriptor instance, FieldDescriptor lock, FieldDescriptor lockUpdater) { } }