From 896df53127ba85c88e9f9e7efeef04d254af1c54 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Tue, 22 Oct 2024 12:59:09 -0400 Subject: [PATCH 1/5] chore: reduce hashmap allocations Signed-off-by: Todd Baert --- CONTRIBUTING.md | 5 +- benchmark.txt | 260 +++++++++--------- .../openfeature/sdk/AbstractStructure.java | 10 +- .../dev/openfeature/sdk/ImmutableContext.java | 13 +- .../openfeature/sdk/ImmutableStructure.java | 13 +- .../dev/openfeature/sdk/MutableContext.java | 6 +- .../openfeature/sdk/OpenFeatureClient.java | 35 ++- .../java/dev/openfeature/sdk/Structure.java | 15 +- src/main/java/dev/openfeature/sdk/Value.java | 2 +- 9 files changed, 200 insertions(+), 159 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a5c05c305..2aafb314f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,10 +35,13 @@ mvn test -P e2e 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' +mvn -P benchmark clean compile 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. +Note that the ONLY MEANINGFUL RESULTS of this benchmark are the `totalAllocatedBytes` and the `totalAllocatedInstances`. +The `run` score, and maven task time are not relevant since this benchmark is purely memory-related and has nothing to do with speed. +You can also view the heap breakdown to see which objects are taking up the most memory. ## Releasing diff --git a/benchmark.txt b/benchmark.txt index d028a3f87..1870818b2 100644 --- a/benchmark.txt +++ b/benchmark.txt @@ -36,9 +36,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/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. @@ -129,139 +129,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: 1407606 67565088 java.util.HashMap (java.base@21.0.4) - 2: 780500 45970160 [Ljava.util.HashMap$Node; (java.base@21.0.4) - 3: 1152020 36864640 java.util.HashMap$Node (java.base@21.0.4) - 4: 860065 13761040 java.util.HashMap$EntrySet (java.base@21.0.4) - 5: 690006 11040096 dev.openfeature.sdk.Value - 6: 47842 9731792 [B (java.base@21.0.4) - 7: 305994 8105936 [Ljava.lang.Object; (java.base@21.0.4) - 8: 360370 5765920 dev.openfeature.sdk.ImmutableStructure - 9: 350370 5605920 dev.openfeature.sdk.ImmutableContext - 10: 100000 4000000 dev.openfeature.sdk.HookContext - 11: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder - 12: 116586 2798064 java.util.ArrayList (java.base@21.0.4) - 13: 174 2171368 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) - 14: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails - 15: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation - 16: 62056 1985792 java.util.HashMap$EntryIterator (java.base@21.0.4) - 17: 118262 1892192 java.util.Optional (java.base@21.0.4) - 18: 41643 1665720 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder - 19: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) - 20: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x000070b4d802fa78 - 21: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) - 22: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x000070b4d8082a18 - 23: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions - 24: 29392 940544 java.util.ArrayList$Itr (java.base@21.0.4) - 25: 57139 914224 dev.openfeature.sdk.ImmutableMetadata - 26: 34240 821760 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder - 27: 48782 780512 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder - 28: 4491 721464 [I (java.base@21.0.4) - 29: 26608 638592 java.lang.String (java.base@21.0.4) - 30: 1461 390008 [J (java.base@21.0.4) - 31: 7139 342672 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder - 32: 18300 292800 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x000070b4d802eae8 - 33: 2357 288328 java.lang.Class (java.base@21.0.4) - 34: 4651 260456 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) - 35: 10001 240024 java.lang.Double (java.base@21.0.4) - 36: 9315 223560 dev.openfeature.sdk.HookSupport$$Lambda/0x000070b4d8081bb8 - 37: 7921 190104 dev.openfeature.sdk.HookSupport$$Lambda/0x000070b4d8081988 - 38: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) - 39: 6011 144264 java.lang.StringBuilder (java.base@21.0.4) - 40: 181 142008 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) - 41: 3829 122528 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) - 42: 48 122168 [C (java.base@21.0.4) - 43: 1440 113512 [S (java.base@21.0.4) - 44: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) - 45: 3035 79672 [Ljava.lang.Class; (java.base@21.0.4) - 46: 1353 75768 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) - 47: 1570 75360 java.lang.invoke.MemberName (java.base@21.0.4) - 48: 336 75264 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) - 49: 1807 72280 java.lang.invoke.MethodType (java.base@21.0.4) - 50: 1089 69696 java.net.URL (java.base@21.0.4) - 51: 3159 50544 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) - 52: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$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: 795 25272 [Ljava.lang.String; (java.base@21.0.4) - 57: 243 25272 java.util.jar.JarFile$JarFileEntry (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: 475 22800 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) - 61: 692 22144 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) - 62: 832 19968 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) - 63: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) - 64: 119 18088 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: 904 14464 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: 467 11208 java.lang.invoke.ResolvedMethodName (java.base@21.0.4) - 79: 464 11136 jdk.internal.org.objectweb.asm.Edge (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: 223 10704 java.lang.invoke.DirectMethodHandle$Constructor (java.base@21.0.4) - 83: 266 10640 java.lang.NoSuchMethodException (java.base@21.0.4) - 84: 266 10640 java.security.CodeSource (java.base@21.0.4) - 85: 264 10560 sun.security.util.KnownOIDs (java.base@21.0.4) - 86: 75 10200 sun.nio.fs.UnixFileAttributes (java.base@21.0.4) - 87: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) - 88: 118 9440 jdk.internal.event.DeserializationEvent (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: 122 7808 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) - 95: 324 7776 java.util.ImmutableCollections$Set12 (java.base@21.0.4) - 96: 71 7384 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: 202 6464 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: 375 6000 java.lang.Byte (java.base@21.0.4) - 105: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (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) + 1: 730464 35062272 java.util.HashMap (java.base@21.0.4) + 2: 320497 19970088 [Ljava.util.HashMap$Node; (java.base@21.0.4) + 3: 475993 15945896 [Ljava.lang.Object; (java.base@21.0.4) + 4: 472017 15104544 java.util.HashMap$Node (java.base@21.0.4) + 5: 47814 9730240 [B (java.base@21.0.4) + 6: 315910 7294560 [Ljava.util.Map$Entry; (java.base@21.0.4) + 7: 303621 7286904 java.util.KeyValueHolder (java.base@21.0.4) + 8: 224606 7187392 java.util.HashMap$EntryIterator (java.base@21.0.4) + 9: 210000 6720000 java.util.ImmutableCollections$Map1 (java.base@21.0.4) + 10: 370062 5920992 java.util.HashMap$EntrySet (java.base@21.0.4) + 11: 370003 5920048 dev.openfeature.sdk.ImmutableStructure + 12: 360003 5760048 dev.openfeature.sdk.ImmutableContext + 13: 350006 5600096 dev.openfeature.sdk.Value + 14: 164 5578776 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) + 15: 174215 4181160 java.util.ArrayList (java.base@21.0.4) + 16: 173935 4174440 java.util.ImmutableCollections$Set12 (java.base@21.0.4) + 17: 173706 4168944 java.util.ImmutableCollections$Set12$1 (java.base@21.0.4) + 18: 100000 4000000 dev.openfeature.sdk.HookContext + 19: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder + 20: 101150 3236800 java.util.ArrayList$Itr (java.base@21.0.4) + 21: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails + 22: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation + 23: 60007 1920224 java.util.ImmutableCollections$MapN (java.base@21.0.4) + 24: 112818 1805088 java.util.Optional (java.base@21.0.4) + 25: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) + 26: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x00007b7ba402fa78 + 27: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) + 28: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata + 29: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder + 30: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x00007b7ba4082800 + 31: 60001 1440024 java.util.ImmutableCollections$MapN$MapNIterator (java.base@21.0.4) + 32: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions + 33: 60001 960016 java.util.ImmutableCollections$MapN$1 (java.base@21.0.4) + 34: 35554 853296 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder + 35: 15203 729744 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder + 36: 4490 721440 [I (java.base@21.0.4) + 37: 26592 638208 java.lang.String (java.base@21.0.4) + 38: 34195 547120 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x00007b7ba402eae8 + 39: 12448 497920 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder + 40: 20260 486240 dev.openfeature.sdk.HookSupport$$Lambda/0x00007b7ba4081978 + 41: 17102 410448 dev.openfeature.sdk.HookSupport$$Lambda/0x00007b7ba4081748 + 42: 1461 390008 [J (java.base@21.0.4) + 43: 2356 288216 java.lang.Class (java.base@21.0.4) + 44: 4632 259392 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) + 45: 10001 240024 java.lang.Double (java.base@21.0.4) + 46: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) + 47: 6009 144216 java.lang.StringBuilder (java.base@21.0.4) + 48: 180 140968 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) + 49: 3832 122624 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) + 50: 48 122168 [C (java.base@21.0.4) + 51: 1440 113512 [S (java.base@21.0.4) + 52: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) + 53: 3043 79984 [Ljava.lang.Class; (java.base@21.0.4) + 54: 1351 75656 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) + 55: 1567 75216 java.lang.invoke.MemberName (java.base@21.0.4) + 56: 334 74816 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) + 57: 1809 72360 java.lang.invoke.MethodType (java.base@21.0.4) + 58: 1089 69696 java.net.URL (java.base@21.0.4) + 59: 3165 50640 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) + 60: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$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: 694 22208 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: 904 14464 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: 221 10608 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: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) + 96: 118 9440 jdk.internal.event.DeserializationEvent (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: 121 7744 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) + 103: 70 7280 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) + 104: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) + 105: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) + 106: 200 6400 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) + 107: 156 6240 java.util.StringJoiner (java.base@21.0.4) + 108: 153 6120 java.io.FileDescriptor (java.base@21.0.4) + 109: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) + 110: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) + 111: 374 5984 java.lang.Byte (java.base@21.0.4) + 112: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (java.base@21.0.4) + 113: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) + 114: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) + 115: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) + 116: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) + 117: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) + 118: 98 5488 java.lang.Module (java.base@21.0.4) truncated... -Total 7264791 244216640 +Total 6560337 206465856 -0.166 s/op - +totalAllocatedBytes: 244216640.000 bytes - +totalAllocatedInstances: 7264791.000 instances +0.148 s/op + +totalAllocatedBytes: 206465856.000 bytes + +totalAllocatedInstances: 6560337.000 instances +totalHeap: 521412608.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedBytes": - 244216640.000 bytes + 206465856.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances": - 7264791.000 instances + 6560337.000 instances Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalHeap": 521412608.000 bytes @@ -282,13 +282,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.166 s/op -AllocationBenchmark.run:+totalAllocatedBytes ss 244216640.000 bytes -AllocationBenchmark.run:+totalAllocatedInstances ss 7264791.000 instances +AllocationBenchmark.run ss 0.148 s/op +AllocationBenchmark.run:+totalAllocatedBytes ss 206465856.000 bytes +AllocationBenchmark.run:+totalAllocatedInstances ss 6560337.000 instances AllocationBenchmark.run:+totalHeap ss 521412608.000 bytes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 8.760 s -[INFO] Finished at: 2024-10-23T12:22:21-04:00 +[INFO] Total time: 8.274 s +[INFO] Finished at: 2024-10-23T12:35:21-04:00 [INFO] ------------------------------------------------------------------------ diff --git a/src/main/java/dev/openfeature/sdk/AbstractStructure.java b/src/main/java/dev/openfeature/sdk/AbstractStructure.java index 13a6cf6cb..56e26559a 100644 --- a/src/main/java/dev/openfeature/sdk/AbstractStructure.java +++ b/src/main/java/dev/openfeature/sdk/AbstractStructure.java @@ -18,7 +18,15 @@ public boolean isEmpty() { } AbstractStructure(Map attributes) { - this.attributes = new HashMap<>(attributes); + this.attributes = attributes; + } + + /** + * Returns an unmodifiable representation of the internal attribute map. + * @return immutable map + */ + public Map asUnmodifiableMap() { + return Map.copyOf(attributes); } /** diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java index 9b27cdd59..cf9dd2349 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import java.util.function.Function; + import dev.openfeature.sdk.internal.ExcludeFromGeneratedCoverageReport; import lombok.ToString; import lombok.experimental.Delegate; @@ -42,7 +43,7 @@ public ImmutableContext(String targetingKey) { * @param attributes evaluation context attributes */ public ImmutableContext(Map attributes) { - this("", attributes); + this(null, attributes); } /** @@ -53,9 +54,7 @@ public ImmutableContext(Map attributes) { */ public ImmutableContext(String targetingKey, Map attributes) { if (targetingKey != null && !targetingKey.trim().isEmpty()) { - final Map actualAttribs = new HashMap<>(attributes); - actualAttribs.put(TARGETING_KEY, new Value(targetingKey)); - this.structure = new ImmutableStructure(actualAttribs); + this.structure = new ImmutableStructure(targetingKey, attributes); } else { this.structure = new ImmutableStructure(attributes); } @@ -79,14 +78,14 @@ public String getTargetingKey() { @Override public EvaluationContext merge(EvaluationContext overridingContext) { if (overridingContext == null || overridingContext.isEmpty()) { - return new ImmutableContext(this.asMap()); + return new ImmutableContext(this.asUnmodifiableMap()); } if (this.isEmpty()) { - return new ImmutableContext(overridingContext.asMap()); + return new ImmutableContext(overridingContext.asUnmodifiableMap()); } return new ImmutableContext( - this.merge(ImmutableStructure::new, this.asMap(), overridingContext.asMap())); + this.merge(ImmutableStructure::new, this.asUnmodifiableMap(), overridingContext.asUnmodifiableMap())); } @SuppressWarnings("all") diff --git a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java index 170602000..06c2551ff 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableStructure.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableStructure.java @@ -36,7 +36,11 @@ public ImmutableStructure() { * @param attributes attributes. */ public ImmutableStructure(Map attributes) { - super(copyAttributes(attributes)); + super(copyAttributes(attributes, null)); + } + + protected ImmutableStructure(String targetingKey, Map attributes) { + super(copyAttributes(attributes, targetingKey)); } @Override @@ -62,11 +66,18 @@ public Map asMap() { } private static Map copyAttributes(Map in) { + return copyAttributes(in, null); + } + + private static Map copyAttributes(Map in, String targetingKey) { Map copy = new HashMap<>(); for (Entry entry : in.entrySet()) { copy.put(entry.getKey(), Optional.ofNullable(entry.getValue()).map((Value val) -> val.clone()).orElse(null)); } + if (targetingKey != null) { + copy.put(EvaluationContext.TARGETING_KEY, new Value(targetingKey)); + } return copy; } diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java index 6a47c83ef..1736584a8 100644 --- a/src/main/java/dev/openfeature/sdk/MutableContext.java +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -33,7 +33,7 @@ public MutableContext(String targetingKey) { } public MutableContext(Map attributes) { - this("", attributes); + this("", new HashMap<>(attributes)); } /** @@ -44,7 +44,7 @@ public MutableContext(Map attributes) { * @param attributes evaluation context attributes */ public MutableContext(String targetingKey, Map attributes) { - this.structure = new MutableStructure(attributes); + this.structure = new MutableStructure(new HashMap<>(attributes)); if (targetingKey != null && !targetingKey.trim().isEmpty()) { this.structure.attributes.put(TARGETING_KEY, new Value(targetingKey)); } @@ -122,7 +122,7 @@ public EvaluationContext merge(EvaluationContext overridingContext) { } Map merged = this.merge( - MutableStructure::new, this.asMap(), overridingContext.asMap()); + MutableStructure::new, this.asUnmodifiableMap(), overridingContext.asUnmodifiableMap()); return new MutableContext(merged); } diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java index 2162f4130..b8906c4a4 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java @@ -110,7 +110,6 @@ private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options, () -> FlagEvaluationOptions.builder().build()); Map hints = Collections.unmodifiableMap(flagOptions.getHookHints()); - ctx = ObjectUtils.defaultIfNull(ctx, () -> new ImmutableContext()); FlagEvaluationDetails details = null; List mergedHooks = null; @@ -183,17 +182,29 @@ private static void enrichDetailsWithErrorDefaults(T defaultValue, FlagEvalu * @return merged evaluation context */ private EvaluationContext mergeEvaluationContext(EvaluationContext invocationContext) { - final EvaluationContext apiContext = openfeatureApi.getEvaluationContext() != null - ? openfeatureApi.getEvaluationContext() - : new ImmutableContext(); - final EvaluationContext clientContext = this.getEvaluationContext() != null - ? this.getEvaluationContext() - : new ImmutableContext(); - final EvaluationContext transactionContext = openfeatureApi.getTransactionContext() != null - ? openfeatureApi.getTransactionContext() - : new ImmutableContext(); - - return apiContext.merge(transactionContext.merge(clientContext.merge(invocationContext))); + // avoid any unnecessary context instantiations and stream usage here; this is call with every evaluation. + final EvaluationContext apiContext = openfeatureApi.getEvaluationContext(); + final EvaluationContext clientContext = this.getEvaluationContext(); + final EvaluationContext transactionContext = openfeatureApi.getTransactionContext(); + final List contextsToMerge = new ArrayList<>(); + if (apiContext != null) { + contextsToMerge.add(apiContext); + } + if (transactionContext != null) { + contextsToMerge.add(transactionContext); + } + if (clientContext != null) { + contextsToMerge.add(clientContext); + } + if (invocationContext != null) { + contextsToMerge.add(invocationContext); + } + + EvaluationContext merged = new ImmutableContext(); + for (EvaluationContext evaluationContext : contextsToMerge) { + merged = merged.merge(evaluationContext); + } + return merged; } private ProviderEvaluation createProviderEvaluation( diff --git a/src/main/java/dev/openfeature/sdk/Structure.java b/src/main/java/dev/openfeature/sdk/Structure.java index 02e36629e..dfc3c91fb 100644 --- a/src/main/java/dev/openfeature/sdk/Structure.java +++ b/src/main/java/dev/openfeature/sdk/Structure.java @@ -46,6 +46,14 @@ public interface Structure { */ Map asMap(); + /** + * Get all values, as a map of Values. + * + * @return all attributes on the structure into a Map + */ + Map asUnmodifiableMap(); + + /** * Get all values, with as a map of Object. * @@ -95,7 +103,7 @@ default Object convertValue(Value value) { if (value.isStructure()) { Structure s = value.asStructure(); - return s.asMap() + return s.asUnmodifiableMap() .entrySet() .stream() .collect(HashMap::new, @@ -126,14 +134,15 @@ default Map merge(Function merged = new HashMap<>(base); for (Entry overridingEntry : overriding.entrySet()) { String key = overridingEntry.getKey(); if (overridingEntry.getValue().isStructure() && merged.containsKey(key) && merged.get(key).isStructure()) { Structure mergedValue = merged.get(key).asStructure(); Structure overridingValue = overridingEntry.getValue().asStructure(); - Map newMap = this.merge(newStructure, mergedValue.asMap(), overridingValue.asMap()); + Map newMap = this.merge(newStructure, mergedValue.asUnmodifiableMap(), + overridingValue.asUnmodifiableMap()); merged.put(key, new Value(newStructure.apply(newMap))); } else { merged.put(key, overridingEntry.getValue()); diff --git a/src/main/java/dev/openfeature/sdk/Value.java b/src/main/java/dev/openfeature/sdk/Value.java index f0fdc8d45..7464ce5af 100644 --- a/src/main/java/dev/openfeature/sdk/Value.java +++ b/src/main/java/dev/openfeature/sdk/Value.java @@ -274,7 +274,7 @@ protected Value clone() { return new Value(copy); } if (this.isStructure()) { - return new Value(new ImmutableStructure(this.asStructure().asMap())); + return new Value(new ImmutableStructure(this.asStructure().asUnmodifiableMap())); } if (this.isInstant()) { Instant copy = Instant.ofEpochMilli(this.asInstant().toEpochMilli()); From 6a879b1dc5a62dd6a07750914d628beb08f1392d Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Tue, 22 Oct 2024 15:09:15 -0400 Subject: [PATCH 2/5] fixup: use Collections.unmodifiableMap Signed-off-by: Todd Baert --- benchmark.txt | 248 +++++++++--------- .../openfeature/sdk/AbstractStructure.java | 3 +- 2 files changed, 126 insertions(+), 125 deletions(-) diff --git a/benchmark.txt b/benchmark.txt index 1870818b2..9e780fcb8 100644 --- a/benchmark.txt +++ b/benchmark.txt @@ -36,8 +36,8 @@ 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/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. @@ -131,137 +131,137 @@ Iteration 1: num #instances #bytes class name (module) ------------------------------------------------------- 1: 730464 35062272 java.util.HashMap (java.base@21.0.4) 2: 320497 19970088 [Ljava.util.HashMap$Node; (java.base@21.0.4) - 3: 475993 15945896 [Ljava.lang.Object; (java.base@21.0.4) - 4: 472017 15104544 java.util.HashMap$Node (java.base@21.0.4) - 5: 47814 9730240 [B (java.base@21.0.4) - 6: 315910 7294560 [Ljava.util.Map$Entry; (java.base@21.0.4) - 7: 303621 7286904 java.util.KeyValueHolder (java.base@21.0.4) - 8: 224606 7187392 java.util.HashMap$EntryIterator (java.base@21.0.4) - 9: 210000 6720000 java.util.ImmutableCollections$Map1 (java.base@21.0.4) - 10: 370062 5920992 java.util.HashMap$EntrySet (java.base@21.0.4) - 11: 370003 5920048 dev.openfeature.sdk.ImmutableStructure - 12: 360003 5760048 dev.openfeature.sdk.ImmutableContext - 13: 350006 5600096 dev.openfeature.sdk.Value - 14: 164 5578776 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) - 15: 174215 4181160 java.util.ArrayList (java.base@21.0.4) - 16: 173935 4174440 java.util.ImmutableCollections$Set12 (java.base@21.0.4) - 17: 173706 4168944 java.util.ImmutableCollections$Set12$1 (java.base@21.0.4) - 18: 100000 4000000 dev.openfeature.sdk.HookContext - 19: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder - 20: 101150 3236800 java.util.ArrayList$Itr (java.base@21.0.4) + 3: 472017 15104544 java.util.HashMap$Node (java.base@21.0.4) + 4: 394936 12637952 java.util.HashMap$EntryIterator (java.base@21.0.4) + 5: 355992 10905888 [Ljava.lang.Object; (java.base@21.0.4) + 6: 320002 10240064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) + 7: 47810 9730152 [B (java.base@21.0.4) + 8: 370062 5920992 java.util.HashMap$EntrySet (java.base@21.0.4) + 9: 370003 5920048 dev.openfeature.sdk.ImmutableStructure + 10: 360003 5760048 dev.openfeature.sdk.ImmutableContext + 11: 350006 5600096 dev.openfeature.sdk.Value + 12: 164 5212816 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) + 13: 299744 4795904 java.util.Optional (java.base@21.0.4) + 14: 188295 4519080 java.util.ArrayList (java.base@21.0.4) + 15: 184836 4436064 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1 (java.base@21.0.4) + 16: 270000 4320000 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet (java.base@21.0.4) + 17: 100000 4000000 dev.openfeature.sdk.HookContext + 18: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder + 19: 247118 3953888 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry (java.base@21.0.4) + 20: 113781 3640992 java.util.ArrayList$Itr (java.base@21.0.4) 21: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails 22: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation - 23: 60007 1920224 java.util.ImmutableCollections$MapN (java.base@21.0.4) - 24: 112818 1805088 java.util.Optional (java.base@21.0.4) - 25: 50002 1600064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) - 26: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x00007b7ba402fa78 - 27: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) - 28: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata - 29: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder - 30: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x00007b7ba4082800 - 31: 60001 1440024 java.util.ImmutableCollections$MapN$MapNIterator (java.base@21.0.4) - 32: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions - 33: 60001 960016 java.util.ImmutableCollections$MapN$1 (java.base@21.0.4) - 34: 35554 853296 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder - 35: 15203 729744 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder - 36: 4490 721440 [I (java.base@21.0.4) - 37: 26592 638208 java.lang.String (java.base@21.0.4) - 38: 34195 547120 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x00007b7ba402eae8 - 39: 12448 497920 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder - 40: 20260 486240 dev.openfeature.sdk.HookSupport$$Lambda/0x00007b7ba4081978 - 41: 17102 410448 dev.openfeature.sdk.HookSupport$$Lambda/0x00007b7ba4081748 - 42: 1461 390008 [J (java.base@21.0.4) - 43: 2356 288216 java.lang.Class (java.base@21.0.4) - 44: 4632 259392 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) - 45: 10001 240024 java.lang.Double (java.base@21.0.4) - 46: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) - 47: 6009 144216 java.lang.StringBuilder (java.base@21.0.4) - 48: 180 140968 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) - 49: 3832 122624 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) - 50: 48 122168 [C (java.base@21.0.4) - 51: 1440 113512 [S (java.base@21.0.4) - 52: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) - 53: 3043 79984 [Ljava.lang.Class; (java.base@21.0.4) - 54: 1351 75656 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) - 55: 1567 75216 java.lang.invoke.MemberName (java.base@21.0.4) - 56: 334 74816 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) - 57: 1809 72360 java.lang.invoke.MethodType (java.base@21.0.4) - 58: 1089 69696 java.net.URL (java.base@21.0.4) - 59: 3165 50640 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) - 60: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$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: 694 22208 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: 904 14464 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: 221 10608 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: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) - 96: 118 9440 jdk.internal.event.DeserializationEvent (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: 121 7744 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) - 103: 70 7280 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) - 104: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) - 105: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) - 106: 200 6400 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) - 107: 156 6240 java.util.StringJoiner (java.base@21.0.4) - 108: 153 6120 java.io.FileDescriptor (java.base@21.0.4) - 109: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) - 110: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) - 111: 374 5984 java.lang.Byte (java.base@21.0.4) - 112: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (java.base@21.0.4) - 113: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) - 114: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) - 115: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) - 116: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) - 117: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) - 118: 98 5488 java.lang.Module (java.base@21.0.4) + 23: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x0000730fb402fa78 + 24: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) + 25: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata + 26: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder + 27: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x0000730fb4082800 + 28: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions + 29: 17443 837264 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder + 30: 34414 825936 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder + 31: 4490 721440 [I (java.base@21.0.4) + 32: 44679 714864 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x0000730fb402eae8 + 33: 17203 688120 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder + 34: 26590 638160 java.lang.String (java.base@21.0.4) + 35: 18822 451728 dev.openfeature.sdk.HookSupport$$Lambda/0x0000730fb4081748 + 36: 18792 451008 dev.openfeature.sdk.HookSupport$$Lambda/0x0000730fb4081978 + 37: 1461 390008 [J (java.base@21.0.4) + 38: 2359 288560 java.lang.Class (java.base@21.0.4) + 39: 4632 259392 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) + 40: 10001 240024 java.lang.Double (java.base@21.0.4) + 41: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) + 42: 6007 144168 java.lang.StringBuilder (java.base@21.0.4) + 43: 180 140968 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) + 44: 3828 122496 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) + 45: 48 122168 [C (java.base@21.0.4) + 46: 1440 113512 [S (java.base@21.0.4) + 47: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) + 48: 3031 79600 [Ljava.lang.Class; (java.base@21.0.4) + 49: 1351 75656 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) + 50: 1563 75024 java.lang.invoke.MemberName (java.base@21.0.4) + 51: 334 74816 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) + 52: 1799 71960 java.lang.invoke.MethodType (java.base@21.0.4) + 53: 1089 69696 java.net.URL (java.base@21.0.4) + 54: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) + 55: 3147 50352 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) + 56: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) + 57: 1225 39200 java.io.File (java.base@21.0.4) + 58: 779 37392 jdk.internal.org.objectweb.asm.Frame (java.base@21.0.4) + 59: 243 25272 java.util.jar.JarFile$JarFileEntry (java.base@21.0.4) + 60: 794 25248 [Ljava.lang.String; (java.base@21.0.4) + 61: 622 24880 java.lang.NoSuchFieldException (java.base@21.0.4) + 62: 571 22840 java.util.LinkedHashMap$Entry (java.base@21.0.4) + 63: 474 22752 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) + 64: 690 22080 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) + 65: 828 19872 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) + 66: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) + 67: 118 17936 jdk.internal.org.objectweb.asm.ClassWriter (java.base@21.0.4) + 68: 380 16824 [Ljava.lang.invoke.LambdaForm$Name; (java.base@21.0.4) + 69: 625 15000 java.lang.Long (java.base@21.0.4) + 70: 463 14816 java.lang.invoke.LambdaForm$Name (java.base@21.0.4) + 71: 904 14464 java.lang.Object (java.base@21.0.4) + 72: 198 14256 java.lang.reflect.Constructor (java.base@21.0.4) + 73: 249 13944 java.util.zip.ZipFile$ZipFileInputStream (java.base@21.0.4) + 74: 334 13360 jdk.internal.org.objectweb.asm.Handler (java.base@21.0.4) + 75: 202 12928 java.util.concurrent.ConcurrentHashMap (java.base@21.0.4) + 76: 201 12864 jdk.internal.org.objectweb.asm.FieldWriter (java.base@21.0.4) + 77: 316 12640 java.util.WeakHashMap$Entry (java.base@21.0.4) + 78: 102 12240 java.io.ObjectStreamClass (java.base@21.0.4) + 79: 249 11952 java.util.zip.ZipFile$ZipFileInflaterInputStream (java.base@21.0.4) + 80: 359 11488 jdk.internal.org.objectweb.asm.Type (java.base@21.0.4) + 81: 465 11160 java.lang.invoke.ResolvedMethodName (java.base@21.0.4) + 82: 464 11136 jdk.internal.org.objectweb.asm.Edge (java.base@21.0.4) + 83: 341 10912 jdk.internal.math.FDBigInteger (java.base@21.0.4) + 84: 94 10728 [Ljava.lang.reflect.Field; (java.base@21.0.4) + 85: 266 10640 java.lang.NoSuchMethodException (java.base@21.0.4) + 86: 266 10640 java.security.CodeSource (java.base@21.0.4) + 87: 221 10608 java.lang.invoke.DirectMethodHandle$Constructor (java.base@21.0.4) + 88: 264 10560 sun.security.util.KnownOIDs (java.base@21.0.4) + 89: 75 10200 sun.nio.fs.UnixFileAttributes (java.base@21.0.4) + 90: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) + 91: 118 9440 jdk.internal.event.DeserializationEvent (java.base@21.0.4) + 92: 115 9200 [Ljava.util.WeakHashMap$Entry; (java.base@21.0.4) + 93: 368 8832 java.lang.module.ModuleDescriptor$Exports (java.base@21.0.4) + 94: 63 8384 [Ljava.lang.invoke.MethodHandle; (java.base@21.0.4) + 95: 146 8176 java.io.FileCleanable (java.base@21.0.4) + 96: 125 8000 java.lang.Class$ReflectionData (java.base@21.0.4) + 97: 323 7752 java.util.ImmutableCollections$Set12 (java.base@21.0.4) + 98: 121 7744 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) + 99: 70 7280 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) + 100: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) + 101: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) + 102: 200 6400 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) + 103: 156 6240 java.util.StringJoiner (java.base@21.0.4) + 104: 153 6120 java.io.FileDescriptor (java.base@21.0.4) + 105: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) + 106: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) + 107: 374 5984 java.lang.Byte (java.base@21.0.4) + 108: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (java.base@21.0.4) + 109: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) + 110: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) + 111: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) + 112: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) + 113: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) + 114: 98 5488 java.lang.Module (java.base@21.0.4) + 115: 219 5256 java.lang.PublicMethods$MethodList (java.base@21.0.4) + 116: 65 5200 java.net.URI (java.base@21.0.4) + 117: 215 5104 [Ljdk.internal.org.objectweb.asm.Type; (java.base@21.0.4) + 118: 158 5056 java.lang.invoke.MethodTypeForm (java.base@21.0.4) truncated... -Total 6560337 206465856 +Total 6456040 198081344 0.148 s/op - +totalAllocatedBytes: 206465856.000 bytes - +totalAllocatedInstances: 6560337.000 instances + +totalAllocatedBytes: 198081344.000 bytes + +totalAllocatedInstances: 6456040.000 instances +totalHeap: 521412608.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedBytes": - 206465856.000 bytes + 198081344.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances": - 6560337.000 instances + 6456040.000 instances Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalHeap": 521412608.000 bytes @@ -283,12 +283,12 @@ modes can be very significant. Please make sure you use the consistent Blackhole Benchmark Mode Cnt Score Error Units AllocationBenchmark.run ss 0.148 s/op -AllocationBenchmark.run:+totalAllocatedBytes ss 206465856.000 bytes -AllocationBenchmark.run:+totalAllocatedInstances ss 6560337.000 instances +AllocationBenchmark.run:+totalAllocatedBytes ss 198081344.000 bytes +AllocationBenchmark.run:+totalAllocatedInstances ss 6456040.000 instances AllocationBenchmark.run:+totalHeap ss 521412608.000 bytes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 8.274 s -[INFO] Finished at: 2024-10-23T12:35:21-04:00 +[INFO] Total time: 7.987 s +[INFO] Finished at: 2024-10-23T12:35:58-04:00 [INFO] ------------------------------------------------------------------------ diff --git a/src/main/java/dev/openfeature/sdk/AbstractStructure.java b/src/main/java/dev/openfeature/sdk/AbstractStructure.java index 56e26559a..86fdde41a 100644 --- a/src/main/java/dev/openfeature/sdk/AbstractStructure.java +++ b/src/main/java/dev/openfeature/sdk/AbstractStructure.java @@ -2,6 +2,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.Collections; @SuppressWarnings({ "PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType" }) abstract class AbstractStructure implements Structure { @@ -26,7 +27,7 @@ public boolean isEmpty() { * @return immutable map */ public Map asUnmodifiableMap() { - return Map.copyOf(attributes); + return Collections.unmodifiableMap(attributes); } /** From 5cd181f1842f59b5c6616841e9a6a4f55948ca8f Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Wed, 23 Oct 2024 10:40:20 -0400 Subject: [PATCH 3/5] fixup: pr feedback Signed-off-by: Todd Baert --- benchmark.txt | 256 +++++++++--------- .../openfeature/sdk/EvaluationContext.java | 41 +++ .../dev/openfeature/sdk/ImmutableContext.java | 15 +- .../dev/openfeature/sdk/MutableContext.java | 4 +- .../openfeature/sdk/OpenFeatureClient.java | 85 +++--- .../java/dev/openfeature/sdk/Structure.java | 38 --- 6 files changed, 224 insertions(+), 215 deletions(-) diff --git a/benchmark.txt b/benchmark.txt index 9e780fcb8..289acd147 100644 --- a/benchmark.txt +++ b/benchmark.txt @@ -36,9 +36,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/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/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. @@ -129,139 +129,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: 730464 35062272 java.util.HashMap (java.base@21.0.4) - 2: 320497 19970088 [Ljava.util.HashMap$Node; (java.base@21.0.4) - 3: 472017 15104544 java.util.HashMap$Node (java.base@21.0.4) - 4: 394936 12637952 java.util.HashMap$EntryIterator (java.base@21.0.4) - 5: 355992 10905888 [Ljava.lang.Object; (java.base@21.0.4) - 6: 320002 10240064 java.util.Collections$UnmodifiableMap (java.base@21.0.4) - 7: 47810 9730152 [B (java.base@21.0.4) - 8: 370062 5920992 java.util.HashMap$EntrySet (java.base@21.0.4) - 9: 370003 5920048 dev.openfeature.sdk.ImmutableStructure - 10: 360003 5760048 dev.openfeature.sdk.ImmutableContext - 11: 350006 5600096 dev.openfeature.sdk.Value - 12: 164 5212816 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) - 13: 299744 4795904 java.util.Optional (java.base@21.0.4) - 14: 188295 4519080 java.util.ArrayList (java.base@21.0.4) - 15: 184836 4436064 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1 (java.base@21.0.4) - 16: 270000 4320000 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet (java.base@21.0.4) - 17: 100000 4000000 dev.openfeature.sdk.HookContext - 18: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder - 19: 247118 3953888 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry (java.base@21.0.4) - 20: 113781 3640992 java.util.ArrayList$Itr (java.base@21.0.4) + 1: 559671 26864208 java.util.HashMap (java.base@21.0.4) + 2: 352017 11264544 java.util.HashMap$Node (java.base@21.0.4) + 3: 210497 11170088 [Ljava.util.HashMap$Node; (java.base@21.0.4) + 4: 47815 9732672 [B (java.base@21.0.4) + 5: 279526 8944832 java.util.HashMap$EntryIterator (java.base@21.0.4) + 6: 305991 8105872 [Ljava.lang.Object; (java.base@21.0.4) + 7: 445455 7127280 java.util.Optional (java.base@21.0.4) + 8: 199209 6374688 java.util.Collections$UnmodifiableMap (java.base@21.0.4) + 9: 154 4368416 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) + 10: 100000 4000000 dev.openfeature.sdk.HookContext + 11: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder + 12: 230006 3680096 dev.openfeature.sdk.Value + 13: 210062 3360992 java.util.HashMap$EntrySet (java.base@21.0.4) + 14: 139788 3354912 java.util.ArrayList (java.base@21.0.4) + 15: 199210 3187360 dev.openfeature.sdk.ImmutableStructure + 16: 130219 3125256 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1 (java.base@21.0.4) + 17: 189210 3027360 dev.openfeature.sdk.ImmutableContext + 18: 177267 2836272 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry (java.base@21.0.4) + 19: 82425 2637600 java.util.ArrayList$Itr (java.base@21.0.4) + 20: 149207 2387312 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet (java.base@21.0.4) 21: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails 22: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation - 23: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x0000730fb402fa78 - 24: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) - 25: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata - 26: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder - 27: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x0000730fb4082800 - 28: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions - 29: 17443 837264 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder - 30: 34414 825936 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder - 31: 4490 721440 [I (java.base@21.0.4) - 32: 44679 714864 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x0000730fb402eae8 - 33: 17203 688120 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder - 34: 26590 638160 java.lang.String (java.base@21.0.4) - 35: 18822 451728 dev.openfeature.sdk.HookSupport$$Lambda/0x0000730fb4081748 - 36: 18792 451008 dev.openfeature.sdk.HookSupport$$Lambda/0x0000730fb4081978 - 37: 1461 390008 [J (java.base@21.0.4) - 38: 2359 288560 java.lang.Class (java.base@21.0.4) - 39: 4632 259392 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) - 40: 10001 240024 java.lang.Double (java.base@21.0.4) - 41: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) - 42: 6007 144168 java.lang.StringBuilder (java.base@21.0.4) - 43: 180 140968 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) - 44: 3828 122496 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) - 45: 48 122168 [C (java.base@21.0.4) - 46: 1440 113512 [S (java.base@21.0.4) - 47: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) - 48: 3031 79600 [Ljava.lang.Class; (java.base@21.0.4) - 49: 1351 75656 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) - 50: 1563 75024 java.lang.invoke.MemberName (java.base@21.0.4) - 51: 334 74816 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) - 52: 1799 71960 java.lang.invoke.MethodType (java.base@21.0.4) - 53: 1089 69696 java.net.URL (java.base@21.0.4) - 54: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) - 55: 3147 50352 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) - 56: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) - 57: 1225 39200 java.io.File (java.base@21.0.4) - 58: 779 37392 jdk.internal.org.objectweb.asm.Frame (java.base@21.0.4) - 59: 243 25272 java.util.jar.JarFile$JarFileEntry (java.base@21.0.4) - 60: 794 25248 [Ljava.lang.String; (java.base@21.0.4) - 61: 622 24880 java.lang.NoSuchFieldException (java.base@21.0.4) - 62: 571 22840 java.util.LinkedHashMap$Entry (java.base@21.0.4) - 63: 474 22752 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) - 64: 690 22080 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) - 65: 828 19872 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) - 66: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) - 67: 118 17936 jdk.internal.org.objectweb.asm.ClassWriter (java.base@21.0.4) - 68: 380 16824 [Ljava.lang.invoke.LambdaForm$Name; (java.base@21.0.4) - 69: 625 15000 java.lang.Long (java.base@21.0.4) - 70: 463 14816 java.lang.invoke.LambdaForm$Name (java.base@21.0.4) - 71: 904 14464 java.lang.Object (java.base@21.0.4) - 72: 198 14256 java.lang.reflect.Constructor (java.base@21.0.4) - 73: 249 13944 java.util.zip.ZipFile$ZipFileInputStream (java.base@21.0.4) - 74: 334 13360 jdk.internal.org.objectweb.asm.Handler (java.base@21.0.4) - 75: 202 12928 java.util.concurrent.ConcurrentHashMap (java.base@21.0.4) - 76: 201 12864 jdk.internal.org.objectweb.asm.FieldWriter (java.base@21.0.4) - 77: 316 12640 java.util.WeakHashMap$Entry (java.base@21.0.4) - 78: 102 12240 java.io.ObjectStreamClass (java.base@21.0.4) - 79: 249 11952 java.util.zip.ZipFile$ZipFileInflaterInputStream (java.base@21.0.4) - 80: 359 11488 jdk.internal.org.objectweb.asm.Type (java.base@21.0.4) - 81: 465 11160 java.lang.invoke.ResolvedMethodName (java.base@21.0.4) - 82: 464 11136 jdk.internal.org.objectweb.asm.Edge (java.base@21.0.4) - 83: 341 10912 jdk.internal.math.FDBigInteger (java.base@21.0.4) - 84: 94 10728 [Ljava.lang.reflect.Field; (java.base@21.0.4) - 85: 266 10640 java.lang.NoSuchMethodException (java.base@21.0.4) - 86: 266 10640 java.security.CodeSource (java.base@21.0.4) - 87: 221 10608 java.lang.invoke.DirectMethodHandle$Constructor (java.base@21.0.4) - 88: 264 10560 sun.security.util.KnownOIDs (java.base@21.0.4) - 89: 75 10200 sun.nio.fs.UnixFileAttributes (java.base@21.0.4) - 90: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) - 91: 118 9440 jdk.internal.event.DeserializationEvent (java.base@21.0.4) - 92: 115 9200 [Ljava.util.WeakHashMap$Entry; (java.base@21.0.4) - 93: 368 8832 java.lang.module.ModuleDescriptor$Exports (java.base@21.0.4) - 94: 63 8384 [Ljava.lang.invoke.MethodHandle; (java.base@21.0.4) - 95: 146 8176 java.io.FileCleanable (java.base@21.0.4) - 96: 125 8000 java.lang.Class$ReflectionData (java.base@21.0.4) - 97: 323 7752 java.util.ImmutableCollections$Set12 (java.base@21.0.4) - 98: 121 7744 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) - 99: 70 7280 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) - 100: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) - 101: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) - 102: 200 6400 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) - 103: 156 6240 java.util.StringJoiner (java.base@21.0.4) - 104: 153 6120 java.io.FileDescriptor (java.base@21.0.4) - 105: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) - 106: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) - 107: 374 5984 java.lang.Byte (java.base@21.0.4) + 23: 34652 1663296 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder + 24: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x00007bf8a002fa78 + 25: 50000 1600000 [Ldev.openfeature.sdk.EvaluationContext; + 26: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) + 27: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata + 28: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder + 29: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x00007bf8a0082800 + 30: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions + 31: 44836 1076064 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder + 32: 26278 1051120 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder + 33: 40785 978840 dev.openfeature.sdk.HookSupport$$Lambda/0x00007bf8a0081da8 + 34: 40423 970152 dev.openfeature.sdk.HookSupport$$Lambda/0x00007bf8a0081b78 + 35: 54212 867392 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x00007bf8a002eae8 + 36: 4490 721440 [I (java.base@21.0.4) + 37: 26594 638256 java.lang.String (java.base@21.0.4) + 38: 1461 390008 [J (java.base@21.0.4) + 39: 2361 288784 java.lang.Class (java.base@21.0.4) + 40: 4632 259392 jdk.internal.org.objectweb.asm.SymbolTable$Entry (java.base@21.0.4) + 41: 10001 240024 java.lang.Double (java.base@21.0.4) + 42: 2502 180144 java.lang.reflect.Field (java.base@21.0.4) + 43: 6007 144168 java.lang.StringBuilder (java.base@21.0.4) + 44: 180 140968 [Ljdk.internal.org.objectweb.asm.SymbolTable$Entry; (java.base@21.0.4) + 45: 3827 122464 java.util.concurrent.ConcurrentHashMap$Node (java.base@21.0.4) + 46: 48 122168 [C (java.base@21.0.4) + 47: 1440 113512 [S (java.base@21.0.4) + 48: 1201 105688 java.lang.reflect.Method (java.base@21.0.4) + 49: 3031 79600 [Ljava.lang.Class; (java.base@21.0.4) + 50: 1351 75656 jdk.internal.org.objectweb.asm.Label (java.base@21.0.4) + 51: 1561 74928 java.lang.invoke.MemberName (java.base@21.0.4) + 52: 334 74816 jdk.internal.org.objectweb.asm.MethodWriter (java.base@21.0.4) + 53: 1799 71960 java.lang.invoke.MethodType (java.base@21.0.4) + 54: 1089 69696 java.net.URL (java.base@21.0.4) + 55: 121 50512 [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.4) + 56: 3147 50352 jdk.internal.util.StrongReferenceKey (java.base@21.0.4) + 57: 1057 42280 java.io.ObjectStreamField (java.base@21.0.4) + 58: 1225 39200 java.io.File (java.base@21.0.4) + 59: 779 37392 jdk.internal.org.objectweb.asm.Frame (java.base@21.0.4) + 60: 243 25272 java.util.jar.JarFile$JarFileEntry (java.base@21.0.4) + 61: 794 25248 [Ljava.lang.String; (java.base@21.0.4) + 62: 622 24880 java.lang.NoSuchFieldException (java.base@21.0.4) + 63: 571 22840 java.util.LinkedHashMap$Entry (java.base@21.0.4) + 64: 474 22752 jdk.internal.ref.CleanerImpl$PhantomCleanableRef (java.base@21.0.4) + 65: 690 22080 jdk.internal.util.WeakReferenceKey (java.base@21.0.4) + 66: 828 19872 jdk.internal.org.objectweb.asm.ByteVector (java.base@21.0.4) + 67: 248 18848 [Ljava.lang.ref.SoftReference; (java.base@21.0.4) + 68: 118 17936 jdk.internal.org.objectweb.asm.ClassWriter (java.base@21.0.4) + 69: 380 16824 [Ljava.lang.invoke.LambdaForm$Name; (java.base@21.0.4) + 70: 625 15000 java.lang.Long (java.base@21.0.4) + 71: 463 14816 java.lang.invoke.LambdaForm$Name (java.base@21.0.4) + 72: 904 14464 java.lang.Object (java.base@21.0.4) + 73: 198 14256 java.lang.reflect.Constructor (java.base@21.0.4) + 74: 249 13944 java.util.zip.ZipFile$ZipFileInputStream (java.base@21.0.4) + 75: 334 13360 jdk.internal.org.objectweb.asm.Handler (java.base@21.0.4) + 76: 202 12928 java.util.concurrent.ConcurrentHashMap (java.base@21.0.4) + 77: 201 12864 jdk.internal.org.objectweb.asm.FieldWriter (java.base@21.0.4) + 78: 316 12640 java.util.WeakHashMap$Entry (java.base@21.0.4) + 79: 102 12240 java.io.ObjectStreamClass (java.base@21.0.4) + 80: 249 11952 java.util.zip.ZipFile$ZipFileInflaterInputStream (java.base@21.0.4) + 81: 359 11488 jdk.internal.org.objectweb.asm.Type (java.base@21.0.4) + 82: 465 11160 java.lang.invoke.ResolvedMethodName (java.base@21.0.4) + 83: 464 11136 jdk.internal.org.objectweb.asm.Edge (java.base@21.0.4) + 84: 341 10912 jdk.internal.math.FDBigInteger (java.base@21.0.4) + 85: 94 10728 [Ljava.lang.reflect.Field; (java.base@21.0.4) + 86: 266 10640 java.lang.NoSuchMethodException (java.base@21.0.4) + 87: 266 10640 java.security.CodeSource (java.base@21.0.4) + 88: 221 10608 java.lang.invoke.DirectMethodHandle$Constructor (java.base@21.0.4) + 89: 264 10560 sun.security.util.KnownOIDs (java.base@21.0.4) + 90: 75 10200 sun.nio.fs.UnixFileAttributes (java.base@21.0.4) + 91: 245 9800 java.lang.ref.SoftReference (java.base@21.0.4) + 92: 118 9440 jdk.internal.event.DeserializationEvent (java.base@21.0.4) + 93: 115 9200 [Ljava.util.WeakHashMap$Entry; (java.base@21.0.4) + 94: 368 8832 java.lang.module.ModuleDescriptor$Exports (java.base@21.0.4) + 95: 63 8384 [Ljava.lang.invoke.MethodHandle; (java.base@21.0.4) + 96: 146 8176 java.io.FileCleanable (java.base@21.0.4) + 97: 125 8000 java.lang.Class$ReflectionData (java.base@21.0.4) + 98: 323 7752 java.util.ImmutableCollections$Set12 (java.base@21.0.4) + 99: 121 7744 jdk.internal.org.objectweb.asm.SymbolTable (java.base@21.0.4) + 100: 70 7280 java.lang.invoke.InnerClassLambdaMetafactory (java.base@21.0.4) + 101: 144 6912 jdk.internal.org.objectweb.asm.AnnotationWriter (java.base@21.0.4) + 102: 167 6680 jdk.internal.loader.URLClassPath$JarLoader$2 (java.base@21.0.4) + 103: 199 6368 java.lang.invoke.MethodHandles$Lookup (java.base@21.0.4) + 104: 156 6240 java.util.StringJoiner (java.base@21.0.4) + 105: 153 6120 java.io.FileDescriptor (java.base@21.0.4) + 106: 126 6048 java.lang.invoke.LambdaForm (java.base@21.0.4) + 107: 77 6016 [Ljava.lang.reflect.Method; (java.base@21.0.4) 108: 249 5976 java.util.zip.ZipFile$InflaterCleanupAction (java.base@21.0.4) - 109: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) - 110: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) - 111: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) - 112: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) - 113: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) - 114: 98 5488 java.lang.Module (java.base@21.0.4) - 115: 219 5256 java.lang.PublicMethods$MethodList (java.base@21.0.4) - 116: 65 5200 java.net.URI (java.base@21.0.4) - 117: 215 5104 [Ljdk.internal.org.objectweb.asm.Type; (java.base@21.0.4) - 118: 158 5056 java.lang.invoke.MethodTypeForm (java.base@21.0.4) + 109: 373 5968 java.lang.Byte (java.base@21.0.4) + 110: 74 5920 java.util.zip.ZipFile$Source (java.base@21.0.4) + 111: 82 5720 [Ljava.io.ObjectStreamField; (java.base@21.0.4) + 112: 40 5640 [Ljava.lang.ClassValue$Entry; (java.base@21.0.4) + 113: 234 5616 java.util.jar.Attributes$Name (java.base@21.0.4) + 114: 174 5568 java.util.concurrent.locks.ReentrantLock$NonfairSync (java.base@21.0.4) + 115: 98 5488 java.lang.Module (java.base@21.0.4) + 116: 219 5256 java.lang.PublicMethods$MethodList (java.base@21.0.4) + 117: 65 5200 java.net.URI (java.base@21.0.4) + 118: 215 5104 [Ljdk.internal.org.objectweb.asm.Type; (java.base@21.0.4) truncated... -Total 6456040 198081344 +Total 5107873 156138304 -0.148 s/op - +totalAllocatedBytes: 198081344.000 bytes - +totalAllocatedInstances: 6456040.000 instances +0.187 s/op + +totalAllocatedBytes: 156138304.000 bytes + +totalAllocatedInstances: 5107873.000 instances +totalHeap: 521412608.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedBytes": - 198081344.000 bytes + 156138304.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances": - 6456040.000 instances + 5107873.000 instances Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalHeap": 521412608.000 bytes @@ -282,13 +282,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.148 s/op -AllocationBenchmark.run:+totalAllocatedBytes ss 198081344.000 bytes -AllocationBenchmark.run:+totalAllocatedInstances ss 6456040.000 instances +AllocationBenchmark.run ss 0.187 s/op +AllocationBenchmark.run:+totalAllocatedBytes ss 156138304.000 bytes +AllocationBenchmark.run:+totalAllocatedInstances ss 5107873.000 instances AllocationBenchmark.run:+totalHeap ss 521412608.000 bytes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 7.987 s -[INFO] Finished at: 2024-10-23T12:35:58-04:00 +[INFO] Total time: 8.226 s +[INFO] Finished at: 2024-10-23T12:36:22-04:00 [INFO] ------------------------------------------------------------------------ diff --git a/src/main/java/dev/openfeature/sdk/EvaluationContext.java b/src/main/java/dev/openfeature/sdk/EvaluationContext.java index b95ea454d..142c78c32 100644 --- a/src/main/java/dev/openfeature/sdk/EvaluationContext.java +++ b/src/main/java/dev/openfeature/sdk/EvaluationContext.java @@ -1,5 +1,10 @@ package dev.openfeature.sdk; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; + /** * The EvaluationContext is a container for arbitrary contextual data * that can be used as a basis for dynamic evaluation. @@ -19,4 +24,40 @@ public interface EvaluationContext extends Structure { * @return resulting merged context */ EvaluationContext merge(EvaluationContext overridingContext); + + /** + * Recursively merges the base Value map with the overriding map. + * + * @param Structure type + * @param newStructure function to create the right structure(s) for Values + * @param base base map to merge + * @param overriding overriding map to merge + * @return resulting merged map + */ + static Map mergeMaps(Function, Structure> newStructure, + Map base, + Map overriding) { + + if (base == null || base.isEmpty()) { + return overriding; + } + if (overriding == null || overriding.isEmpty()) { + return base; + } + + final Map merged = new HashMap<>(base); + for (Entry overridingEntry : overriding.entrySet()) { + String key = overridingEntry.getKey(); + if (overridingEntry.getValue().isStructure() && merged.containsKey(key) && merged.get(key).isStructure()) { + Structure mergedValue = merged.get(key).asStructure(); + Structure overridingValue = overridingEntry.getValue().asStructure(); + Map newMap = mergeMaps(newStructure, mergedValue.asUnmodifiableMap(), + overridingValue.asUnmodifiableMap()); + merged.put(key, new Value(newStructure.apply(newMap))); + } else { + merged.put(key, overridingEntry.getValue()); + } + } + return merged; + } } diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java index cf9dd2349..53d49a78f 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java @@ -11,7 +11,8 @@ /** * The EvaluationContext is a container for arbitrary contextual data * that can be used as a basis for dynamic evaluation. - * The ImmutableContext is an EvaluationContext implementation which is threadsafe, and whose attributes can + * The ImmutableContext is an EvaluationContext implementation which is + * threadsafe, and whose attributes can * not be modified after instantiation. */ @ToString @@ -22,7 +23,8 @@ public final class ImmutableContext implements EvaluationContext { private final ImmutableStructure structure; /** - * Create an immutable context with an empty targeting_key and attributes provided. + * Create an immutable context with an empty targeting_key and attributes + * provided. */ public ImmutableContext() { this(new HashMap<>()); @@ -70,7 +72,8 @@ public String getTargetingKey() { } /** - * Merges this EvaluationContext object with the passed EvaluationContext, overriding in case of conflict. + * Merges this EvaluationContext object with the passed EvaluationContext, + * overriding in case of conflict. * * @param overridingContext overriding context * @return new, resulting merged context @@ -85,16 +88,16 @@ public EvaluationContext merge(EvaluationContext overridingContext) { } return new ImmutableContext( - this.merge(ImmutableStructure::new, this.asUnmodifiableMap(), overridingContext.asUnmodifiableMap())); + EvaluationContext.mergeMaps(ImmutableStructure::new, this.asUnmodifiableMap(), + overridingContext.asUnmodifiableMap())); } @SuppressWarnings("all") private static class DelegateExclusions { @ExcludeFromGeneratedCoverageReport - public Map merge(Function, Structure> newStructure, + public Map merge(Function, Structure> newStructure, Map base, Map overriding) { - return null; } } diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java index 1736584a8..742c70833 100644 --- a/src/main/java/dev/openfeature/sdk/MutableContext.java +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -33,7 +33,7 @@ public MutableContext(String targetingKey) { } public MutableContext(Map attributes) { - this("", new HashMap<>(attributes)); + this(null, new HashMap<>(attributes)); } /** @@ -121,7 +121,7 @@ public EvaluationContext merge(EvaluationContext overridingContext) { return overridingContext; } - Map merged = this.merge( + Map merged = EvaluationContext.mergeMaps( MutableStructure::new, this.asUnmodifiableMap(), overridingContext.asUnmodifiableMap()); return new MutableContext(merged); } diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java index b8906c4a4..20ed67bb8 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java @@ -1,15 +1,24 @@ package dev.openfeature.sdk; -import dev.openfeature.sdk.exceptions.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.function.Consumer; + +import dev.openfeature.sdk.exceptions.ExceptionUtils; +import dev.openfeature.sdk.exceptions.FatalError; +import dev.openfeature.sdk.exceptions.GeneralError; +import dev.openfeature.sdk.exceptions.OpenFeatureError; +import dev.openfeature.sdk.exceptions.ProviderNotReadyError; import dev.openfeature.sdk.internal.AutoCloseableLock; import dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock; import dev.openfeature.sdk.internal.ObjectUtils; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import java.util.*; -import java.util.function.Consumer; - /** * OpenFeature Client implementation. * You should not instantiate this or reference this class. @@ -19,8 +28,8 @@ * @deprecated // TODO: eventually we will make this non-public. See issue #872 */ @Slf4j -@SuppressWarnings({"PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable", - "unchecked", "rawtypes"}) +@SuppressWarnings({ "PMD.DataflowAnomalyAnalysis", "PMD.BeanMembersShouldSerialize", "PMD.UnusedLocalVariable", + "unchecked", "rawtypes" }) @Deprecated() // TODO: eventually we will make this non-public. See issue #872 public class OpenFeatureClient implements Client { @@ -39,18 +48,18 @@ public class OpenFeatureClient implements Client { * Deprecated public constructor. Use OpenFeature.API.getClient() instead. * * @param openFeatureAPI Backing global singleton - * @param domain An identifier which logically binds clients with providers (used by observability tools). + * @param domain An identifier which logically binds clients with + * providers (used by observability tools). * @param version Version of the client (used by observability tools). * @deprecated Do not use this constructor. It's for internal use only. - * Clients created using it will not run event handlers. - * Use the OpenFeatureAPI's getClient factory method instead. + * Clients created using it will not run event handlers. + * Use the OpenFeatureAPI's getClient factory method instead. */ @Deprecated() // TODO: eventually we will make this non-public. See issue #872 public OpenFeatureClient( OpenFeatureAPI openFeatureAPI, String domain, - String version - ) { + String version) { this.openfeatureApi = openFeatureAPI; this.domain = domain; this.version = version; @@ -106,7 +115,7 @@ public EvaluationContext getEvaluationContext() { } private FlagEvaluationDetails evaluateFlag(FlagValueType type, String key, T defaultValue, - EvaluationContext ctx, FlagEvaluationOptions options) { + EvaluationContext ctx, FlagEvaluationOptions options) { FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options, () -> FlagEvaluationOptions.builder().build()); Map hints = Collections.unmodifiableMap(flagOptions.getHookHints()); @@ -182,29 +191,23 @@ private static void enrichDetailsWithErrorDefaults(T defaultValue, FlagEvalu * @return merged evaluation context */ private EvaluationContext mergeEvaluationContext(EvaluationContext invocationContext) { - // avoid any unnecessary context instantiations and stream usage here; this is call with every evaluation. final EvaluationContext apiContext = openfeatureApi.getEvaluationContext(); final EvaluationContext clientContext = this.getEvaluationContext(); final EvaluationContext transactionContext = openfeatureApi.getTransactionContext(); - final List contextsToMerge = new ArrayList<>(); - if (apiContext != null) { - contextsToMerge.add(apiContext); - } - if (transactionContext != null) { - contextsToMerge.add(transactionContext); - } - if (clientContext != null) { - contextsToMerge.add(clientContext); - } - if (invocationContext != null) { - contextsToMerge.add(invocationContext); - } + return mergeContextMaps(apiContext, transactionContext, clientContext, invocationContext); + } - EvaluationContext merged = new ImmutableContext(); - for (EvaluationContext evaluationContext : contextsToMerge) { - merged = merged.merge(evaluationContext); + private EvaluationContext mergeContextMaps(EvaluationContext... contexts) { + // avoid any unnecessary context instantiations and stream usage here; this is + // called with every evaluation. + Map merged = new HashMap<>(); + for (EvaluationContext evaluationContext : contexts) { + if (evaluationContext != null && !evaluationContext.isEmpty()) { + merged = EvaluationContext.mergeMaps(ImmutableStructure::new, merged, + evaluationContext.asUnmodifiableMap()); + } } - return merged; + return new ImmutableContext(merged); } private ProviderEvaluation createProviderEvaluation( @@ -241,7 +244,7 @@ public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationConte @Override public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getBooleanDetails(key, defaultValue, ctx, options).getValue(); } @@ -257,7 +260,7 @@ public FlagEvaluationDetails getBooleanDetails(String key, Boolean defa @Override public FlagEvaluationDetails getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options); } @@ -273,7 +276,7 @@ public String getStringValue(String key, String defaultValue, EvaluationContext @Override public String getStringValue(String key, String defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getStringDetails(key, defaultValue, ctx, options).getValue(); } @@ -289,7 +292,7 @@ public FlagEvaluationDetails getStringDetails(String key, String default @Override public FlagEvaluationDetails getStringDetails(String key, String defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options); } @@ -305,7 +308,7 @@ public Integer getIntegerValue(String key, Integer defaultValue, EvaluationConte @Override public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getIntegerDetails(key, defaultValue, ctx, options).getValue(); } @@ -321,7 +324,7 @@ public FlagEvaluationDetails getIntegerDetails(String key, Integer defa @Override public FlagEvaluationDetails getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options); } @@ -337,7 +340,7 @@ public Double getDoubleValue(String key, Double defaultValue, EvaluationContext @Override public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue(); } @@ -353,7 +356,7 @@ public FlagEvaluationDetails getDoubleDetails(String key, Double default @Override public FlagEvaluationDetails getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options); } @@ -369,7 +372,7 @@ public Value getObjectValue(String key, Value defaultValue, EvaluationContext ct @Override public Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return getObjectDetails(key, defaultValue, ctx, options).getValue(); } @@ -380,13 +383,13 @@ public FlagEvaluationDetails getObjectDetails(String key, Value defaultVa @Override public FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, - EvaluationContext ctx) { + EvaluationContext ctx) { return getObjectDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build()); } @Override public FlagEvaluationDetails getObjectDetails(String key, Value defaultValue, EvaluationContext ctx, - FlagEvaluationOptions options) { + FlagEvaluationOptions options) { return this.evaluateFlag(FlagValueType.OBJECT, key, defaultValue, ctx, options); } diff --git a/src/main/java/dev/openfeature/sdk/Structure.java b/src/main/java/dev/openfeature/sdk/Structure.java index dfc3c91fb..f2fdc53e7 100644 --- a/src/main/java/dev/openfeature/sdk/Structure.java +++ b/src/main/java/dev/openfeature/sdk/Structure.java @@ -5,8 +5,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.Map.Entry; -import java.util.function.Function; import java.util.stream.Collectors; import static dev.openfeature.sdk.Value.objectToValue; @@ -115,42 +113,6 @@ default Object convertValue(Value value) { throw new ValueNotConvertableError(); } - /** - * Recursively merges the base map with the overriding map. - * - * @param Structure type - * @param newStructure function to create the right structure - * @param base base map to merge - * @param overriding overriding map to merge - * @return resulting merged map - */ - 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(); - if (overridingEntry.getValue().isStructure() && merged.containsKey(key) && merged.get(key).isStructure()) { - Structure mergedValue = merged.get(key).asStructure(); - Structure overridingValue = overridingEntry.getValue().asStructure(); - Map newMap = this.merge(newStructure, mergedValue.asUnmodifiableMap(), - overridingValue.asUnmodifiableMap()); - merged.put(key, new Value(newStructure.apply(newMap))); - } else { - merged.put(key, overridingEntry.getValue()); - } - } - return merged; - } - /** * Transform an object map to a {@link Structure} type. * From bddf7f2d4e33ff0f31a6017a48017492eb771b83 Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Wed, 23 Oct 2024 12:37:49 -0400 Subject: [PATCH 4/5] fixup: make mergemap mutate Signed-off-by: Todd Baert --- benchmark.txt | 96 +++++++++---------- .../openfeature/sdk/EvaluationContext.java | 29 +++--- .../dev/openfeature/sdk/ImmutableContext.java | 7 +- .../dev/openfeature/sdk/MutableContext.java | 7 +- .../openfeature/sdk/OpenFeatureClient.java | 2 +- 5 files changed, 71 insertions(+), 70 deletions(-) diff --git a/benchmark.txt b/benchmark.txt index 289acd147..e43e684d0 100644 --- a/benchmark.txt +++ b/benchmark.txt @@ -36,9 +36,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. @@ -129,41 +129,41 @@ 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: 559671 26864208 java.util.HashMap (java.base@21.0.4) - 2: 352017 11264544 java.util.HashMap$Node (java.base@21.0.4) - 3: 210497 11170088 [Ljava.util.HashMap$Node; (java.base@21.0.4) - 4: 47815 9732672 [B (java.base@21.0.4) - 5: 279526 8944832 java.util.HashMap$EntryIterator (java.base@21.0.4) - 6: 305991 8105872 [Ljava.lang.Object; (java.base@21.0.4) - 7: 445455 7127280 java.util.Optional (java.base@21.0.4) - 8: 199209 6374688 java.util.Collections$UnmodifiableMap (java.base@21.0.4) - 9: 154 4368416 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) - 10: 100000 4000000 dev.openfeature.sdk.HookContext - 11: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder - 12: 230006 3680096 dev.openfeature.sdk.Value - 13: 210062 3360992 java.util.HashMap$EntrySet (java.base@21.0.4) - 14: 139788 3354912 java.util.ArrayList (java.base@21.0.4) - 15: 199210 3187360 dev.openfeature.sdk.ImmutableStructure - 16: 130219 3125256 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1 (java.base@21.0.4) - 17: 189210 3027360 dev.openfeature.sdk.ImmutableContext - 18: 177267 2836272 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry (java.base@21.0.4) - 19: 82425 2637600 java.util.ArrayList$Itr (java.base@21.0.4) - 20: 149207 2387312 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet (java.base@21.0.4) - 21: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails - 22: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation - 23: 34652 1663296 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder - 24: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x00007bf8a002fa78 - 25: 50000 1600000 [Ldev.openfeature.sdk.EvaluationContext; - 26: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) - 27: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata - 28: 100000 1600000 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder - 29: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x00007bf8a0082800 - 30: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions - 31: 44836 1076064 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder - 32: 26278 1051120 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder - 33: 40785 978840 dev.openfeature.sdk.HookSupport$$Lambda/0x00007bf8a0081da8 - 34: 40423 970152 dev.openfeature.sdk.HookSupport$$Lambda/0x00007bf8a0081b78 - 35: 54212 867392 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x00007bf8a002eae8 + 1: 480234 23051232 java.util.HashMap (java.base@21.0.4) + 2: 150497 12050088 [Ljava.util.HashMap$Node; (java.base@21.0.4) + 3: 332017 10624544 java.util.HashMap$Node (java.base@21.0.4) + 4: 47815 9732480 [B (java.base@21.0.4) + 5: 305991 8105872 [Ljava.lang.Object; (java.base@21.0.4) + 6: 366682 5866912 java.util.Optional (java.base@21.0.4) + 7: 183332 5866624 java.util.HashMap$EntryIterator (java.base@21.0.4) + 8: 172970 5535040 java.util.Collections$UnmodifiableMap (java.base@21.0.4) + 9: 100000 4000000 dev.openfeature.sdk.HookContext + 10: 100000 4000000 dev.openfeature.sdk.HookContext$HookContextBuilder + 11: 230006 3680096 dev.openfeature.sdk.Value + 12: 200062 3200992 java.util.HashMap$EntrySet (java.base@21.0.4) + 13: 132870 3188880 java.util.ArrayList (java.base@21.0.4) + 14: 192292 3076672 dev.openfeature.sdk.ImmutableStructure + 15: 182292 2916672 dev.openfeature.sdk.ImmutableContext + 16: 50000 2000000 dev.openfeature.sdk.FlagEvaluationDetails + 17: 50000 2000000 dev.openfeature.sdk.ProviderEvaluation + 18: 122968 1967488 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet (java.base@21.0.4) + 19: 149 1884376 [Ljdk.internal.vm.FillerElement; (java.base@21.0.4) + 20: 56476 1807232 java.util.ArrayList$Itr (java.base@21.0.4) + 21: 37481 1799088 dev.openfeature.sdk.FlagEvaluationDetails$FlagEvaluationDetailsBuilder + 22: 100001 1600016 dev.openfeature.sdk.NoOpProvider$$Lambda/0x000076e79c02fa78 + 23: 50000 1600000 [Ldev.openfeature.sdk.EvaluationContext; + 24: 50000 1600000 [Ljava.util.List; (java.base@21.0.4) + 25: 100000 1600000 dev.openfeature.sdk.OpenFeatureClient$$Lambda/0x000076e79c082800 + 26: 36720 1468800 dev.openfeature.sdk.ProviderEvaluation$ProviderEvaluationBuilder + 27: 87481 1399696 dev.openfeature.sdk.ImmutableMetadata + 28: 50000 1200000 dev.openfeature.sdk.FlagEvaluationOptions + 29: 74201 1187216 dev.openfeature.sdk.ImmutableMetadata$ImmutableMetadataBuilder + 30: 73235 1171760 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$UnmodifiableEntry (java.base@21.0.4) + 31: 45869 1100856 java.util.Collections$UnmodifiableMap$UnmodifiableEntrySet$1 (java.base@21.0.4) + 32: 43776 1050624 dev.openfeature.sdk.FlagEvaluationOptions$FlagEvaluationOptionsBuilder + 33: 40016 960384 dev.openfeature.sdk.HookSupport$$Lambda/0x000076e79c081b78 + 34: 39967 959208 dev.openfeature.sdk.HookSupport$$Lambda/0x000076e79c081da8 + 35: 57783 924528 dev.openfeature.sdk.internal.AutoCloseableReentrantReadWriteLock$$Lambda/0x000076e79c02eae8 36: 4490 721440 [I (java.base@21.0.4) 37: 26594 638256 java.lang.String (java.base@21.0.4) 38: 1461 390008 [J (java.base@21.0.4) @@ -248,20 +248,20 @@ Iteration 1: num #instances #bytes class name (module) 117: 65 5200 java.net.URI (java.base@21.0.4) 118: 215 5104 [Ljdk.internal.org.objectweb.asm.Type; (java.base@21.0.4) truncated... -Total 5107873 156138304 +Total 4452140 139359040 -0.187 s/op - +totalAllocatedBytes: 156138304.000 bytes - +totalAllocatedInstances: 5107873.000 instances +0.186 s/op + +totalAllocatedBytes: 139359040.000 bytes + +totalAllocatedInstances: 4452140.000 instances +totalHeap: 521412608.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedBytes": - 156138304.000 bytes + 139359040.000 bytes Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances": - 5107873.000 instances + 4452140.000 instances Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalHeap": 521412608.000 bytes @@ -282,13 +282,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.187 s/op -AllocationBenchmark.run:+totalAllocatedBytes ss 156138304.000 bytes -AllocationBenchmark.run:+totalAllocatedInstances ss 5107873.000 instances +AllocationBenchmark.run ss 0.186 s/op +AllocationBenchmark.run:+totalAllocatedBytes ss 139359040.000 bytes +AllocationBenchmark.run:+totalAllocatedInstances ss 4452140.000 instances AllocationBenchmark.run:+totalHeap ss 521412608.000 bytes [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ -[INFO] Total time: 8.226 s -[INFO] Finished at: 2024-10-23T12:36:22-04:00 +[INFO] Total time: 8.280 s +[INFO] Finished at: 2024-10-23T12:37:24-04:00 [INFO] ------------------------------------------------------------------------ diff --git a/src/main/java/dev/openfeature/sdk/EvaluationContext.java b/src/main/java/dev/openfeature/sdk/EvaluationContext.java index 142c78c32..b8cc684db 100644 --- a/src/main/java/dev/openfeature/sdk/EvaluationContext.java +++ b/src/main/java/dev/openfeature/sdk/EvaluationContext.java @@ -1,6 +1,5 @@ package dev.openfeature.sdk; -import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.function.Function; @@ -26,38 +25,38 @@ public interface EvaluationContext extends Structure { EvaluationContext merge(EvaluationContext overridingContext); /** - * Recursively merges the base Value map with the overriding map. + * Recursively merges the overriding map into the base Value map. + * The base map is mutated, the overriding map is not. + * Null maps will cause no-op. * - * @param Structure type * @param newStructure function to create the right structure(s) for Values * @param base base map to merge * @param overriding overriding map to merge - * @return resulting merged map */ - static Map mergeMaps(Function, Structure> newStructure, + static void mergeMaps(Function, Structure> newStructure, Map base, Map overriding) { - if (base == null || base.isEmpty()) { - return overriding; + if (base == null) { + return; } if (overriding == null || overriding.isEmpty()) { - return base; + return; } - final Map merged = new HashMap<>(base); for (Entry overridingEntry : overriding.entrySet()) { String key = overridingEntry.getKey(); - if (overridingEntry.getValue().isStructure() && merged.containsKey(key) && merged.get(key).isStructure()) { - Structure mergedValue = merged.get(key).asStructure(); + if (overridingEntry.getValue().isStructure() && base.containsKey(key) && base.get(key).isStructure()) { + Structure mergedValue = base.get(key).asStructure(); Structure overridingValue = overridingEntry.getValue().asStructure(); - Map newMap = mergeMaps(newStructure, mergedValue.asUnmodifiableMap(), + Map newMap = mergedValue.asMap(); + mergeMaps(newStructure, newMap, overridingValue.asUnmodifiableMap()); - merged.put(key, new Value(newStructure.apply(newMap))); + base.put(key, new Value(newStructure.apply(newMap))); } else { - merged.put(key, overridingEntry.getValue()); + base.put(key, overridingEntry.getValue()); } } - return merged; + return; } } diff --git a/src/main/java/dev/openfeature/sdk/ImmutableContext.java b/src/main/java/dev/openfeature/sdk/ImmutableContext.java index 53d49a78f..d0dae6051 100644 --- a/src/main/java/dev/openfeature/sdk/ImmutableContext.java +++ b/src/main/java/dev/openfeature/sdk/ImmutableContext.java @@ -87,9 +87,10 @@ public EvaluationContext merge(EvaluationContext overridingContext) { return new ImmutableContext(overridingContext.asUnmodifiableMap()); } - return new ImmutableContext( - EvaluationContext.mergeMaps(ImmutableStructure::new, this.asUnmodifiableMap(), - overridingContext.asUnmodifiableMap())); + Map attributes = this.asMap(); + EvaluationContext.mergeMaps(ImmutableStructure::new, attributes, + overridingContext.asUnmodifiableMap()); + return new ImmutableContext(attributes); } @SuppressWarnings("all") diff --git a/src/main/java/dev/openfeature/sdk/MutableContext.java b/src/main/java/dev/openfeature/sdk/MutableContext.java index 742c70833..ffab28af2 100644 --- a/src/main/java/dev/openfeature/sdk/MutableContext.java +++ b/src/main/java/dev/openfeature/sdk/MutableContext.java @@ -121,9 +121,10 @@ public EvaluationContext merge(EvaluationContext overridingContext) { return overridingContext; } - Map merged = EvaluationContext.mergeMaps( - MutableStructure::new, this.asUnmodifiableMap(), overridingContext.asUnmodifiableMap()); - return new MutableContext(merged); + Map attributes = this.asMap(); + EvaluationContext.mergeMaps( + MutableStructure::new, attributes, overridingContext.asUnmodifiableMap()); + return new MutableContext(attributes); } /** diff --git a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java index 20ed67bb8..f56df15a0 100644 --- a/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java +++ b/src/main/java/dev/openfeature/sdk/OpenFeatureClient.java @@ -203,7 +203,7 @@ private EvaluationContext mergeContextMaps(EvaluationContext... contexts) { Map merged = new HashMap<>(); for (EvaluationContext evaluationContext : contexts) { if (evaluationContext != null && !evaluationContext.isEmpty()) { - merged = EvaluationContext.mergeMaps(ImmutableStructure::new, merged, + EvaluationContext.mergeMaps(ImmutableStructure::new, merged, evaluationContext.asUnmodifiableMap()); } } From e9c251cb0442db9d6139ad996c713e8457c9febc Mon Sep 17 00:00:00 2001 From: Todd Baert Date: Wed, 23 Oct 2024 12:48:29 -0400 Subject: [PATCH 5/5] fixup: pmd Signed-off-by: Todd Baert --- src/main/java/dev/openfeature/sdk/EvaluationContext.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/dev/openfeature/sdk/EvaluationContext.java b/src/main/java/dev/openfeature/sdk/EvaluationContext.java index b8cc684db..5b2a33113 100644 --- a/src/main/java/dev/openfeature/sdk/EvaluationContext.java +++ b/src/main/java/dev/openfeature/sdk/EvaluationContext.java @@ -57,6 +57,5 @@ static void mergeMaps(Function, Structure> newStructure, base.put(key, overridingEntry.getValue()); } } - return; } }