Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion grails-encoder/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,13 @@ dependencies {
apply {
from rootProject.layout.projectDirectory.file('gradle/docs-config.gradle')
from rootProject.layout.projectDirectory.file('gradle/test-config.gradle')
}
}

tasks.register('codecMetaClassBenchmark', JavaExec) {
Comment thread
jamesfredley marked this conversation as resolved.
Outdated
group = 'verification'
description = 'Runs the codec metaclass registration benchmark for Grails issue 15374.'
classpath = sourceSets.test.runtimeClasspath
mainClass = 'org.grails.encoder.CodecMetaClassBenchmark'
systemProperties System.properties.findAll { key, value -> key.toString().startsWith('grails.codec.benchmark.') }
dependsOn testClasses
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
*/
package org.grails.encoder

import java.lang.ref.WeakReference
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicLong

import groovy.transform.CompileStatic
import org.codehaus.groovy.runtime.GStringImpl
import org.codehaus.groovy.runtime.NullObject
Expand All @@ -38,6 +42,8 @@ class CodecMetaClassSupport {
static final Object[] EMPTY_ARGS = []
static final String ENCODE_AS_PREFIX = 'encodeAs'
static final String DECODE_PREFIX = 'decode'
private static final Set<MetaMethodRegistrationKey> REGISTERED_META_METHODS = Collections.newSetFromMap(new ConcurrentHashMap<MetaMethodRegistrationKey, Boolean>())
Comment thread
jamesfredley marked this conversation as resolved.
Outdated
private static final AtomicLong META_METHOD_REGISTRATION_COUNT = new AtomicLong()

/**
* Adds "encodeAs*" and "decode*" metamethods for given codecClass
Expand Down Expand Up @@ -101,14 +107,14 @@ class CodecMetaClassSupport {
}
}

addMetaMethod(targetMetaClasses, encodeMethodName, encoderClosure)
addMetaMethod(targetMetaClasses, encodeMethodName, encoderClosure, cacheLookup, codecFactory)
if (codecFactory.encoder) {
addAliasMetaMethods(targetMetaClasses, codecFactory.encoder.codecIdentifier.codecAliases, encodeMethodNameClosure, encoderClosure)
addAliasMetaMethods(targetMetaClasses, codecFactory.encoder.codecIdentifier.codecAliases, encodeMethodNameClosure, encoderClosure, cacheLookup, codecFactory)
}

addMetaMethod(targetMetaClasses, decodeMethodName, decoderClosure)
addMetaMethod(targetMetaClasses, decodeMethodName, decoderClosure, cacheLookup, codecFactory)
if (codecFactory.decoder) {
addAliasMetaMethods(targetMetaClasses, codecFactory.decoder.codecIdentifier.codecAliases, decodeMethodNameClosure, decoderClosure)
addAliasMetaMethods(targetMetaClasses, codecFactory.decoder.codecIdentifier.codecAliases, decodeMethodNameClosure, decoderClosure, cacheLookup, codecFactory)
}
}

Expand All @@ -130,12 +136,18 @@ class CodecMetaClassSupport {

@CompileStatic
private addAliasMetaMethods(List<ExpandoMetaClass> targetMetaClasses, Set<String> aliases, Closure<String> methodNameClosure, Closure methodClosure) {
addAliasMetaMethods(targetMetaClasses, aliases, methodNameClosure, methodClosure, false, null)
}

@CompileStatic
private addAliasMetaMethods(List<ExpandoMetaClass> targetMetaClasses, Set<String> aliases, Closure<String> methodNameClosure, Closure methodClosure,
boolean cacheLookup, CodecFactory codecFactory) {
aliases?.each { String aliasName ->
addMetaMethod(targetMetaClasses, methodNameClosure(aliasName), methodClosure)
addMetaMethod(targetMetaClasses, methodNameClosure(aliasName), methodClosure, cacheLookup, codecFactory)
}
}

private String resolveCodecName(CodecFactory codecFactory) {
private static String resolveCodecName(CodecFactory codecFactory) {
codecFactory.encoder?.codecIdentifier?.codecName ?: codecFactory.decoder?.codecIdentifier?.codecName
}

Expand All @@ -152,8 +164,102 @@ class CodecMetaClassSupport {
}

protected void addMetaMethod(List<ExpandoMetaClass> targetMetaClasses, String methodName, Closure closure) {
addMetaMethod(targetMetaClasses, methodName, closure, false, null)
}

protected void addMetaMethod(List<ExpandoMetaClass> targetMetaClasses, String methodName, Closure closure, boolean cacheLookup, CodecFactory codecFactory) {
targetMetaClasses.each { ExpandoMetaClass emc ->
emc."${methodName}" << closure
if (!cacheLookup) {
emc."${methodName}" << closure
META_METHOD_REGISTRATION_COUNT.incrementAndGet()
}
else {
synchronized (emc) {
if (shouldRegisterMetaMethod(emc, methodName, codecFactory)) {
emc."${methodName}" << closure
META_METHOD_REGISTRATION_COUNT.incrementAndGet()
}
}
}
}
}

@CompileStatic
protected static long getMetaMethodRegistrationCount() {
META_METHOD_REGISTRATION_COUNT.get()
}

@CompileStatic
protected static int getMetaMethodRegistrationKeyCount() {
REGISTERED_META_METHODS.size()
}

@CompileStatic
protected static void clearMetaMethodRegistrationState() {
REGISTERED_META_METHODS.clear()
META_METHOD_REGISTRATION_COUNT.set(0L)
}

@CompileStatic
private static boolean shouldRegisterMetaMethod(ExpandoMetaClass emc, String methodName, CodecFactory codecFactory) {
removeStaleMetaMethodRegistrationKeys()
MetaMethodRegistrationKey key = registrationKey(emc, methodName, codecFactory)
REGISTERED_META_METHODS.add(key) || emc.getMetaMethod(methodName, EMPTY_ARGS) == null
}

@CompileStatic
private static void removeStaleMetaMethodRegistrationKeys() {
REGISTERED_META_METHODS.removeIf { MetaMethodRegistrationKey key -> key.stale }
}

@CompileStatic
private static MetaMethodRegistrationKey registrationKey(ExpandoMetaClass emc, String methodName, CodecFactory codecFactory) {
new MetaMethodRegistrationKey(emc.getTheClass().getName(), methodName, codecFactory)
}

@CompileStatic
private static class MetaMethodRegistrationKey {

private final String targetClassName
private final String methodName
private final WeakReference<CodecFactory> codecFactoryReference
private final int codecFactoryIdentityHashCode

MetaMethodRegistrationKey(String targetClassName, String methodName, CodecFactory codecFactory) {
this.targetClassName = targetClassName
this.methodName = methodName
this.codecFactoryReference = new WeakReference<CodecFactory>(codecFactory)
this.codecFactoryIdentityHashCode = System.identityHashCode(codecFactory)
}

@Override
boolean equals(Object other) {
if (this.is(other)) {
return true
}
if (!(other instanceof MetaMethodRegistrationKey)) {
return false
}

MetaMethodRegistrationKey otherKey = (MetaMethodRegistrationKey) other
CodecFactory codecFactory = codecFactoryReference.get()
CodecFactory otherCodecFactory = otherKey.codecFactoryReference.get()
codecFactory != null &&
otherCodecFactory != null &&
codecFactory.is(otherCodecFactory) &&
targetClassName == otherKey.targetClassName &&
methodName == otherKey.methodName
}

@Override
int hashCode() {
int result = targetClassName.hashCode()
result = 31 * result + methodName.hashCode()
31 * result + codecFactoryIdentityHashCode
}

boolean isStale() {
codecFactoryReference.get() == null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.grails.encoder

import java.util.concurrent.atomic.AtomicInteger

import groovy.transform.CompileStatic
import groovy.transform.TypeCheckingMode
import groovy.lang.MetaClassRegistryChangeEventListener
import org.codehaus.groovy.runtime.InvokerHelper

import grails.util.GrailsMetaClassUtils

@CompileStatic
class CodecMetaClassBenchmark {

private static final int REGISTRATION_ITERATIONS = Integer.getInteger('grails.codec.benchmark.registrationIterations', 10_000)
private static final int ENCODE_ITERATIONS = Integer.getInteger('grails.codec.benchmark.encodeIterations', 1_000_000)
private static final int ENCODE_WARMUP_ITERATIONS = Integer.getInteger('grails.codec.benchmark.encodeWarmupIterations', 100_000)
private static final boolean NEW_FACTORY_EACH_REGISTRATION = Boolean.getBoolean('grails.codec.benchmark.newFactoryEachRegistration')

static void main(String[] args) {
GroovySystem.metaClassRegistry.removeMetaClass(CodecBenchmarkTarget)
CodecMetaClassSupport support = new CodecMetaClassSupport()
BenchmarkCodecFactory codecFactory = new BenchmarkCodecFactory()
List<ExpandoMetaClass> targetMetaClasses = [GrailsMetaClassUtils.getExpandoMetaClass(CodecBenchmarkTarget)]
AtomicInteger changeEvents = new AtomicInteger()
MetaClassRegistryChangeEventListener listener = { changeEvents.incrementAndGet() } as MetaClassRegistryChangeEventListener
GroovySystem.metaClassRegistry.addMetaClassRegistryChangeEventListener(listener)
try {
long registrationStart = System.nanoTime()
for (int i = 0; i < REGISTRATION_ITERATIONS; i++) {
CodecFactory registrationFactory = NEW_FACTORY_EACH_REGISTRATION ? new BenchmarkCodecFactory() : codecFactory
support.configureCodecMethods(registrationFactory, true, targetMetaClasses)
}
long registrationNanos = System.nanoTime() - registrationStart

CodecBenchmarkTarget target = new CodecBenchmarkTarget('a&b<c>d')
for (int i = 0; i < ENCODE_WARMUP_ITERATIONS; i++) {
encode(target)
}
long encodeStart = System.nanoTime()
Object encoded = null
for (int i = 0; i < ENCODE_ITERATIONS; i++) {
encoded = encode(target)
}
long encodeNanos = System.nanoTime() - encodeStart

println "registrationIterations=${REGISTRATION_ITERATIONS}"
println "newFactoryEachRegistration=${NEW_FACTORY_EACH_REGISTRATION}"
println "registrationNanos=${registrationNanos}"
println "registrationNanosPerOp=${registrationNanos / REGISTRATION_ITERATIONS}"
println "metaClassChangeEvents=${changeEvents.get()}"
println "codecMetaMethodRegistrations=${codecMetaMethodRegistrations()}"
println "encodeAsBenchmarkMetaMethods=${countEncodeAsBenchmarkMetaMethods(targetMetaClasses)}"
println "encodeIterations=${ENCODE_ITERATIONS}"
println "encodeNanos=${encodeNanos}"
println "encodeNanosPerOp=${encodeNanos / ENCODE_ITERATIONS}"
println "lastEncoded=${encoded}"
} finally {
GroovySystem.metaClassRegistry.removeMetaClassRegistryChangeEventListener(listener)
GroovySystem.metaClassRegistry.removeMetaClass(CodecBenchmarkTarget)
}
}

@CompileStatic(TypeCheckingMode.SKIP)
private static Object encode(CodecBenchmarkTarget target) {
InvokerHelper.invokeMethod(target, 'encodeAsBenchmark', null)
}

@CompileStatic(TypeCheckingMode.SKIP)
private static long codecMetaMethodRegistrations() {
try {
CodecMetaClassSupport.getMetaMethodRegistrationCount()
} catch (MissingMethodException ignored) {
-1L
}
}

@CompileStatic(TypeCheckingMode.SKIP)
private static int countEncodeAsBenchmarkMetaMethods(List<ExpandoMetaClass> targetMetaClasses) {
targetMetaClasses.sum { ExpandoMetaClass emc ->
emc.metaMethods.count { it.name == 'encodeAsBenchmark' }
} as int
}

private static class CodecBenchmarkTarget {

private final String value

CodecBenchmarkTarget(String value) {
this.value = value
}

@Override
String toString() {
value
}
}

private static class BenchmarkCodecFactory implements CodecFactory {

private final BenchmarkEncoder encoder = new BenchmarkEncoder()

@Override
Encoder getEncoder() {
encoder
}

@Override
Decoder getDecoder() {
null
}
}

private static class BenchmarkEncoder implements Encoder {

private final CodecIdentifier codecIdentifier = new DefaultCodecIdentifier('Benchmark')

@Override
CodecIdentifier getCodecIdentifier() {
codecIdentifier
}

@Override
Object encode(Object o) {
o?.toString()?.replace('&', '&amp;')?.replace('<', '&lt;')?.replace('>', '&gt;')
}

@Override
void markEncoded(CharSequence string) {
}

@Override
boolean isSafe() {
true
}

@Override
boolean isApplyToSafelyEncoded() {
false
}
}
}
Loading
Loading