-
Notifications
You must be signed in to change notification settings - Fork 24.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Script: Metadata validateMetadata optimization #93333
Script: Metadata validateMetadata optimization #93333
Conversation
The safety guarantees are essentially the same, but in practice this prevents us from wrapping an Map.of(...) -generated map (i.e. an ImmutableMap) in another layer of unmodifiability (i.e. an UnmodifiableMap).
We only call it from within this package or in subclasses via calls to `super(...)`.
Hi @joegallo, I've created a changelog YAML for you. |
Pinging @elastic/es-core-infra (Team:Core/Infra) |
/** | ||
* Check that all metadata map contains only valid metadata and no extraneous keys | ||
*/ | ||
protected void validateMetadata() { | ||
int numMetadata = 0; | ||
for (Map.Entry<String, FieldProperty<?>> entry : properties.entrySet()) { | ||
String key = entry.getKey(); | ||
if (map.containsKey(key)) { | ||
Object value = map.getOrDefault(key, NOT_FOUND); | ||
if (value != NOT_FOUND) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you flip this condition? I know why it's written this way atm but not NOT_FOUND doesn't read nicely.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, yeah, it's way better as the reverse. Thanks! fdd1f27
numMetadata++; | ||
// check whether it's permissible to have the value for the property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment can be dropped, the other is sufficient. Consider adding a comment of the form "getOrDefault is faster than hasKey + get".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
++, fdd1f27
// we can't tell the compiler that properties must be a java.util.ImmutableCollections.AbstractImmutableMap, but | ||
// we can use this copyOf + assert to verify that at runtime. | ||
this.properties = Map.copyOf(properties); | ||
assert this.properties == properties : "properties map must be constructed via Map.of(...) or Map.copyOf(...)"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a test for this assert?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gah, yeah, I fall upon my sword. 0c97aa3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good change.
Map.copyOf
The first load-bearing bit here is the change from
this.properties = Collections.unmodifiableMap(properties);
tothis.properties = Map.copyOf(properties);
. Since all non-test callers pass in aproperties
map that was constructed via a call toMap.of
, it's already an "immutable map" andMap.copyOf
ends up being a no-op.The precise details of this are very annoying, but if you trace
Map.copyOf
andCollections.unmodifiableMap
through, both do have shortcutting behavior for multiple calls to themselves, but they are not mutually shortcutting -- so even though all our non-test properties maps are constructed as immutable via a call toMap.of
, they were still wrapped in another layer on unmodifiability via the call toCollections.unmodifiableMap
. On its own that's not so bad, it's just an allocation, but the wrapping also extends to another immutable wrapper being generated around each individualEntry
when you loop over one of these maps viaentrySet
, which we do on construction for all of these maps in the form ofvalidateMetadata()
.That is, prior to this PR, we were at O(N*M) allocations here -- N on the number of Metadata being constructed, and M on the number of entries in their associated properties. After this PR there are no additional allocations, just N no-oping method calls.
getOrDefault
The second important change is that rather than calling
.containsKey(key)
and then immediately calling.get(key)
after that, we can callgetOrDefault(key, NOT_FOUND)
just once and avoid an extra hashmap lookup. It's only a factor of 2 (so not actually speedup in terms of big O, right?), but it is still a small speedup in reality.Overall, this changes the picture from this flamegraph before:
To this flamegraph after:
I've normalized the
Matched: x.xx%
to be as compared to all of the time spent in the IngestService, so these changes together are shaving 0.5% off our overall ingest time. Not an enormous win, but the changes themselves are pretty tidy and self-contained, so I think this is a nice small speedup as low-hanging fruit.