From 17578fd25ab8885819f958d3382ad77e3bd512df Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Wed, 9 Oct 2024 14:41:38 -0400 Subject: [PATCH 1/8] chore: add heap benchmark and reduce allocations Signed-off-by: Todd Baert --- benchmark.txt | 239 ++++++++++++++++++ pom.xml | 28 +- .../openfeature/sdk/AbstractStructure.java | 16 +- .../java/dev/openfeature/sdk/HookSupport.java | 58 ++--- .../dev/openfeature/sdk/ImmutableContext.java | 7 +- .../openfeature/sdk/ImmutableStructure.java | 34 ++- .../dev/openfeature/sdk/MutableContext.java | 9 +- .../dev/openfeature/sdk/MutableStructure.java | 22 +- .../java/dev/openfeature/sdk/Structure.java | 15 +- .../sdk/benchmark/AllocationBenchmark.java | 55 ++++ .../sdk/benchmark/AllocationProfiler.java | 120 +++++++++ .../sdk/testutils/TestFlagsUtils.java | 22 +- 12 files changed, 544 insertions(+), 81 deletions(-) create mode 100644 benchmark.txt create mode 100644 src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java create mode 100644 src/test/java/dev/openfeature/sdk/benchmark/AllocationProfiler.java diff --git a/benchmark.txt b/benchmark.txt new file mode 100644 index 000000000..87b9dd106 --- /dev/null +++ b/benchmark.txt @@ -0,0 +1,239 @@ +[INFO] Scanning for projects... +[INFO] +[INFO] ------------------------< dev.openfeature:sdk >------------------------- +[INFO] Building OpenFeature Java SDK 1.12.0 +[INFO] from pom.xml +[INFO] --------------------------------[ jar ]--------------------------------- +[WARNING] Parameter 'encoding' is unknown for plugin 'maven-checkstyle-plugin:3.5.0:check (validate)' +[INFO] +[INFO] >>> jmh:0.2.2:benchmark (default-cli) > process-test-resources @ sdk >>> +[INFO] +[INFO] --- checkstyle:3.5.0:check (validate) @ sdk --- +[INFO] Starting audit... +Audit done. +[INFO] You have 0 Checkstyle violations. +[INFO] +[INFO] --- jacoco:0.8.12:prepare-agent (prepare-agent) @ sdk --- +[INFO] surefireArgLine set to -javaagent:/home/todd/.m2/repository/org/jacoco/org.jacoco.agent/0.8.12/org.jacoco.agent-0.8.12-runtime.jar=destfile=/home/todd/git/java-sdk/target/coverage-reports/jacoco-ut.exec +[INFO] +[INFO] --- resources:3.3.1:resources (default-resources) @ sdk --- +[INFO] skip non existing resourceDirectory /home/todd/git/java-sdk/src/main/resources +[INFO] +[INFO] --- compiler:3.13.0:compile (default-compile) @ sdk --- +[INFO] Recompiling the module because of changed source code. +[INFO] Compiling 65 source files with javac [debug target 1.8] to target/classes +[WARNING] bootstrap class path not set in conjunction with -source 8 +[WARNING] source value 8 is obsolete and will be removed in a future release +[WARNING] target value 8 is obsolete and will be removed in a future release +[WARNING] To suppress warnings about obsolete options, use -Xlint:-options. +[INFO] Annotation processing is enabled because one or more processors were found + on the class path. A future release of javac may disable annotation processing + unless at least one processor is specified by name (-processor), or a search + path is specified (--processor-path, --processor-module-path), or annotation + processing is enabled explicitly (-proc:only, -proc:full). + Use -Xlint:-options to suppress this message. + Use -proc:none to disable annotation processing. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/EventDetails.java:[9,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/ImmutableStructure.java:[22,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/MutableStructure.java:[19,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java:[27,26] finalize() in java.lang.Object has been deprecated and marked for removal +[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Some input files use or override a deprecated API. +[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Recompile with -Xlint:deprecation for details. +[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java: Some input files use unchecked or unsafe operations. +[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java: Recompile with -Xlint:unchecked for details. +[INFO] +[INFO] --- resources:3.3.1:testResources (default-testResources) @ sdk --- +[INFO] Copying 2 resources from src/test/resources to target/test-classes +[INFO] +[INFO] <<< jmh:0.2.2:benchmark (default-cli) < process-test-resources @ sdk <<< +[INFO] +[INFO] +[INFO] --- jmh:0.2.2:benchmark (default-cli) @ sdk --- +[INFO] Changes detected - recompiling the module! +[INFO] Compiling 52 source files to /home/todd/git/java-sdk/target/test-classes +[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/LockingTest.java: Some input files use or override a deprecated API. +[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/LockingTest.java: Recompile with -Xlint:deprecation for details. +[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java: Some input files use unchecked or unsafe operations. +[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java: Recompile with -Xlint:unchecked for details. +[INFO] Executing the JMH benchmarks +# JMH version: 1.37 +# VM version: JDK 21.0.4, OpenJDK 64-Bit Server VM, 21.0.4+7 +# VM invoker: /usr/lib/jvm/java-21-openjdk/bin/java +# VM options: -Xmx1024m -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xmx1024m -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC +# Blackhole mode: compiler (auto-detected, use -Djmh.blackhole.autoDetect=false to disable) +# Warmup: +# Measurement: 1 iterations, single-shot each +# Timeout: 10 min per iteration +# Threads: 1 thread +# Benchmark mode: Single shot invocation time +# Benchmark: dev.openfeature.sdk.benchmark.AllocationBenchmark.run + +# Run progress: 0.00% complete, ETA 00:00:00 +# Fork: 1 of 1 +[0.002s][warning][gc,init] Consider setting -Xms equal to -Xmx to avoid resizing hiccups +[0.002s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups +Iteration 1: num #instances #bytes class name (module) +------------------------------------------------------- + 1: 732138 35142624 java.util.HashMap (java.base@21.0.4) + 2: 250067 14003752 java.util.stream.ReferencePipeline$Head (java.base@21.0.4) + 3: 47792 9311464 [B (java.base@21.0.4) + 4: 300056 4800896 java.util.HashMap$EntrySet (java.base@21.0.4) + 5: 150000 4800000 java.util.ArrayList$ArrayListSpliterator (java.base@21.0.4) + 6: 105996 4506240 [Ljava.lang.Object; (java.base@21.0.4) + 7: 280124 4481984 dev.openfeature.sdk.ImmutableStructure + 8: 270124 4321984 dev.openfeature.sdk.ImmutableContext + 9: 100000 4000000 dev.openfeature.sdk.HookContext + 10: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder + 11: 120706 2896944 java.util.ArrayList (java.base@21.0.4) + 12: 50000 2800000 java.util.stream.ReferencePipeline$7 (java.base@21.0.4) + 13: 50000 2400000 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder + 14: 152 2227424 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) + 15: 135755 2172080 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x000070c57402eae8 + 16: 66533 2129056 java.util.ArrayList$Itr (java.base@21.0.4) + 17: 50062 2002480 java.util.Spliterators$ArraySpliterator (java.base@21.0.4) + 18: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails + 19: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation + 20: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder + 21: 50060 1601920 java.util.stream.Collectors$CollectorImpl (java.base@21.0.4) + 22: 50060 1601920 java.util.stream.ReduceOps$3ReducingSink (java.base@21.0.4) + 23: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) + 24: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x000070c57402fa78 + 25: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) + 26: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata + 27: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder + 28: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x000070c5740826c0 + 29: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions + 30: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder + 31: 50000 1200000 dev.openfeature.sdk.HookSupport$$Lambda/0x000070c574081500 + 32: 50000 1200000 dev.openfeature.sdk.HookSupport$$Lambda/0x000070c574081730 + 33: 50000 1200000 java.util.stream.ReferencePipeline$7$1 (java.base@21.0.4) + 34: 70131 1122096 java.util.Optional (java.base@21.0.4) + 35: 33185 1061920 java.util.stream.ReduceOps$3 (java.base@21.0.4) + 36: 4491 679416 [I (java.base@21.0.4) + 37: 26575 637800 java.lang.String (java.base@21.0.4) + 38: 1462 390280 [J (java.base@21.0.4) + 39: 11653 372896 java.util.HashMap$EntryIterator (java.base@21.0.4) + 40: 2356 288240 java.lang.Class (java.base@21.0.4) + 41: 4631 259336 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) + 42: 10001 240024 java.lang.Double (java.base@21.0.4) + 43: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) + 44: 10000 160000 dev.openfeature.sdk.Value + 45: 6008 144192 java.lang.StringBuilder (java.base@21.0.4) + 46: 180 140968 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) + 47: 3829 122528 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) + 48: 48 122168 [C (java.base@21.0.4) + 49: 1441 113592 [S (java.base@21.0.4) + 50: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) + 51: 3034 79648 [Ljava.lang.Class; (java.base@21.0.4) + 52: 1351 75656 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) + 53: 1561 74928 java.lang.invoke.MemberName (java.base@21.0.4) + 54: 334 74816 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) + 55: 1799 71960 java.lang.invoke.MethodType (java.base@21.0.4) + 56: 1088 69632 java.net.URL (java.base@21.0.4) + 57: 2011 64352 java.util.HashMap$Node (java.base@21.0.4) + 58: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) + 59: 3149 50384 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) + 60: 491 49608 [Ljava.util.HashMap$Node; (java.base@21.0.4) + 61: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) + 62: 1224 39168 java.io.File (java.base@21.0.4) + 63: 779 37392 jdk.internal.org.objectweb.asm.Frame (java.base@21.0.4) + 64: 243 25272 java.util.jar.JarFile$JarFileEntry (java.base@21.0.4) + 65: 794 25248 [Ljava.lang.String; (java.base@21.0.4) + 66: 622 24880 java.lang.NoSuchFieldException (java.base@21.0.4) + 67: 571 22840 java.util.LinkedHashMap$Entry (java.base@21.0.4) + 68: 474 22752 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) + 69: 691 22112 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) + 70: 828 19872 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) + 71: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) + 72: 118 17936 jdk.internal.org.objectweb.asm.ClassWriter (java.base@21.0.4) + 73: 380 16824 [Ljava.lang.invoke.LambdaForm$Name; (java.base@21.0.4) + 74: 625 15000 java.lang.Long (java.base@21.0.4) + 75: 463 14816 java.lang.invoke.LambdaForm$Name (java.base@21.0.4) + 76: 903 14448 java.lang.Object (java.base@21.0.4) + 77: 198 14256 java.lang.reflect.Constructor (java.base@21.0.4) + 78: 249 13944 java.util.zip.ZipFile$ZipFileInputStream (java.base@21.0.4) + 79: 334 13360 jdk.internal.org.objectweb.asm.Handler (java.base@21.0.4) + 80: 202 12928 java.util.concurrent.ConcurrentHashMap (java.base@21.0.4) + 81: 201 12864 jdk.internal.org.objectweb.asm.FieldWriter (java.base@21.0.4) + 82: 316 12640 java.util.WeakHashMap$Entry (java.base@21.0.4) + 83: 102 12240 java.io.ObjectStreamClass (java.base@21.0.4) + 84: 249 11952 java.util.zip.ZipFile$ZipFileInflaterInputStream (java.base@21.0.4) + 85: 359 11488 jdk.internal.org.objectweb.asm.Type (java.base@21.0.4) + 86: 465 11160 java.lang.invoke.ResolvedMethodName (java.base@21.0.4) + 87: 464 11136 jdk.internal.org.objectweb.asm.Edge (java.base@21.0.4) + 88: 341 10912 jdk.internal.math.FDBigInteger (java.base@21.0.4) + 89: 94 10728 [Ljava.lang.reflect.Field; (java.base@21.0.4) + 90: 266 10640 java.lang.NoSuchMethodException (java.base@21.0.4) + 91: 266 10640 java.security.CodeSource (java.base@21.0.4) + 92: 220 10560 java.lang.invoke.DirectMethodHandle$Constructor (java.base@21.0.4) + 93: 264 10560 sun.security.util.KnownOIDs (java.base@21.0.4) + 94: 75 10200 sun.nio.fs.UnixFileAttributes (java.base@21.0.4) + 95: 123 9840 jdk.internal.event.DeserializationEvent (java.base@21.0.4) + 96: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) + 97: 115 9200 [Ljava.util.WeakHashMap$Entry; (java.base@21.0.4) + 98: 368 8832 java.lang.module.ModuleDescriptor$Exports (java.base@21.0.4) + 99: 63 8384 [Ljava.lang.invoke.MethodHandle; (java.base@21.0.4) + 100: 146 8176 java.io.FileCleanable (java.base@21.0.4) + 101: 125 8000 java.lang.Class$ReflectionData (java.base@21.0.4) + 102: 323 7752 java.util.ImmutableCollections$Set12 (java.base@21.0.4) + 103: 121 7744 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) + 104: 70 7280 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) + 105: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) + 106: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) + 107: 199 6368 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) + 108: 156 6240 java.util.StringJoiner (java.base@21.0.4) + 109: 153 6120 java.io.FileDescriptor (java.base@21.0.4) + 110: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) + 111: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) + 112: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (java.base@21.0.4) + 113: 373 5968 java.lang.Byte (java.base@21.0.4) + 114: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) + 115: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) + 116: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) + 117: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) + 118: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) +truncated... +Total 4045727 138758864 + +0.157 s/op + +totalAllocatedBytes: 138758864.000 bytes + +totalAllocatedInstances: 4045727.000 instances + +totalHeap: 521412608.000 bytes + + + +Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedBytes": + 138758864.000 bytes + +Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances": + 4045727.000 instances + +Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalHeap": + 521412608.000 bytes + + +# Run complete. Total time: 00:00:00 + +REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on +why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial +experiments, perform baseline and negative tests that provide experimental control, make sure +the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. +Do not assume the numbers tell you what you want them to tell. + +NOTE: Current JVM experimentally supports Compiler Blackholes, and they are in use. Please exercise +extra caution when trusting the results, look into the generated code to check the benchmark still +works, and factor in a small probability of new VM bugs. Additionally, while comparisons between +different JVMs are already problematic, the performance difference caused by different Blackhole +modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons. + +Benchmark Mode Cnt Score Error Units +AllocationBenchmark.run ss 0.157 s/op +AllocationBenchmark.run:+totalAllocatedBytes ss 138758864.000 bytes +AllocationBenchmark.run:+totalAllocatedInstances ss 4045727.000 instances +AllocationBenchmark.run:+totalHeap ss 521412608.000 bytes +[INFO] ------------------------------------------------------------------------ +[INFO] BUILD SUCCESS +[INFO] ------------------------------------------------------------------------ +[INFO] Total time: 5.275 s +[INFO] Finished at: 2024-10-10T09:59:32-04:00 +[INFO] ------------------------------------------------------------------------ diff --git a/pom.xml b/pom.xml index 335922bee..4b9f3836d 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 dev.openfeature @@ -11,7 +11,7 @@ 1.8 ${maven.compiler.source} 5.11.2 - + **/e2e/*.java ${project.groupId}.${project.artifactId} @@ -146,6 +146,13 @@ test + + org.openjdk.jmh + jmh-core + 1.37 + test + + @@ -473,7 +480,7 @@ 3.10.1 true - all,-missing + all,-missing @@ -507,6 +514,19 @@ + + benchmark + + + + pw.krejci + jmh-maven-plugin + 0.2.2 + + + + + e2e diff --git a/src/main/java/dev/openfeature/sdk/AbstractStructure.java b/src/main/java/dev/openfeature/sdk/AbstractStructure.java index e50fbe920..f3543f31e 100644 --- a/src/main/java/dev/openfeature/sdk/AbstractStructure.java +++ b/src/main/java/dev/openfeature/sdk/AbstractStructure.java @@ -6,10 +6,15 @@ @SuppressWarnings({ "PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType" }) abstract class AbstractStructure implements Structure { - protected final Map attributes; + private Map attributes; + + @Override + public boolean isEmpty() { + return attributes == null || attributes.size() == 0; + } AbstractStructure() { - this.attributes = new HashMap<>(); + // intentionally don't initialize the attributes - do this lazily } AbstractStructure(Map attributes) { @@ -32,4 +37,11 @@ public Map asObjectMap() { (accumulated, entry) -> accumulated.put(entry.getKey(), convertValue(entry.getValue())), HashMap::putAll); } + + protected Map getAttributes() { + if (attributes == null) { + attributes = new HashMap<>(); + } + return attributes; + } } diff --git a/src/main/java/dev/openfeature/sdk/HookSupport.java b/src/main/java/dev/openfeature/sdk/HookSupport.java index 52c5b9727..90f41b932 100644 --- a/src/main/java/dev/openfeature/sdk/HookSupport.java +++ b/src/main/java/dev/openfeature/sdk/HookSupport.java @@ -1,13 +1,11 @@ package dev.openfeature.sdk; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import java.util.stream.Stream; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,11 +17,7 @@ class HookSupport { public EvaluationContext beforeHooks(FlagValueType flagValueType, HookContext hookCtx, List hooks, Map hints) { - Stream result = callBeforeHooks(flagValueType, hookCtx, hooks, hints); - return hookCtx.getCtx().merge( - result.reduce(hookCtx.getCtx(), (EvaluationContext accumulated, EvaluationContext current) -> { - return accumulated.merge(current); - })); + return callBeforeHooks(flagValueType, hookCtx, hooks, hints); } public void afterHooks(FlagValueType flagValueType, HookContext hookContext, FlagEvaluationDetails details, @@ -46,10 +40,11 @@ private void executeHooks( String hookMethod, Consumer> hookCode) { if (hooks != null) { - hooks - .stream() - .filter(hook -> hook.supportsFlagValueType(flagValueType)) - .forEach(hook -> executeChecked(hook, hookCode, hookMethod)); + for (Hook hook : hooks) { + if (hook.supportsFlagValueType(flagValueType)) { + executeChecked(hook, hookCode, hookMethod); + } + } } } @@ -68,29 +63,28 @@ private void executeHooksUnchecked( FlagValueType flagValueType, List hooks, Consumer> hookCode) { if (hooks != null) { - hooks - .stream() - .filter(hook -> hook.supportsFlagValueType(flagValueType)) - .forEach(hookCode::accept); + for (Hook hook : hooks) { + if (hook.supportsFlagValueType(flagValueType)) { + hookCode.accept(hook); + } + } } } - private Stream callBeforeHooks(FlagValueType flagValueType, HookContext hookCtx, + private EvaluationContext callBeforeHooks(FlagValueType flagValueType, HookContext hookCtx, List hooks, Map hints) { // These traverse backwards from normal. - List reversedHooks = IntStream - .range(0, hooks.size()) - .map(i -> hooks.size() - 1 - i) - .mapToObj(hooks::get) - .collect(Collectors.toList()); - - return reversedHooks - .stream() - .filter(hook -> hook.supportsFlagValueType(flagValueType)) - .map(hook -> hook.before(hookCtx, hints)) - .filter(Objects::nonNull) - .filter(Optional::isPresent) - .map(Optional::get) - .map(EvaluationContext.class::cast); + List reversedHooks = new ArrayList<>(hooks); + Collections.reverse(reversedHooks); + EvaluationContext context = hookCtx.getCtx(); + for (Hook hook : reversedHooks) { + if (hook.supportsFlagValueType(flagValueType)) { + Optional optional = hook.before(hookCtx, hints); + if (optional != null && optional.isPresent()) { + context = context.merge(optional.get()); + } + } + } + return context; } } diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java index fd2ff2a68..578378032 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java @@ -78,8 +78,11 @@ public String getTargetingKey() { */ @Override public EvaluationContext merge(EvaluationContext overridingContext) { - if (overridingContext == null) { - return new ImmutableContext(this.asMap()); + if (overridingContext == null || overridingContext.isEmpty()) { + return this; + } + if (this.isEmpty()) { + return overridingContext; } return new ImmutableContext( diff --git a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java index d70a01637..57a68db9f 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Set; @@ -35,25 +36,18 @@ public ImmutableStructure() { * @param attributes attributes. */ public ImmutableStructure(Map attributes) { - super(new HashMap<>(attributes.entrySet() - .stream() - .collect(HashMap::new, - (accumulated, entry) -> accumulated.put(entry.getKey(), - Optional.ofNullable(entry.getValue()) - .map(Value::clone) - .orElse(null)), - HashMap::putAll))); + super(copyAttributes(attributes)); } @Override public Set keySet() { - return new HashSet<>(this.attributes.keySet()); + return new HashSet<>(this.getAttributes().keySet()); } // getters @Override public Value getValue(String key) { - Value value = this.attributes.get(key); + Value value = getAttributes().get(key); return value != null ? value.clone() : null; } @@ -64,14 +58,16 @@ public Value getValue(String key) { */ @Override public Map asMap() { - return attributes - .entrySet() - .stream() - .collect(HashMap::new, - (accumulated, entry) -> accumulated.put(entry.getKey(), - Optional.ofNullable(entry.getValue()) - .map(Value::clone) - .orElse(null)), - HashMap::putAll); + return copyAttributes(getAttributes()); } + + private static Map copyAttributes(Map in) { + Map copy = new HashMap<>(); + for (Entry entry : in.entrySet()) { + copy.put(entry.getKey(), + Optional.ofNullable(entry.getValue()).map((Value val) -> val.clone()).orElse(null)); + } + return copy; + } + } diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java index 653441d31..0104379f5 100644 --- a/src/main/java/dev/openfeature/sdk/MutableContext.java +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -46,7 +46,7 @@ public MutableContext(Map attributes) { public MutableContext(String targetingKey, Map attributes) { this.structure = new MutableStructure(attributes); if (targetingKey != null && !targetingKey.trim().isEmpty()) { - this.structure.attributes.put(TARGETING_KEY, new Value(targetingKey)); + this.structure.getAttributes().put(TARGETING_KEY, new Value(targetingKey)); } } @@ -114,8 +114,11 @@ public String getTargetingKey() { */ @Override public EvaluationContext merge(EvaluationContext overridingContext) { - if (overridingContext == null) { - return new MutableContext(this.asMap()); + if (overridingContext == null || overridingContext.isEmpty()) { + return this; + } + if (this.isEmpty()) { + return overridingContext; } Map merged = this.merge( diff --git a/src/main/java/dev/openfeature/sdk/MutableStructure.java b/src/main/java/dev/openfeature/sdk/MutableStructure.java index fadd68051..919c85746 100644 --- a/src/main/java/dev/openfeature/sdk/MutableStructure.java +++ b/src/main/java/dev/openfeature/sdk/MutableStructure.java @@ -30,53 +30,53 @@ public MutableStructure(Map attributes) { @Override public Set keySet() { - return this.attributes.keySet(); + return getAttributes().keySet(); } // getters @Override public Value getValue(String key) { - return this.attributes.get(key); + return getAttributes().get(key); } // adders public MutableStructure add(String key, Value value) { - attributes.put(key, value); + getAttributes().put(key, value); return this; } public MutableStructure add(String key, Boolean value) { - attributes.put(key, new Value(value)); + getAttributes().put(key, new Value(value)); return this; } public MutableStructure add(String key, String value) { - attributes.put(key, new Value(value)); + getAttributes().put(key, new Value(value)); return this; } public MutableStructure add(String key, Integer value) { - attributes.put(key, new Value(value)); + getAttributes().put(key, new Value(value)); return this; } public MutableStructure add(String key, Double value) { - attributes.put(key, new Value(value)); + getAttributes().put(key, new Value(value)); return this; } public MutableStructure add(String key, Instant value) { - attributes.put(key, new Value(value)); + getAttributes().put(key, new Value(value)); return this; } public MutableStructure add(String key, Structure value) { - attributes.put(key, new Value(value)); + getAttributes().put(key, new Value(value)); return this; } public MutableStructure add(String key, List value) { - attributes.put(key, new Value(value)); + getAttributes().put(key, new Value(value)); return this; } @@ -87,6 +87,6 @@ public MutableStructure add(String key, List value) { */ @Override public Map asMap() { - return new HashMap<>(this.attributes); + return new HashMap<>(getAttributes()); } } diff --git a/src/main/java/dev/openfeature/sdk/Structure.java b/src/main/java/dev/openfeature/sdk/Structure.java index f3768e958..02e36629e 100644 --- a/src/main/java/dev/openfeature/sdk/Structure.java +++ b/src/main/java/dev/openfeature/sdk/Structure.java @@ -18,6 +18,12 @@ @SuppressWarnings("PMD.BeanMembersShouldSerialize") public interface Structure { + /** + * Boolean indicating if this structure is empty. + * @return boolean for emptiness + */ + boolean isEmpty(); + /** * Get all keys. * @@ -113,7 +119,14 @@ default Object convertValue(Value value) { default Map merge(Function, Structure> newStructure, Map base, Map overriding) { - + + if (base.isEmpty()) { + return overriding; + } + if (overriding.isEmpty()) { + return base; + } + final Map merged = new HashMap<>(base); for (Entry overridingEntry : overriding.entrySet()) { String key = overridingEntry.getKey(); diff --git a/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java b/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java new file mode 100644 index 000000000..8034eb6e7 --- /dev/null +++ b/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java @@ -0,0 +1,55 @@ +package dev.openfeature.sdk.benchmark; + +import static dev.openfeature.sdk.testutils.TestFlagsUtils.BOOLEAN_FLAG_KEY; +import static dev.openfeature.sdk.testutils.TestFlagsUtils.FLOAT_FLAG_KEY; +import static dev.openfeature.sdk.testutils.TestFlagsUtils.INT_FLAG_KEY; +import static dev.openfeature.sdk.testutils.TestFlagsUtils.OBJECT_FLAG_KEY; +import static dev.openfeature.sdk.testutils.TestFlagsUtils.STRING_FLAG_KEY; + +import java.util.Map; +import java.util.Optional; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.Warmup; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.Hook; +import dev.openfeature.sdk.HookContext; +import dev.openfeature.sdk.ImmutableContext; +import dev.openfeature.sdk.ImmutableStructure; +import dev.openfeature.sdk.NoOpProvider; +import dev.openfeature.sdk.OpenFeatureAPI; +import dev.openfeature.sdk.Value; + +public class AllocationBenchmark { + + @Benchmark + @Warmup(iterations = 0) + @Measurement(iterations = 1) + @BenchmarkMode(Mode.SingleShotTime) + @Fork(jvmArgsAppend = { "-Xmx1024m", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseEpsilonGC" }) + public void run() { + + OpenFeatureAPI.getInstance().setProviderAndWait(new NoOpProvider()); + Client client = OpenFeatureAPI.getInstance().getClient(); + client.addHooks(new Hook() { + @Override + public Optional before(HookContext ctx, Map hints) { + return Optional.ofNullable(new ImmutableContext()); + } + }); + + for (int i = 0; i < 10000; i++) { + client.getBooleanValue(BOOLEAN_FLAG_KEY, false); + client.getStringValue(STRING_FLAG_KEY, "default"); + client.getIntegerValue(INT_FLAG_KEY, 0); + client.getDoubleValue(FLOAT_FLAG_KEY, 0.0); + client.getObjectDetails(OBJECT_FLAG_KEY, new Value(new ImmutableStructure()), new ImmutableContext()); + } + } +} diff --git a/src/test/java/dev/openfeature/sdk/benchmark/AllocationProfiler.java b/src/test/java/dev/openfeature/sdk/benchmark/AllocationProfiler.java new file mode 100644 index 000000000..2725c85f6 --- /dev/null +++ b/src/test/java/dev/openfeature/sdk/benchmark/AllocationProfiler.java @@ -0,0 +1,120 @@ +package dev.openfeature.sdk.benchmark; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.LineNumberReader; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Collection; + +import org.openjdk.jmh.infra.BenchmarkParams; +import org.openjdk.jmh.infra.IterationParams; +import org.openjdk.jmh.profile.InternalProfiler; +import org.openjdk.jmh.results.AggregationPolicy; +import org.openjdk.jmh.results.IterationResult; +import org.openjdk.jmh.results.Result; +import org.openjdk.jmh.results.ScalarResult; +import org.openjdk.jmh.util.Utils; + +public class AllocationProfiler implements InternalProfiler { + + public static class AllocationTotals { + long instances; + long bytes; + + public AllocationTotals(long instances, long bytes) { + this.instances = instances; + this.bytes = bytes; + } + } + + @Override + public String getDescription() { + return "Max memory heap profiler"; + } + + @Override + public void beforeIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams) { + // intentionally left blank + } + + @Override + public Collection afterIteration(BenchmarkParams benchmarkParams, IterationParams iterationParams, + IterationResult result) { + + long totalHeap = Runtime.getRuntime().totalMemory(); + AllocationTotals allocationTotals = AllocationProfiler.printHeapHistogram(System.out, 120); + + Collection results = new ArrayList<>(); + results.add(new ScalarResult("+totalHeap", totalHeap, "bytes", AggregationPolicy.MAX)); + results.add(new ScalarResult("+totalAllocatedInstances", allocationTotals.instances, "instances", + AggregationPolicy.MAX)); + results.add(new ScalarResult("+totalAllocatedBytes", allocationTotals.bytes, "bytes", AggregationPolicy.MAX)); + + return results; + } + + private static String getJmapExcutable() { + String javaHome = System.getProperty("java.home"); + String jreDir = File.separator + "jre"; + if (javaHome.endsWith(jreDir)) { + javaHome = javaHome.substring(0, javaHome.length() - jreDir.length()); + } + return (javaHome + + File.separator + + "bin" + + File.separator + + "jmap" + + (Utils.isWindows() ? ".exe" : "")); + } + + // runs JMAP executable in a new process to collect a heap dump + // heavily inspired by: https://github.com/cache2k/cache2k-benchmark/blob/master/jmh-suite/src/main/java/org/cache2k/benchmark/jmh/HeapProfiler.java + private static AllocationTotals printHeapHistogram(PrintStream out, int maxLines) { + long totalBytes = 0; + long totalInstances = 0; + boolean partial = false; + try { + Process jmapProcess = Runtime.getRuntime().exec(new String[] { + getJmapExcutable(), + "-histo:live", + Long.toString(Utils.getPid()) }); + InputStream in = jmapProcess.getInputStream(); + LineNumberReader r = new LineNumberReader(new InputStreamReader(in)); + String line; + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + PrintStream printStream = new PrintStream(buffer); + while ((line = r.readLine()) != null) { + if (line.startsWith("Total")) { + printStream.println(line); + String[] tokens = line.split("\\s+"); + totalInstances += Long.parseLong(tokens[1]); + totalBytes = Long.parseLong(tokens[2]); + } else if (r.getLineNumber() <= maxLines) { + printStream.println(line); + } else { + if (!partial) { + printStream.println("truncated..."); + } + partial = true; + } + } + r.close(); + in.close(); + printStream.close(); + byte[] histogramOutput = buffer.toByteArray(); + buffer = new ByteArrayOutputStream(); + printStream = new PrintStream(buffer); + printStream.write(histogramOutput); + printStream.println(); + printStream.close(); + out.write(buffer.toByteArray()); + } catch (Exception ex) { + System.err.println("ForcedGcMemoryProfiler: error attaching / reading histogram"); + ex.printStackTrace(); + } + return new AllocationTotals(totalInstances, totalBytes); + } +} \ No newline at end of file diff --git a/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java b/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java index d90359294..d7f436c24 100644 --- a/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java +++ b/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java @@ -16,33 +16,41 @@ @UtilityClass public class TestFlagsUtils { + public static String BOOLEAN_FLAG_KEY = "boolean-flag"; + public static String STRING_FLAG_KEY = "string-flag"; + public static String INT_FLAG_KEY = "integer-flag"; + public static String FLOAT_FLAG_KEY = "float-flag"; + public static String OBJECT_FLAG_KEY = "object-flag"; + public static String CONTEXT_AWARE_FLAG_KEY = "context-aware"; + public static String WRONG_FLAG_KEY = "wrong-flag"; + /** * Building flags for testing purposes. * @return map of flags */ public static Map> buildFlags() { Map> flags = new HashMap<>(); - flags.put("boolean-flag", Flag.builder() + flags.put(BOOLEAN_FLAG_KEY, Flag.builder() .variant("on", true) .variant("off", false) .defaultVariant("on") .build()); - flags.put("string-flag", Flag.builder() + flags.put(STRING_FLAG_KEY, Flag.builder() .variant("greeting", "hi") .variant("parting", "bye") .defaultVariant("greeting") .build()); - flags.put("integer-flag", Flag.builder() + flags.put(INT_FLAG_KEY, Flag.builder() .variant("one", 1) .variant("ten", 10) .defaultVariant("ten") .build()); - flags.put("float-flag", Flag.builder() + flags.put(FLOAT_FLAG_KEY, Flag.builder() .variant("tenth", 0.1) .variant("half", 0.5) .defaultVariant("half") .build()); - flags.put("object-flag", Flag.builder() + flags.put(OBJECT_FLAG_KEY, Flag.builder() .variant("empty", new HashMap<>()) .variant("template", new Value(mapToStructure(ImmutableMap.of( "showImages", new Value(true), @@ -51,7 +59,7 @@ public static Map> buildFlags() { )))) .defaultVariant("template") .build()); - flags.put("context-aware", Flag.builder() + flags.put(CONTEXT_AWARE_FLAG_KEY, Flag.builder() .variant("internal", "INTERNAL") .variant("external", "EXTERNAL") .defaultVariant("external") @@ -63,7 +71,7 @@ public static Map> buildFlags() { } }) .build()); - flags.put("wrong-flag", Flag.builder() + flags.put(WRONG_FLAG_KEY, Flag.builder() .variant("one", "uno") .variant("two", "dos") .defaultVariant("one") From fae1582a7a7d1f711c2e8fef7554f7b76b4aff55 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 10 Oct 2024 10:53:20 -0400 Subject: [PATCH 2/8] fixup: sonar Signed-off-by: Todd Baert --- src/main/java/dev/openfeature/sdk/HookSupport.java | 5 +++-- .../openfeature/sdk/testutils/TestFlagsUtils.java | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/dev/openfeature/sdk/HookSupport.java b/src/main/java/dev/openfeature/sdk/HookSupport.java index 90f41b932..f0216b255 100644 --- a/src/main/java/dev/openfeature/sdk/HookSupport.java +++ b/src/main/java/dev/openfeature/sdk/HookSupport.java @@ -79,8 +79,9 @@ private EvaluationContext callBeforeHooks(FlagValueType flagValueType, HookConte EvaluationContext context = hookCtx.getCtx(); for (Hook hook : reversedHooks) { if (hook.supportsFlagValueType(flagValueType)) { - Optional optional = hook.before(hookCtx, hints); - if (optional != null && optional.isPresent()) { + Optional optional = Optional.ofNullable(hook.before(hookCtx, hints)) + .orElse(Optional.empty()); + if (optional.isPresent()) { context = context.merge(optional.get()); } } diff --git a/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java b/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java index d7f436c24..dd2d03ca1 100644 --- a/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java +++ b/src/test/java/dev/openfeature/sdk/testutils/TestFlagsUtils.java @@ -16,13 +16,13 @@ @UtilityClass public class TestFlagsUtils { - public static String BOOLEAN_FLAG_KEY = "boolean-flag"; - public static String STRING_FLAG_KEY = "string-flag"; - public static String INT_FLAG_KEY = "integer-flag"; - public static String FLOAT_FLAG_KEY = "float-flag"; - public static String OBJECT_FLAG_KEY = "object-flag"; - public static String CONTEXT_AWARE_FLAG_KEY = "context-aware"; - public static String WRONG_FLAG_KEY = "wrong-flag"; + public static final String BOOLEAN_FLAG_KEY = "boolean-flag"; + public static final String STRING_FLAG_KEY = "string-flag"; + public static final String INT_FLAG_KEY = "integer-flag"; + public static final String FLOAT_FLAG_KEY = "float-flag"; + public static final String OBJECT_FLAG_KEY = "object-flag"; + public static final String CONTEXT_AWARE_FLAG_KEY = "context-aware"; + public static final String WRONG_FLAG_KEY = "wrong-flag"; /** * Building flags for testing purposes. From e68f221f2340a5674b0730dc74f389cdb5b3b8b8 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 10 Oct 2024 11:05:17 -0400 Subject: [PATCH 3/8] fixup: mutabilit Signed-off-by: Todd Baert --- benchmark.txt | 110 +++++++++--------- .../dev/openfeature/sdk/ImmutableContext.java | 4 +- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/benchmark.txt b/benchmark.txt index 87b9dd106..814984dc4 100644 --- a/benchmark.txt +++ b/benchmark.txt @@ -33,9 +33,9 @@ Audit done. processing is enabled explicitly (-proc:only, -proc:full). Use -Xlint:-options to suppress this message. Use -proc:none to disable annotation processing. -[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/EventDetails.java:[9,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. -[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/ImmutableStructure.java:[22,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. [WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/MutableStructure.java:[19,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/ImmutableStructure.java:[22,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/EventDetails.java:[9,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. [WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java:[27,26] finalize() in java.lang.Object has been deprecated and marked for removal [INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Some input files use or override a deprecated API. [INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Recompile with -Xlint:deprecation for details. @@ -70,57 +70,57 @@ Audit done. # Run progress: 0.00% complete, ETA 00:00:00 # Fork: 1 of 1 -[0.002s][warning][gc,init] Consider setting -Xms equal to -Xmx to avoid resizing hiccups -[0.002s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups +[0.001s][warning][gc,init] Consider setting -Xms equal to -Xmx to avoid resizing hiccups +[0.001s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups Iteration 1: num #instances #bytes class name (module) ------------------------------------------------------- - 1: 732138 35142624 java.util.HashMap (java.base@21.0.4) + 1: 1140090 54724320 java.util.HashMap (java.base@21.0.4) 2: 250067 14003752 java.util.stream.ReferencePipeline$Head (java.base@21.0.4) - 3: 47792 9311464 [B (java.base@21.0.4) - 4: 300056 4800896 java.util.HashMap$EntrySet (java.base@21.0.4) - 5: 150000 4800000 java.util.ArrayList$ArrayListSpliterator (java.base@21.0.4) - 6: 105996 4506240 [Ljava.lang.Object; (java.base@21.0.4) - 7: 280124 4481984 dev.openfeature.sdk.ImmutableStructure - 8: 270124 4321984 dev.openfeature.sdk.ImmutableContext + 3: 700056 11200896 java.util.HashMap$EntrySet (java.base@21.0.4) + 4: 47801 9303280 [B (java.base@21.0.4) + 5: 486170 7778720 dev.openfeature.sdk.ImmutableStructure + 6: 476170 7618720 dev.openfeature.sdk.ImmutableContext + 7: 150000 4800000 java.util.ArrayList$ArrayListSpliterator (java.base@21.0.4) + 8: 105995 4506088 [Ljava.lang.Object; (java.base@21.0.4) 9: 100000 4000000 dev.openfeature.sdk.HookContext 10: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder - 11: 120706 2896944 java.util.ArrayList (java.base@21.0.4) + 11: 126752 3042048 java.util.ArrayList (java.base@21.0.4) 12: 50000 2800000 java.util.stream.ReferencePipeline$7 (java.base@21.0.4) - 13: 50000 2400000 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder - 14: 152 2227424 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) - 15: 135755 2172080 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x000070c57402eae8 - 16: 66533 2129056 java.util.ArrayList$Itr (java.base@21.0.4) - 17: 50062 2002480 java.util.Spliterators$ArraySpliterator (java.base@21.0.4) - 18: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails - 19: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation - 20: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder - 21: 50060 1601920 java.util.stream.Collectors$CollectorImpl (java.base@21.0.4) - 22: 50060 1601920 java.util.stream.ReduceOps$3ReducingSink (java.base@21.0.4) - 23: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) - 24: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x000070c57402fa78 - 25: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) - 26: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata - 27: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder - 28: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x000070c5740826c0 - 29: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions - 30: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder - 31: 50000 1200000 dev.openfeature.sdk.HookSupport$$Lambda/0x000070c574081500 - 32: 50000 1200000 dev.openfeature.sdk.HookSupport$$Lambda/0x000070c574081730 - 33: 50000 1200000 java.util.stream.ReferencePipeline$7$1 (java.base@21.0.4) - 34: 70131 1122096 java.util.Optional (java.base@21.0.4) - 35: 33185 1061920 java.util.stream.ReduceOps$3 (java.base@21.0.4) - 36: 4491 679416 [I (java.base@21.0.4) - 37: 26575 637800 java.lang.String (java.base@21.0.4) - 38: 1462 390280 [J (java.base@21.0.4) - 39: 11653 372896 java.util.HashMap$EntryIterator (java.base@21.0.4) - 40: 2356 288240 java.lang.Class (java.base@21.0.4) + 13: 50062 2002480 java.util.Spliterators$ArraySpliterator (java.base@21.0.4) + 14: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails + 15: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation + 16: 160 1799192 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) + 17: 51775 1656800 java.util.ArrayList$Itr (java.base@21.0.4) + 18: 50060 1601920 java.util.stream.Collectors$CollectorImpl (java.base@21.0.4) + 19: 50060 1601920 java.util.stream.ReduceOps$3ReducingSink (java.base@21.0.4) + 20: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) + 21: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x00007c562402fa78 + 22: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) + 23: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata + 24: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x00007c5624082430 + 25: 99350 1589600 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder + 26: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions + 27: 50000 1200000 java.util.stream.ReferencePipeline$7$1 (java.base@21.0.4) + 28: 71122 1137952 java.util.Optional (java.base@21.0.4) + 29: 18775 901200 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder + 30: 22140 885600 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder + 31: 34680 832320 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder + 32: 4491 679416 [I (java.base@21.0.4) + 33: 26582 637968 java.lang.String (java.base@21.0.4) + 34: 38046 608736 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x00007c562402eae8 + 35: 24049 577176 dev.openfeature.sdk.HookSupport$$Lambda/0x00007c5624081478 + 36: 21192 508608 dev.openfeature.sdk.HookSupport$$Lambda/0x00007c5624081248 + 37: 1462 390280 [J (java.base@21.0.4) + 38: 11678 373696 java.util.stream.ReduceOps$3 (java.base@21.0.4) + 39: 2356 288208 java.lang.Class (java.base@21.0.4) + 40: 8513 272416 java.util.HashMap$EntryIterator (java.base@21.0.4) 41: 4631 259336 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) 42: 10001 240024 java.lang.Double (java.base@21.0.4) 43: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) 44: 10000 160000 dev.openfeature.sdk.Value - 45: 6008 144192 java.lang.StringBuilder (java.base@21.0.4) + 45: 6009 144216 java.lang.StringBuilder (java.base@21.0.4) 46: 180 140968 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) - 47: 3829 122528 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) + 47: 3827 122464 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) 48: 48 122168 [C (java.base@21.0.4) 49: 1441 113592 [S (java.base@21.0.4) 50: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) @@ -129,13 +129,13 @@ Iteration 1: num #instances #bytes class name (module) 53: 1561 74928 java.lang.invoke.MemberName (java.base@21.0.4) 54: 334 74816 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) 55: 1799 71960 java.lang.invoke.MethodType (java.base@21.0.4) - 56: 1088 69632 java.net.URL (java.base@21.0.4) + 56: 1089 69696 java.net.URL (java.base@21.0.4) 57: 2011 64352 java.util.HashMap$Node (java.base@21.0.4) 58: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) 59: 3149 50384 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) 60: 491 49608 [Ljava.util.HashMap$Node; (java.base@21.0.4) 61: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) - 62: 1224 39168 java.io.File (java.base@21.0.4) + 62: 1225 39200 java.io.File (java.base@21.0.4) 63: 779 37392 jdk.internal.org.objectweb.asm.Frame (java.base@21.0.4) 64: 243 25272 java.util.jar.JarFile$JarFileEntry (java.base@21.0.4) 65: 794 25248 [Ljava.lang.String; (java.base@21.0.4) @@ -193,20 +193,20 @@ Iteration 1: num #instances #bytes class name (module) 117: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) 118: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) truncated... -Total 4045727 138758864 +Total 5005903 163928784 -0.157 s/op - +totalAllocatedBytes: 138758864.000 bytes - +totalAllocatedInstances: 4045727.000 instances +0.116 s/op + +totalAllocatedBytes: 163928784.000 bytes + +totalAllocatedInstances: 5005903.000 instances +totalHeap: 521412608.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedBytes": - 138758864.000 bytes + 163928784.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances": - 4045727.000 instances + 5005903.000 instances Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalHeap": 521412608.000 bytes @@ -227,13 +227,13 @@ different JVMs are already problematic, the performance difference caused by dif modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons. Benchmark Mode Cnt Score Error Units -AllocationBenchmark.run ss 0.157 s/op -AllocationBenchmark.run:+totalAllocatedBytes ss 138758864.000 bytes -AllocationBenchmark.run:+totalAllocatedInstances ss 4045727.000 instances +AllocationBenchmark.run ss 0.116 s/op +AllocationBenchmark.run:+totalAllocatedBytes ss 163928784.000 bytes +AllocationBenchmark.run:+totalAllocatedInstances ss 5005903.000 instances AllocationBenchmark.run:+totalHeap ss 521412608.000 bytes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 5.275 s -[INFO] Finished at: 2024-10-10T09:59:32-04:00 +[INFO] Total time: 6.487 s +[INFO] Finished at: 2024-10-10T11:03:16-04:00 [INFO] ------------------------------------------------------------------------ diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java index 578378032..9b27cdd59 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java @@ -79,10 +79,10 @@ public String getTargetingKey() { @Override public EvaluationContext merge(EvaluationContext overridingContext) { if (overridingContext == null || overridingContext.isEmpty()) { - return this; + return new ImmutableContext(this.asMap()); } if (this.isEmpty()) { - return overridingContext; + return new ImmutableContext(overridingContext.asMap()); } return new ImmutableContext( From 54ec3046b48f93f4c79731e7d6df21fae9f38d43 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 10 Oct 2024 11:23:09 -0400 Subject: [PATCH 4/8] fixup: comments Signed-off-by: Todd Baert --- .../openfeature/sdk/benchmark/AllocationBenchmark.java | 9 ++++++++- .../openfeature/sdk/benchmark/AllocationProfiler.java | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java b/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java index 8034eb6e7..2e9544d55 100644 --- a/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java +++ b/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java @@ -26,8 +26,15 @@ import dev.openfeature.sdk.OpenFeatureAPI; import dev.openfeature.sdk.Value; +/** + * Runs a large volume of flag evaluations on a VM with 1G memory and GC + * completely disabled so we can take a heap-dump. + */ public class AllocationBenchmark { + // 10K iterations works well with Xmx1024m (we don't want to run out of memory) + private static final int ITERATIONS = 10000; + @Benchmark @Warmup(iterations = 0) @Measurement(iterations = 1) @@ -44,7 +51,7 @@ public Optional before(HookContext ctx, Map Date: Thu, 10 Oct 2024 11:50:52 -0400 Subject: [PATCH 5/8] fixup: list merge Signed-off-by: Todd Baert --- benchmark.txt | 289 ++++++++---------- .../openfeature/sdk/internal/ObjectUtils.java | 13 +- 2 files changed, 139 insertions(+), 163 deletions(-) diff --git a/benchmark.txt b/benchmark.txt index 814984dc4..7c252be4d 100644 --- a/benchmark.txt +++ b/benchmark.txt @@ -1,4 +1,6 @@ [INFO] Scanning for projects... +Downloading from central: https://repo.maven.apache.org/maven2/org/cyclonedx/cyclonedx-maven-plugin/2.9.0/cyclonedx-maven-plugin-2.9.0.pom +Progress (1): 1.4/19 kB Progress (1): 2.8/19 kB Progress (1): 4.1/19 kB Progress (1): 5.5/19 kB Progress (1): 6.9/19 kB Progress (1): 8.3/19 kB Progress (1): 9.7/19 kB Progress (1): 11/19 kB Progress (1): 12/19 kB Progress (1): 14/19 kB Progress (1): 15/19 kB Progress (1): 17/19 kB Progress (1): 18/19 kB Progress (1): 19 kB Downloaded from central: https://repo.maven.apache.org/maven2/org/cyclonedx/cyclonedx-maven-plugin/2.9.0/cyclonedx-maven-plugin-2.9.0.pom (19 kB at 97 kB/s) [INFO] [INFO] ------------------------< dev.openfeature:sdk >------------------------- [INFO] Building OpenFeature Java SDK 1.12.0 @@ -20,27 +22,7 @@ Audit done. [INFO] skip non existing resourceDirectory /home/todd/git/java-sdk/src/main/resources [INFO] [INFO] --- compiler:3.13.0:compile (default-compile) @ sdk --- -[INFO] Recompiling the module because of changed source code. -[INFO] Compiling 65 source files with javac [debug target 1.8] to target/classes -[WARNING] bootstrap class path not set in conjunction with -source 8 -[WARNING] source value 8 is obsolete and will be removed in a future release -[WARNING] target value 8 is obsolete and will be removed in a future release -[WARNING] To suppress warnings about obsolete options, use -Xlint:-options. -[INFO] Annotation processing is enabled because one or more processors were found - on the class path. A future release of javac may disable annotation processing - unless at least one processor is specified by name (-processor), or a search - path is specified (--processor-path, --processor-module-path), or annotation - processing is enabled explicitly (-proc:only, -proc:full). - Use -Xlint:-options to suppress this message. - Use -proc:none to disable annotation processing. -[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/MutableStructure.java:[19,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. -[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/ImmutableStructure.java:[22,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. -[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/EventDetails.java:[9,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. -[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java:[27,26] finalize() in java.lang.Object has been deprecated and marked for removal -[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Some input files use or override a deprecated API. -[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Recompile with -Xlint:deprecation for details. -[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java: Some input files use unchecked or unsafe operations. -[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java: Recompile with -Xlint:unchecked for details. +[INFO] Nothing to compile - all classes are up to date. [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ sdk --- [INFO] Copying 2 resources from src/test/resources to target/test-classes @@ -49,12 +31,7 @@ Audit done. [INFO] [INFO] [INFO] --- jmh:0.2.2:benchmark (default-cli) @ sdk --- -[INFO] Changes detected - recompiling the module! -[INFO] Compiling 52 source files to /home/todd/git/java-sdk/target/test-classes -[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/LockingTest.java: Some input files use or override a deprecated API. -[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/LockingTest.java: Recompile with -Xlint:deprecation for details. -[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java: Some input files use unchecked or unsafe operations. -[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java: Recompile with -Xlint:unchecked for details. +[INFO] Nothing to compile - all classes are up to date [INFO] Executing the JMH benchmarks # JMH version: 1.37 # VM version: JDK 21.0.4, OpenJDK 64-Bit Server VM, 21.0.4+7 @@ -74,139 +51,139 @@ Audit done. [0.001s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups Iteration 1: num #instances #bytes class name (module) ------------------------------------------------------- - 1: 1140090 54724320 java.util.HashMap (java.base@21.0.4) - 2: 250067 14003752 java.util.stream.ReferencePipeline$Head (java.base@21.0.4) - 3: 700056 11200896 java.util.HashMap$EntrySet (java.base@21.0.4) - 4: 47801 9303280 [B (java.base@21.0.4) - 5: 486170 7778720 dev.openfeature.sdk.ImmutableStructure - 6: 476170 7618720 dev.openfeature.sdk.ImmutableContext - 7: 150000 4800000 java.util.ArrayList$ArrayListSpliterator (java.base@21.0.4) - 8: 105995 4506088 [Ljava.lang.Object; (java.base@21.0.4) - 9: 100000 4000000 dev.openfeature.sdk.HookContext - 10: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder - 11: 126752 3042048 java.util.ArrayList (java.base@21.0.4) - 12: 50000 2800000 java.util.stream.ReferencePipeline$7 (java.base@21.0.4) - 13: 50062 2002480 java.util.Spliterators$ArraySpliterator (java.base@21.0.4) - 14: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails - 15: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation - 16: 160 1799192 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) - 17: 51775 1656800 java.util.ArrayList$Itr (java.base@21.0.4) - 18: 50060 1601920 java.util.stream.Collectors$CollectorImpl (java.base@21.0.4) - 19: 50060 1601920 java.util.stream.ReduceOps$3ReducingSink (java.base@21.0.4) - 20: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) - 21: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x00007c562402fa78 - 22: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) - 23: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata - 24: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x00007c5624082430 - 25: 99350 1589600 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder - 26: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions - 27: 50000 1200000 java.util.stream.ReferencePipeline$7$1 (java.base@21.0.4) - 28: 71122 1137952 java.util.Optional (java.base@21.0.4) - 29: 18775 901200 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder - 30: 22140 885600 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder - 31: 34680 832320 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder - 32: 4491 679416 [I (java.base@21.0.4) - 33: 26582 637968 java.lang.String (java.base@21.0.4) - 34: 38046 608736 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x00007c562402eae8 - 35: 24049 577176 dev.openfeature.sdk.HookSupport$$Lambda/0x00007c5624081478 - 36: 21192 508608 dev.openfeature.sdk.HookSupport$$Lambda/0x00007c5624081248 - 37: 1462 390280 [J (java.base@21.0.4) - 38: 11678 373696 java.util.stream.ReduceOps$3 (java.base@21.0.4) - 39: 2356 288208 java.lang.Class (java.base@21.0.4) - 40: 8513 272416 java.util.HashMap$EntryIterator (java.base@21.0.4) - 41: 4631 259336 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) - 42: 10001 240024 java.lang.Double (java.base@21.0.4) - 43: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) - 44: 10000 160000 dev.openfeature.sdk.Value - 45: 6009 144216 java.lang.StringBuilder (java.base@21.0.4) - 46: 180 140968 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) - 47: 3827 122464 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) - 48: 48 122168 [C (java.base@21.0.4) - 49: 1441 113592 [S (java.base@21.0.4) - 50: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) - 51: 3034 79648 [Ljava.lang.Class; (java.base@21.0.4) - 52: 1351 75656 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) - 53: 1561 74928 java.lang.invoke.MemberName (java.base@21.0.4) - 54: 334 74816 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) - 55: 1799 71960 java.lang.invoke.MethodType (java.base@21.0.4) - 56: 1089 69696 java.net.URL (java.base@21.0.4) - 57: 2011 64352 java.util.HashMap$Node (java.base@21.0.4) - 58: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) - 59: 3149 50384 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) - 60: 491 49608 [Ljava.util.HashMap$Node; (java.base@21.0.4) - 61: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) - 62: 1225 39200 java.io.File (java.base@21.0.4) - 63: 779 37392 jdk.internal.org.objectweb.asm.Frame (java.base@21.0.4) - 64: 243 25272 java.util.jar.JarFile$JarFileEntry (java.base@21.0.4) - 65: 794 25248 [Ljava.lang.String; (java.base@21.0.4) - 66: 622 24880 java.lang.NoSuchFieldException (java.base@21.0.4) - 67: 571 22840 java.util.LinkedHashMap$Entry (java.base@21.0.4) - 68: 474 22752 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) - 69: 691 22112 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) - 70: 828 19872 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) - 71: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) - 72: 118 17936 jdk.internal.org.objectweb.asm.ClassWriter (java.base@21.0.4) - 73: 380 16824 [Ljava.lang.invoke.LambdaForm$Name; (java.base@21.0.4) - 74: 625 15000 java.lang.Long (java.base@21.0.4) - 75: 463 14816 java.lang.invoke.LambdaForm$Name (java.base@21.0.4) - 76: 903 14448 java.lang.Object (java.base@21.0.4) - 77: 198 14256 java.lang.reflect.Constructor (java.base@21.0.4) - 78: 249 13944 java.util.zip.ZipFile$ZipFileInputStream (java.base@21.0.4) - 79: 334 13360 jdk.internal.org.objectweb.asm.Handler (java.base@21.0.4) - 80: 202 12928 java.util.concurrent.ConcurrentHashMap (java.base@21.0.4) - 81: 201 12864 jdk.internal.org.objectweb.asm.FieldWriter (java.base@21.0.4) - 82: 316 12640 java.util.WeakHashMap$Entry (java.base@21.0.4) - 83: 102 12240 java.io.ObjectStreamClass (java.base@21.0.4) - 84: 249 11952 java.util.zip.ZipFile$ZipFileInflaterInputStream (java.base@21.0.4) - 85: 359 11488 jdk.internal.org.objectweb.asm.Type (java.base@21.0.4) - 86: 465 11160 java.lang.invoke.ResolvedMethodName (java.base@21.0.4) - 87: 464 11136 jdk.internal.org.objectweb.asm.Edge (java.base@21.0.4) - 88: 341 10912 jdk.internal.math.FDBigInteger (java.base@21.0.4) - 89: 94 10728 [Ljava.lang.reflect.Field; (java.base@21.0.4) - 90: 266 10640 java.lang.NoSuchMethodException (java.base@21.0.4) - 91: 266 10640 java.security.CodeSource (java.base@21.0.4) - 92: 220 10560 java.lang.invoke.DirectMethodHandle$Constructor (java.base@21.0.4) - 93: 264 10560 sun.security.util.KnownOIDs (java.base@21.0.4) - 94: 75 10200 sun.nio.fs.UnixFileAttributes (java.base@21.0.4) - 95: 123 9840 jdk.internal.event.DeserializationEvent (java.base@21.0.4) - 96: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) - 97: 115 9200 [Ljava.util.WeakHashMap$Entry; (java.base@21.0.4) - 98: 368 8832 java.lang.module.ModuleDescriptor$Exports (java.base@21.0.4) - 99: 63 8384 [Ljava.lang.invoke.MethodHandle; (java.base@21.0.4) - 100: 146 8176 java.io.FileCleanable (java.base@21.0.4) - 101: 125 8000 java.lang.Class$ReflectionData (java.base@21.0.4) - 102: 323 7752 java.util.ImmutableCollections$Set12 (java.base@21.0.4) - 103: 121 7744 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) - 104: 70 7280 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) - 105: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) - 106: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) - 107: 199 6368 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) - 108: 156 6240 java.util.StringJoiner (java.base@21.0.4) - 109: 153 6120 java.io.FileDescriptor (java.base@21.0.4) - 110: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) - 111: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) - 112: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (java.base@21.0.4) - 113: 373 5968 java.lang.Byte (java.base@21.0.4) - 114: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) - 115: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) - 116: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) - 117: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) - 118: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) + 1: 1146018 55008864 java.util.HashMap (java.base@21.0.4) + 2: 700056 11200896 java.util.HashMap$EntrySet (java.base@21.0.4) + 3: 47755 9296008 [B (java.base@21.0.4) + 4: 305988 8105728 [Ljava.lang.Object; (java.base@21.0.4) + 5: 490930 7854880 dev.openfeature.sdk.ImmutableStructure + 6: 480930 7694880 dev.openfeature.sdk.ImmutableContext + 7: 100000 4000000 dev.openfeature.sdk.HookContext + 8: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder + 9: 131512 3156288 java.util.ArrayList (java.base@21.0.4) + 10: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails + 11: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation + 12: 153 1731448 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) + 13: 51675 1653600 java.util.ArrayList$Itr (java.base@21.0.4) + 14: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) + 15: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x00007b6ff402fa78 + 16: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) + 17: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata + 18: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder + 19: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x00007b6ff40821f8 + 20: 75022 1200352 java.util.Optional (java.base@21.0.4) + 21: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions + 22: 35143 843432 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder + 23: 4489 679248 [I (java.base@21.0.4) + 24: 26553 637272 java.lang.String (java.base@21.0.4) + 25: 13155 631440 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder + 26: 14195 567800 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder + 27: 30850 493600 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x00007b6ff402eae8 + 28: 18779 450696 dev.openfeature.sdk.HookSupport$$Lambda/0x00007b6ff4081230 + 29: 18171 436104 dev.openfeature.sdk.HookSupport$$Lambda/0x00007b6ff4081000 + 30: 1461 390008 [J (java.base@21.0.4) + 31: 2355 288104 java.lang.Class (java.base@21.0.4) + 32: 4610 258160 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) + 33: 10001 240024 java.lang.Double (java.base@21.0.4) + 34: 7153 228896 java.util.HashMap$EntryIterator (java.base@21.0.4) + 35: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) + 36: 10000 160000 dev.openfeature.sdk.Value + 37: 6003 144072 java.lang.StringBuilder (java.base@21.0.4) + 38: 179 139928 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) + 39: 3821 122272 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) + 40: 48 122168 [C (java.base@21.0.4) + 41: 1440 113512 [S (java.base@21.0.4) + 42: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) + 43: 3024 79424 [Ljava.lang.Class; (java.base@21.0.4) + 44: 1349 75544 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) + 45: 332 74368 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) + 46: 1548 74304 java.lang.invoke.MemberName (java.base@21.0.4) + 47: 1789 71560 java.lang.invoke.MethodType (java.base@21.0.4) + 48: 1089 69696 java.net.URL (java.base@21.0.4) + 49: 2011 64352 java.util.HashMap$Node (java.base@21.0.4) + 50: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) + 51: 3131 50096 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) + 52: 491 49608 [Ljava.util.HashMap$Node; (java.base@21.0.4) + 53: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) + 54: 1225 39200 java.io.File (java.base@21.0.4) + 55: 779 37392 jdk.internal.org.objectweb.asm.Frame (java.base@21.0.4) + 56: 243 25272 java.util.jar.JarFile$JarFileEntry (java.base@21.0.4) + 57: 793 25224 [Ljava.lang.String; (java.base@21.0.4) + 58: 622 24880 java.lang.NoSuchFieldException (java.base@21.0.4) + 59: 571 22840 java.util.LinkedHashMap$Entry (java.base@21.0.4) + 60: 473 22704 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) + 61: 687 21984 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) + 62: 824 19776 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) + 63: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) + 64: 117 17784 jdk.internal.org.objectweb.asm.ClassWriter (java.base@21.0.4) + 65: 380 16824 [Ljava.lang.invoke.LambdaForm$Name; (java.base@21.0.4) + 66: 625 15000 java.lang.Long (java.base@21.0.4) + 67: 463 14816 java.lang.invoke.LambdaForm$Name (java.base@21.0.4) + 68: 903 14448 java.lang.Object (java.base@21.0.4) + 69: 198 14256 java.lang.reflect.Constructor (java.base@21.0.4) + 70: 249 13944 java.util.zip.ZipFile$ZipFileInputStream (java.base@21.0.4) + 71: 334 13360 jdk.internal.org.objectweb.asm.Handler (java.base@21.0.4) + 72: 202 12928 java.util.concurrent.ConcurrentHashMap (java.base@21.0.4) + 73: 201 12864 jdk.internal.org.objectweb.asm.FieldWriter (java.base@21.0.4) + 74: 316 12640 java.util.WeakHashMap$Entry (java.base@21.0.4) + 75: 102 12240 java.io.ObjectStreamClass (java.base@21.0.4) + 76: 249 11952 java.util.zip.ZipFile$ZipFileInflaterInputStream (java.base@21.0.4) + 77: 359 11488 jdk.internal.org.objectweb.asm.Type (java.base@21.0.4) + 78: 464 11136 jdk.internal.org.objectweb.asm.Edge (java.base@21.0.4) + 79: 463 11112 java.lang.invoke.ResolvedMethodName (java.base@21.0.4) + 80: 341 10912 jdk.internal.math.FDBigInteger (java.base@21.0.4) + 81: 94 10728 [Ljava.lang.reflect.Field; (java.base@21.0.4) + 82: 266 10640 java.lang.NoSuchMethodException (java.base@21.0.4) + 83: 266 10640 java.security.CodeSource (java.base@21.0.4) + 84: 264 10560 sun.security.util.KnownOIDs (java.base@21.0.4) + 85: 218 10464 java.lang.invoke.DirectMethodHandle$Constructor (java.base@21.0.4) + 86: 75 10200 sun.nio.fs.UnixFileAttributes (java.base@21.0.4) + 87: 123 9840 jdk.internal.event.DeserializationEvent (java.base@21.0.4) + 88: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) + 89: 115 9200 [Ljava.util.WeakHashMap$Entry; (java.base@21.0.4) + 90: 368 8832 java.lang.module.ModuleDescriptor$Exports (java.base@21.0.4) + 91: 63 8384 [Ljava.lang.invoke.MethodHandle; (java.base@21.0.4) + 92: 146 8176 java.io.FileCleanable (java.base@21.0.4) + 93: 125 8000 java.lang.Class$ReflectionData (java.base@21.0.4) + 94: 322 7728 java.util.ImmutableCollections$Set12 (java.base@21.0.4) + 95: 120 7680 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) + 96: 69 7176 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) + 97: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) + 98: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) + 99: 196 6272 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) + 100: 156 6240 java.util.StringJoiner (java.base@21.0.4) + 101: 153 6120 java.io.FileDescriptor (java.base@21.0.4) + 102: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) + 103: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) + 104: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (java.base@21.0.4) + 105: 370 5920 java.lang.Byte (java.base@21.0.4) + 106: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) + 107: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) + 108: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) + 109: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) + 110: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) + 111: 98 5488 java.lang.Module (java.base@21.0.4) + 112: 219 5256 java.lang.PublicMethods$MethodList (java.base@21.0.4) + 113: 65 5200 java.net.URI (java.base@21.0.4) + 114: 215 5104 [Ljdk.internal.org.objectweb.asm.Type; (java.base@21.0.4) + 115: 158 5056 java.lang.invoke.MethodTypeForm (java.base@21.0.4) + 116: 152 4864 java.nio.file.attribute.FileTime (java.base@21.0.4) + 117: 301 4816 java.util.HashSet (java.base@21.0.4) + 118: 75 4800 java.util.zip.Inflater (java.base@21.0.4) truncated... -Total 5005903 163928784 +Total 4538772 138760912 -0.116 s/op - +totalAllocatedBytes: 163928784.000 bytes - +totalAllocatedInstances: 5005903.000 instances +0.083 s/op + +totalAllocatedBytes: 138760912.000 bytes + +totalAllocatedInstances: 4538772.000 instances +totalHeap: 521412608.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedBytes": - 163928784.000 bytes + 138760912.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances": - 5005903.000 instances + 4538772.000 instances Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalHeap": 521412608.000 bytes @@ -227,13 +204,13 @@ different JVMs are already problematic, the performance difference caused by dif modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons. Benchmark Mode Cnt Score Error Units -AllocationBenchmark.run ss 0.116 s/op -AllocationBenchmark.run:+totalAllocatedBytes ss 163928784.000 bytes -AllocationBenchmark.run:+totalAllocatedInstances ss 5005903.000 instances +AllocationBenchmark.run ss 0.083 s/op +AllocationBenchmark.run:+totalAllocatedBytes ss 138760912.000 bytes +AllocationBenchmark.run:+totalAllocatedInstances ss 4538772.000 instances AllocationBenchmark.run:+totalHeap ss 521412608.000 bytes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.487 s -[INFO] Finished at: 2024-10-10T11:03:16-04:00 +[INFO] Total time: 3.068 s +[INFO] Finished at: 2024-10-10T11:49:20-04:00 [INFO] ------------------------------------------------------------------------ diff --git a/src/main/java/dev/openfeature/sdk/internal/ObjectUtils.java b/src/main/java/dev/openfeature/sdk/internal/ObjectUtils.java index 34caadaea..9e5dcf613 100644 --- a/src/main/java/dev/openfeature/sdk/internal/ObjectUtils.java +++ b/src/main/java/dev/openfeature/sdk/internal/ObjectUtils.java @@ -1,11 +1,9 @@ package dev.openfeature.sdk.internal; -import java.util.Arrays; -import java.util.Collection; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.function.Supplier; -import java.util.stream.Collectors; import lombok.experimental.UtilityClass; @@ -64,9 +62,10 @@ public static T defaultIfNull(T source, Supplier defaultValue) { */ @SafeVarargs public static List merge(List... sources) { - return Arrays - .stream(sources) - .flatMap(Collection::stream) - .collect(Collectors.toList()); + List merged = new ArrayList<>(); + for (List source : sources) { + merged.addAll(source); + } + return merged; } } From 02d57b31d1f3632705dacadf6348ec910d50f182 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 10 Oct 2024 12:28:17 -0400 Subject: [PATCH 6/8] fixup: revert lazy hashmap Signed-off-by: Todd Baert --- benchmark.txt | 127 +++++++++++------- .../openfeature/sdk/AbstractStructure.java | 10 +- .../openfeature/sdk/ImmutableStructure.java | 6 +- .../dev/openfeature/sdk/MutableContext.java | 2 +- .../dev/openfeature/sdk/MutableStructure.java | 22 +-- 5 files changed, 92 insertions(+), 75 deletions(-) diff --git a/benchmark.txt b/benchmark.txt index 7c252be4d..696ffa24c 100644 --- a/benchmark.txt +++ b/benchmark.txt @@ -1,6 +1,4 @@ [INFO] Scanning for projects... -Downloading from central: https://repo.maven.apache.org/maven2/org/cyclonedx/cyclonedx-maven-plugin/2.9.0/cyclonedx-maven-plugin-2.9.0.pom -Progress (1): 1.4/19 kB Progress (1): 2.8/19 kB Progress (1): 4.1/19 kB Progress (1): 5.5/19 kB Progress (1): 6.9/19 kB Progress (1): 8.3/19 kB Progress (1): 9.7/19 kB Progress (1): 11/19 kB Progress (1): 12/19 kB Progress (1): 14/19 kB Progress (1): 15/19 kB Progress (1): 17/19 kB Progress (1): 18/19 kB Progress (1): 19 kB Downloaded from central: https://repo.maven.apache.org/maven2/org/cyclonedx/cyclonedx-maven-plugin/2.9.0/cyclonedx-maven-plugin-2.9.0.pom (19 kB at 97 kB/s) [INFO] [INFO] ------------------------< dev.openfeature:sdk >------------------------- [INFO] Building OpenFeature Java SDK 1.12.0 @@ -22,7 +20,27 @@ Audit done. [INFO] skip non existing resourceDirectory /home/todd/git/java-sdk/src/main/resources [INFO] [INFO] --- compiler:3.13.0:compile (default-compile) @ sdk --- -[INFO] Nothing to compile - all classes are up to date. +[INFO] Recompiling the module because of changed source code. +[INFO] Compiling 65 source files with javac [debug target 1.8] to target/classes +[WARNING] bootstrap class path not set in conjunction with -source 8 +[WARNING] source value 8 is obsolete and will be removed in a future release +[WARNING] target value 8 is obsolete and will be removed in a future release +[WARNING] To suppress warnings about obsolete options, use -Xlint:-options. +[INFO] Annotation processing is enabled because one or more processors were found + on the class path. A future release of javac may disable annotation processing + unless at least one processor is specified by name (-processor), or a search + path is specified (--processor-path, --processor-module-path), or annotation + processing is enabled explicitly (-proc:only, -proc:full). + Use -Xlint:-options to suppress this message. + Use -proc:none to disable annotation processing. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/MutableStructure.java:[19,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/ImmutableStructure.java:[22,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/EventDetails.java:[9,1] Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type. +[WARNING] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java:[27,26] finalize() in java.lang.Object has been deprecated and marked for removal +[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Some input files use or override a deprecated API. +[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/NoOpProvider.java: Recompile with -Xlint:deprecation for details. +[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java: Some input files use unchecked or unsafe operations. +[INFO] /home/todd/git/java-sdk/src/main/java/dev/openfeature/sdk/Value.java: Recompile with -Xlint:unchecked for details. [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ sdk --- [INFO] Copying 2 resources from src/test/resources to target/test-classes @@ -31,7 +49,12 @@ Audit done. [INFO] [INFO] [INFO] --- jmh:0.2.2:benchmark (default-cli) @ sdk --- -[INFO] Nothing to compile - all classes are up to date +[INFO] Changes detected - recompiling the module! +[INFO] Compiling 52 source files to /home/todd/git/java-sdk/target/test-classes +[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/LockingTest.java: Some input files use or override a deprecated API. +[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/LockingTest.java: Recompile with -Xlint:deprecation for details. +[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java: Some input files use unchecked or unsafe operations. +[INFO] /home/todd/git/java-sdk/src/test/java/dev/openfeature/sdk/internal/TriConsumerTest.java: Recompile with -Xlint:unchecked for details. [INFO] Executing the JMH benchmarks # JMH version: 1.37 # VM version: JDK 21.0.4, OpenJDK 64-Bit Server VM, 21.0.4+7 @@ -51,57 +74,57 @@ Audit done. [0.001s][warning][gc,init] Consider enabling -XX:+AlwaysPreTouch to avoid memory commit hiccups Iteration 1: num #instances #bytes class name (module) ------------------------------------------------------- - 1: 1146018 55008864 java.util.HashMap (java.base@21.0.4) + 1: 1146984 55055232 java.util.HashMap (java.base@21.0.4) 2: 700056 11200896 java.util.HashMap$EntrySet (java.base@21.0.4) - 3: 47755 9296008 [B (java.base@21.0.4) - 4: 305988 8105728 [Ljava.lang.Object; (java.base@21.0.4) - 5: 490930 7854880 dev.openfeature.sdk.ImmutableStructure - 6: 480930 7694880 dev.openfeature.sdk.ImmutableContext + 3: 47757 9295888 [B (java.base@21.0.4) + 4: 305989 8105752 [Ljava.lang.Object; (java.base@21.0.4) + 5: 482225 7715600 dev.openfeature.sdk.ImmutableStructure + 6: 472225 7555600 dev.openfeature.sdk.ImmutableContext 7: 100000 4000000 dev.openfeature.sdk.HookContext 8: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder - 9: 131512 3156288 java.util.ArrayList (java.base@21.0.4) - 10: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails - 11: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation - 12: 153 1731448 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) - 13: 51675 1653600 java.util.ArrayList$Itr (java.base@21.0.4) - 14: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) - 15: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x00007b6ff402fa78 - 16: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) - 17: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata - 18: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder - 19: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x00007b6ff40821f8 - 20: 75022 1200352 java.util.Optional (java.base@21.0.4) - 21: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions - 22: 35143 843432 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder + 9: 154 2995712 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) + 10: 122807 2947368 java.util.ArrayList (java.base@21.0.4) + 11: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails + 12: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation + 13: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) + 14: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x000074760c02fa78 + 15: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) + 16: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata + 17: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder + 18: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x000074760c0821f8 + 19: 43808 1401856 java.util.ArrayList$Itr (java.base@21.0.4) + 20: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions + 21: 56919 910704 java.util.Optional (java.base@21.0.4) + 22: 34754 834096 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder 23: 4489 679248 [I (java.base@21.0.4) - 24: 26553 637272 java.lang.String (java.base@21.0.4) - 25: 13155 631440 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder - 26: 14195 567800 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder - 27: 30850 493600 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x00007b6ff402eae8 - 28: 18779 450696 dev.openfeature.sdk.HookSupport$$Lambda/0x00007b6ff4081230 - 29: 18171 436104 dev.openfeature.sdk.HookSupport$$Lambda/0x00007b6ff4081000 - 30: 1461 390008 [J (java.base@21.0.4) + 24: 26554 637296 java.lang.String (java.base@21.0.4) + 25: 12462 598176 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder + 26: 13748 549920 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder + 27: 16418 394032 dev.openfeature.sdk.HookSupport$$Lambda/0x000074760c081230 + 28: 1461 390008 [J (java.base@21.0.4) + 29: 24033 384528 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x000074760c02eae8 + 30: 14591 350184 dev.openfeature.sdk.HookSupport$$Lambda/0x000074760c081000 31: 2355 288104 java.lang.Class (java.base@21.0.4) - 32: 4610 258160 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) - 33: 10001 240024 java.lang.Double (java.base@21.0.4) - 34: 7153 228896 java.util.HashMap$EntryIterator (java.base@21.0.4) + 32: 8141 260512 java.util.HashMap$EntryIterator (java.base@21.0.4) + 33: 4610 258160 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) + 34: 10001 240024 java.lang.Double (java.base@21.0.4) 35: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) 36: 10000 160000 dev.openfeature.sdk.Value - 37: 6003 144072 java.lang.StringBuilder (java.base@21.0.4) + 37: 6004 144096 java.lang.StringBuilder (java.base@21.0.4) 38: 179 139928 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) - 39: 3821 122272 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) + 39: 3824 122368 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) 40: 48 122168 [C (java.base@21.0.4) 41: 1440 113512 [S (java.base@21.0.4) 42: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) - 43: 3024 79424 [Ljava.lang.Class; (java.base@21.0.4) + 43: 3030 79616 [Ljava.lang.Class; (java.base@21.0.4) 44: 1349 75544 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) - 45: 332 74368 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) - 46: 1548 74304 java.lang.invoke.MemberName (java.base@21.0.4) - 47: 1789 71560 java.lang.invoke.MethodType (java.base@21.0.4) + 45: 1550 74400 java.lang.invoke.MemberName (java.base@21.0.4) + 46: 332 74368 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) + 47: 1794 71760 java.lang.invoke.MethodType (java.base@21.0.4) 48: 1089 69696 java.net.URL (java.base@21.0.4) 49: 2011 64352 java.util.HashMap$Node (java.base@21.0.4) 50: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) - 51: 3131 50096 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) + 51: 3140 50240 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) 52: 491 49608 [Ljava.util.HashMap$Node; (java.base@21.0.4) 53: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) 54: 1225 39200 java.io.File (java.base@21.0.4) @@ -111,7 +134,7 @@ Iteration 1: num #instances #bytes class name (module) 58: 622 24880 java.lang.NoSuchFieldException (java.base@21.0.4) 59: 571 22840 java.util.LinkedHashMap$Entry (java.base@21.0.4) 60: 473 22704 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) - 61: 687 21984 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) + 61: 689 22048 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) 62: 824 19776 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) 63: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) 64: 117 17784 jdk.internal.org.objectweb.asm.ClassWriter (java.base@21.0.4) @@ -170,20 +193,20 @@ Iteration 1: num #instances #bytes class name (module) 117: 301 4816 java.util.HashSet (java.base@21.0.4) 118: 75 4800 java.util.zip.Inflater (java.base@21.0.4) truncated... -Total 4538772 138760912 +Total 4474389 138762960 -0.083 s/op - +totalAllocatedBytes: 138760912.000 bytes - +totalAllocatedInstances: 4538772.000 instances +0.113 s/op + +totalAllocatedBytes: 138762960.000 bytes + +totalAllocatedInstances: 4474389.000 instances +totalHeap: 521412608.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedBytes": - 138760912.000 bytes + 138762960.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances": - 4538772.000 instances + 4474389.000 instances Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalHeap": 521412608.000 bytes @@ -204,13 +227,13 @@ different JVMs are already problematic, the performance difference caused by dif modes can be very significant. Please make sure you use the consistent Blackhole mode for comparisons. Benchmark Mode Cnt Score Error Units -AllocationBenchmark.run ss 0.083 s/op -AllocationBenchmark.run:+totalAllocatedBytes ss 138760912.000 bytes -AllocationBenchmark.run:+totalAllocatedInstances ss 4538772.000 instances +AllocationBenchmark.run ss 0.113 s/op +AllocationBenchmark.run:+totalAllocatedBytes ss 138762960.000 bytes +AllocationBenchmark.run:+totalAllocatedInstances ss 4474389.000 instances AllocationBenchmark.run:+totalHeap ss 521412608.000 bytes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 3.068 s -[INFO] Finished at: 2024-10-10T11:49:20-04:00 +[INFO] Total time: 8.073 s +[INFO] Finished at: 2024-10-10T12:26:18-04:00 [INFO] ------------------------------------------------------------------------ diff --git a/src/main/java/dev/openfeature/sdk/AbstractStructure.java b/src/main/java/dev/openfeature/sdk/AbstractStructure.java index f3543f31e..13a6cf6cb 100644 --- a/src/main/java/dev/openfeature/sdk/AbstractStructure.java +++ b/src/main/java/dev/openfeature/sdk/AbstractStructure.java @@ -6,7 +6,7 @@ @SuppressWarnings({ "PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType" }) abstract class AbstractStructure implements Structure { - private Map attributes; + protected final Map attributes; @Override public boolean isEmpty() { @@ -14,7 +14,7 @@ public boolean isEmpty() { } AbstractStructure() { - // intentionally don't initialize the attributes - do this lazily + this.attributes = new HashMap<>(); } AbstractStructure(Map attributes) { @@ -38,10 +38,4 @@ public Map asObjectMap() { HashMap::putAll); } - protected Map getAttributes() { - if (attributes == null) { - attributes = new HashMap<>(); - } - return attributes; - } } diff --git a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java index 57a68db9f..170602000 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java @@ -41,13 +41,13 @@ public ImmutableStructure(Map attributes) { @Override public Set keySet() { - return new HashSet<>(this.getAttributes().keySet()); + return new HashSet<>(this.attributes.keySet()); } // getters @Override public Value getValue(String key) { - Value value = getAttributes().get(key); + Value value = attributes.get(key); return value != null ? value.clone() : null; } @@ -58,7 +58,7 @@ public Value getValue(String key) { */ @Override public Map asMap() { - return copyAttributes(getAttributes()); + return copyAttributes(attributes); } private static Map copyAttributes(Map in) { diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java index 0104379f5..6a47c83ef 100644 --- a/src/main/java/dev/openfeature/sdk/MutableContext.java +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -46,7 +46,7 @@ public MutableContext(Map attributes) { public MutableContext(String targetingKey, Map attributes) { this.structure = new MutableStructure(attributes); if (targetingKey != null && !targetingKey.trim().isEmpty()) { - this.structure.getAttributes().put(TARGETING_KEY, new Value(targetingKey)); + this.structure.attributes.put(TARGETING_KEY, new Value(targetingKey)); } } diff --git a/src/main/java/dev/openfeature/sdk/MutableStructure.java b/src/main/java/dev/openfeature/sdk/MutableStructure.java index 919c85746..1246aa5ef 100644 --- a/src/main/java/dev/openfeature/sdk/MutableStructure.java +++ b/src/main/java/dev/openfeature/sdk/MutableStructure.java @@ -30,53 +30,53 @@ public MutableStructure(Map attributes) { @Override public Set keySet() { - return getAttributes().keySet(); + return attributes.keySet(); } // getters @Override public Value getValue(String key) { - return getAttributes().get(key); + return attributes.get(key); } // adders public MutableStructure add(String key, Value value) { - getAttributes().put(key, value); + attributes.put(key, value); return this; } public MutableStructure add(String key, Boolean value) { - getAttributes().put(key, new Value(value)); + attributes.put(key, new Value(value)); return this; } public MutableStructure add(String key, String value) { - getAttributes().put(key, new Value(value)); + attributes.put(key, new Value(value)); return this; } public MutableStructure add(String key, Integer value) { - getAttributes().put(key, new Value(value)); + attributes.put(key, new Value(value)); return this; } public MutableStructure add(String key, Double value) { - getAttributes().put(key, new Value(value)); + attributes.put(key, new Value(value)); return this; } public MutableStructure add(String key, Instant value) { - getAttributes().put(key, new Value(value)); + attributes.put(key, new Value(value)); return this; } public MutableStructure add(String key, Structure value) { - getAttributes().put(key, new Value(value)); + attributes.put(key, new Value(value)); return this; } public MutableStructure add(String key, List value) { - getAttributes().put(key, new Value(value)); + attributes.put(key, new Value(value)); return this; } @@ -87,6 +87,6 @@ public MutableStructure add(String key, List value) { */ @Override public Map asMap() { - return new HashMap<>(getAttributes()); + return new HashMap<>(attributes); } } From c3a543466cf2028ad58f89244c72ebfdfc7fa36d Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Thu, 10 Oct 2024 16:00:48 -0400 Subject: [PATCH 7/8] fixup: readme Signed-off-by: Todd Baert --- CONTRIBUTING.md | 8 ++++++++ .../openfeature/sdk/benchmark/AllocationBenchmark.java | 2 -- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a62af64c6..d43b70e36 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -30,6 +30,14 @@ to run alone: mvn test -P e2e ``` +## Benchmarking + +There is a small JMH benchmark suite for testing allocations that can be run with: + +```sh +mvn -P benchmark test-compile jmh:benchmark -Djmh.f=1 -Djmh.prof='dev.openfeature.sdk.benchmark.AllocationProfiler' +``` + ## Releasing See [releasing](./docs/release.md). diff --git a/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java b/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java index 2e9544d55..e6f63a98d 100644 --- a/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java +++ b/src/test/java/dev/openfeature/sdk/benchmark/AllocationBenchmark.java @@ -36,8 +36,6 @@ public class AllocationBenchmark { private static final int ITERATIONS = 10000; @Benchmark - @Warmup(iterations = 0) - @Measurement(iterations = 1) @BenchmarkMode(Mode.SingleShotTime) @Fork(jvmArgsAppend = { "-Xmx1024m", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseEpsilonGC" }) public void run() { From c81c7d638f0ca27be2a2a9ad634d7ff7535c0a6f Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Tue, 15 Oct 2024 09:40:08 -0400 Subject: [PATCH 8/8] fixup: readme Signed-off-by: Todd Baert --- CONTRIBUTING.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d43b70e36..a5c05c305 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,8 @@ There is a small JMH benchmark suite for testing allocations that can be run wit mvn -P benchmark test-compile jmh:benchmark -Djmh.f=1 -Djmh.prof='dev.openfeature.sdk.benchmark.AllocationProfiler' ``` +If you are concerned about the repercussions of a change on memory usage, run this an compare the results to the committed. `benchmark.txt` file. + ## Releasing See [releasing](./docs/release.md).