perf: reduce hashmap allocations (#1178)

* chore: reduce hashmap allocations

Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
This commit is contained in:
Todd Baert 2024-10-24 08:41:55 -04:00 committed by GitHub
parent 7a1eb9b9e9
commit fd7659a46f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 276 additions and 225 deletions

View File

@ -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

View File

@ -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,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: 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)
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: 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 7264791 244216640
Total 4452140 139359040
0.166 s/op
+totalAllocatedBytes: 244216640.000 bytes
+totalAllocatedInstances: 7264791.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":
244216640.000 bytes
139359040.000 bytes
Secondary result "dev.openfeature.sdk.benchmark.AllocationBenchmark.run:+totalAllocatedInstances":
7264791.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.166 s/op
AllocationBenchmark.run:+totalAllocatedBytes ss 244216640.000 bytes
AllocationBenchmark.run:+totalAllocatedInstances ss 7264791.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.760 s
[INFO] Finished at: 2024-10-23T12:22:21-04:00
[INFO] Total time: 8.280 s
[INFO] Finished at: 2024-10-23T12:37:24-04:00
[INFO] ------------------------------------------------------------------------

View File

@ -2,6 +2,7 @@ package dev.openfeature.sdk;
import java.util.HashMap;
import java.util.Map;
import java.util.Collections;
@SuppressWarnings({ "PMD.BeanMembersShouldSerialize", "checkstyle:MissingJavadocType" })
abstract class AbstractStructure implements Structure {
@ -18,7 +19,15 @@ abstract class AbstractStructure implements Structure {
}
AbstractStructure(Map<String, Value> attributes) {
this.attributes = new HashMap<>(attributes);
this.attributes = attributes;
}
/**
* Returns an unmodifiable representation of the internal attribute map.
* @return immutable map
*/
public Map<String, Value> asUnmodifiableMap() {
return Collections.unmodifiableMap(attributes);
}
/**

View File

@ -1,5 +1,9 @@
package dev.openfeature.sdk;
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 +23,39 @@ public interface EvaluationContext extends Structure {
* @return resulting merged context
*/
EvaluationContext merge(EvaluationContext overridingContext);
/**
* 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 newStructure function to create the right structure(s) for Values
* @param base base map to merge
* @param overriding overriding map to merge
*/
static void mergeMaps(Function<Map<String, Value>, Structure> newStructure,
Map<String, Value> base,
Map<String, Value> overriding) {
if (base == null) {
return;
}
if (overriding == null || overriding.isEmpty()) {
return;
}
for (Entry<String, Value> overridingEntry : overriding.entrySet()) {
String key = overridingEntry.getKey();
if (overridingEntry.getValue().isStructure() && base.containsKey(key) && base.get(key).isStructure()) {
Structure mergedValue = base.get(key).asStructure();
Structure overridingValue = overridingEntry.getValue().asStructure();
Map<String, Value> newMap = mergedValue.asMap();
mergeMaps(newStructure, newMap,
overridingValue.asUnmodifiableMap());
base.put(key, new Value(newStructure.apply(newMap)));
} else {
base.put(key, overridingEntry.getValue());
}
}
}
}

View File

@ -3,6 +3,7 @@ package dev.openfeature.sdk;
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;
@ -10,7 +11,8 @@ import lombok.experimental.Delegate;
/**
* 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
@ -21,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<>());
@ -42,7 +45,7 @@ public final class ImmutableContext implements EvaluationContext {
* @param attributes evaluation context attributes
*/
public ImmutableContext(Map<String, Value> attributes) {
this("", attributes);
this(null, attributes);
}
/**
@ -53,9 +56,7 @@ public final class ImmutableContext implements EvaluationContext {
*/
public ImmutableContext(String targetingKey, Map<String, Value> attributes) {
if (targetingKey != null && !targetingKey.trim().isEmpty()) {
final Map<String, Value> 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);
}
@ -71,7 +72,8 @@ public final class ImmutableContext implements EvaluationContext {
}
/**
* 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
@ -79,23 +81,24 @@ public final class ImmutableContext implements EvaluationContext {
@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()));
Map<String, Value> attributes = this.asMap();
EvaluationContext.mergeMaps(ImmutableStructure::new, attributes,
overridingContext.asUnmodifiableMap());
return new ImmutableContext(attributes);
}
@SuppressWarnings("all")
private static class DelegateExclusions {
@ExcludeFromGeneratedCoverageReport
public <T extends Structure> Map<String, Value> merge(Function<Map<String, Value>, Structure> newStructure,
public <T extends Structure> Map<String, Value> merge(Function<Map<String, Value>, Structure> newStructure,
Map<String, Value> base,
Map<String, Value> overriding) {
return null;
}
}

View File

@ -36,7 +36,11 @@ public final class ImmutableStructure extends AbstractStructure {
* @param attributes attributes.
*/
public ImmutableStructure(Map<String, Value> attributes) {
super(copyAttributes(attributes));
super(copyAttributes(attributes, null));
}
protected ImmutableStructure(String targetingKey, Map<String, Value> attributes) {
super(copyAttributes(attributes, targetingKey));
}
@Override
@ -62,11 +66,18 @@ public final class ImmutableStructure extends AbstractStructure {
}
private static Map<String, Value> copyAttributes(Map<String, Value> in) {
return copyAttributes(in, null);
}
private static Map<String, Value> copyAttributes(Map<String, Value> in, String targetingKey) {
Map<String, Value> copy = new HashMap<>();
for (Entry<String, Value> 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;
}

View File

@ -33,7 +33,7 @@ public class MutableContext implements EvaluationContext {
}
public MutableContext(Map<String, Value> attributes) {
this("", attributes);
this(null, new HashMap<>(attributes));
}
/**
@ -44,7 +44,7 @@ public class MutableContext implements EvaluationContext {
* @param attributes evaluation context attributes
*/
public MutableContext(String targetingKey, Map<String, Value> 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));
}
@ -121,9 +121,10 @@ public class MutableContext implements EvaluationContext {
return overridingContext;
}
Map<String, Value> merged = this.merge(
MutableStructure::new, this.asMap(), overridingContext.asMap());
return new MutableContext(merged);
Map<String, Value> attributes = this.asMap();
EvaluationContext.mergeMaps(
MutableStructure::new, attributes, overridingContext.asUnmodifiableMap());
return new MutableContext(attributes);
}
/**

View File

@ -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 @@ import java.util.function.Consumer;
* @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,11 +115,10 @@ public class OpenFeatureClient implements Client {
}
private <T> FlagEvaluationDetails<T> evaluateFlag(FlagValueType type, String key, T defaultValue,
EvaluationContext ctx, FlagEvaluationOptions options) {
EvaluationContext ctx, FlagEvaluationOptions options) {
FlagEvaluationOptions flagOptions = ObjectUtils.defaultIfNull(options,
() -> FlagEvaluationOptions.builder().build());
Map<String, Object> hints = Collections.unmodifiableMap(flagOptions.getHookHints());
ctx = ObjectUtils.defaultIfNull(ctx, () -> new ImmutableContext());
FlagEvaluationDetails<T> details = null;
List<Hook> mergedHooks = null;
@ -183,17 +191,23 @@ public class OpenFeatureClient implements Client {
* @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();
final EvaluationContext apiContext = openfeatureApi.getEvaluationContext();
final EvaluationContext clientContext = this.getEvaluationContext();
final EvaluationContext transactionContext = openfeatureApi.getTransactionContext();
return mergeContextMaps(apiContext, transactionContext, clientContext, invocationContext);
}
return apiContext.merge(transactionContext.merge(clientContext.merge(invocationContext)));
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()) {
EvaluationContext.mergeMaps(ImmutableStructure::new, merged,
evaluationContext.asUnmodifiableMap());
}
}
return new ImmutableContext(merged);
}
private <T> ProviderEvaluation<?> createProviderEvaluation(
@ -230,7 +244,7 @@ public class OpenFeatureClient implements Client {
@Override
public Boolean getBooleanValue(String key, Boolean defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getBooleanDetails(key, defaultValue, ctx, options).getValue();
}
@ -246,7 +260,7 @@ public class OpenFeatureClient implements Client {
@Override
public FlagEvaluationDetails<Boolean> getBooleanDetails(String key, Boolean defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.BOOLEAN, key, defaultValue, ctx, options);
}
@ -262,7 +276,7 @@ public class OpenFeatureClient implements Client {
@Override
public String getStringValue(String key, String defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getStringDetails(key, defaultValue, ctx, options).getValue();
}
@ -278,7 +292,7 @@ public class OpenFeatureClient implements Client {
@Override
public FlagEvaluationDetails<String> getStringDetails(String key, String defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.STRING, key, defaultValue, ctx, options);
}
@ -294,7 +308,7 @@ public class OpenFeatureClient implements Client {
@Override
public Integer getIntegerValue(String key, Integer defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getIntegerDetails(key, defaultValue, ctx, options).getValue();
}
@ -310,7 +324,7 @@ public class OpenFeatureClient implements Client {
@Override
public FlagEvaluationDetails<Integer> getIntegerDetails(String key, Integer defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.INTEGER, key, defaultValue, ctx, options);
}
@ -326,7 +340,7 @@ public class OpenFeatureClient implements Client {
@Override
public Double getDoubleValue(String key, Double defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options).getValue();
}
@ -342,7 +356,7 @@ public class OpenFeatureClient implements Client {
@Override
public FlagEvaluationDetails<Double> getDoubleDetails(String key, Double defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.DOUBLE, key, defaultValue, ctx, options);
}
@ -358,7 +372,7 @@ public class OpenFeatureClient implements Client {
@Override
public Value getObjectValue(String key, Value defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return getObjectDetails(key, defaultValue, ctx, options).getValue();
}
@ -369,13 +383,13 @@ public class OpenFeatureClient implements Client {
@Override
public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue,
EvaluationContext ctx) {
EvaluationContext ctx) {
return getObjectDetails(key, defaultValue, ctx, FlagEvaluationOptions.builder().build());
}
@Override
public FlagEvaluationDetails<Value> getObjectDetails(String key, Value defaultValue, EvaluationContext ctx,
FlagEvaluationOptions options) {
FlagEvaluationOptions options) {
return this.evaluateFlag(FlagValueType.OBJECT, key, defaultValue, ctx, options);
}

View File

@ -5,8 +5,6 @@ import dev.openfeature.sdk.exceptions.ValueNotConvertableError;
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;
@ -46,6 +44,14 @@ public interface Structure {
*/
Map<String, Value> asMap();
/**
* Get all values, as a map of Values.
*
* @return all attributes on the structure into a Map
*/
Map<String, Value> asUnmodifiableMap();
/**
* Get all values, with as a map of Object.
*
@ -95,7 +101,7 @@ public interface Structure {
if (value.isStructure()) {
Structure s = value.asStructure();
return s.asMap()
return s.asUnmodifiableMap()
.entrySet()
.stream()
.collect(HashMap::new,
@ -107,41 +113,6 @@ public interface Structure {
throw new ValueNotConvertableError();
}
/**
* Recursively merges the base map with the overriding map.
*
* @param <T> 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 <T extends Structure> Map<String, Value> merge(Function<Map<String, Value>, Structure> newStructure,
Map<String, Value> base,
Map<String, Value> overriding) {
if (base.isEmpty()) {
return overriding;
}
if (overriding.isEmpty()) {
return base;
}
final Map<String, Value> merged = new HashMap<>(base);
for (Entry<String, Value> 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<String, Value> newMap = this.merge(newStructure, mergedValue.asMap(), overridingValue.asMap());
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.
*

View File

@ -274,7 +274,7 @@ public class Value implements Cloneable {
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());