Skip to content
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

JsonObject optimization #4994

Closed
wants to merge 10 commits into from

Conversation

magicprinc
Copy link
Contributor

@magicprinc magicprinc commented Dec 1, 2023

  1. use JDK Map.entry(key,value) instead of self-made Entry class

One caveat: Map.entry disallows null keys and values (Attempts to create Map.Entry using a null key or value result in NullPointerException), but it is used in JsonObject iterator only and the iterator doesn't wrap null values in new Map.Entry

  1. (Linked)HashMap has DEFAULT_LOAD_FACTOR = 0.75f: if you create it with initialCapacity=X and put X items into it, then HashMap resizes (with new internal array allocation and re-hash). One has to create HashMap with initialCapacity=X/0.75
    e.g. for 10 keys initialCapacity must be at least 14

You can see it yourself under the debugger

  1. better inheritance: equals
    if (getClass() != o.getClass()) return false rejects subclasses
    if (!(o instanceof JsonObject)) return false allows subclasses and checks for null

1) use JDK Map.entry(key,value) instead of self-made Entry class
One caveat: Map.entry disallows null keys and values (Attempts to create Map.Entry using a null key or value result in NullPointerException)
2) (Linked)HashMap has DEFAULT_LOAD_FACTOR = 0.75f: if you create it with initialCapacity=X and put X items into it then HashMap resizes (with new internal array allocation and re-hash). One has to create HashMap with initialCapacity=X/0.75 e.g. for 10 keys initialCapacity must be at least 14
3) better inheritance: equals `if (getClass() != o.getClass()) return false;` rejects subclasses. if (!(o instanceof JsonObject)) return false allows subclasses and checks for null
@magicprinc
Copy link
Contributor Author

No problems with null
Test passes

@Test 
public void _null (){
  JsonObject jo = JsonObject.of("x", null, "", null);
  jo.stream().forEach(kv->{
    assertNull(kv.getValue());
  });
}

Copy link
Contributor

@tsegismont tsegismont left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK with a few comments

@@ -122,7 +122,7 @@ public static JsonObject of(String k1, Object v1) {
* @return a JsonObject containing the specified mappings.
*/
public static JsonObject of(String k1, Object v1, String k2, Object v2) {
JsonObject obj = new JsonObject(new LinkedHashMap<>(2));
JsonObject obj = new JsonObject(new LinkedHashMap<>(4));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JsonObject obj = new JsonObject(new LinkedHashMap<>(4));
JsonObject obj = new JsonObject(new LinkedHashMap<>(3));

According to https://guava.dev/releases/21.0/api/docs/src-html/com/google/common/collect/Maps.html#line.291

@@ -105,7 +105,7 @@ public static JsonObject of() {
* @return a JsonObject containing the specified mapping.
*/
public static JsonObject of(String k1, Object v1) {
JsonObject obj = new JsonObject(new LinkedHashMap<>(1));
JsonObject obj = new JsonObject(new LinkedHashMap<>(3));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JsonObject obj = new JsonObject(new LinkedHashMap<>(3));
JsonObject obj = new JsonObject(new LinkedHashMap<>(2));

According to https://guava.dev/releases/21.0/api/docs/src-html/com/google/common/collect/Maps.html#line.291

@@ -166,7 +166,7 @@ public static JsonObject of(String k1, Object v1, String k2, Object v2, String k
*/
public static JsonObject of(String k1, Object v1, String k2, Object v2, String k3, Object v3,
String k4, Object v4) {
JsonObject obj = new JsonObject(new LinkedHashMap<>(4));
JsonObject obj = new JsonObject(new LinkedHashMap<>(7));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JsonObject obj = new JsonObject(new LinkedHashMap<>(7));
JsonObject obj = new JsonObject(new LinkedHashMap<>(6));

https://guava.dev/releases/21.0/api/docs/src-html/com/google/common/collect/Maps.html#line.291

@@ -193,7 +193,7 @@ public static JsonObject of(String k1, Object v1, String k2, Object v2, String k
*/
public static JsonObject of(String k1, Object v1, String k2, Object v2, String k3, Object v3,
String k4, Object v4, String k5, Object v5) {
JsonObject obj = new JsonObject(new LinkedHashMap<>(5));
JsonObject obj = new JsonObject(new LinkedHashMap<>(8));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JsonObject obj = new JsonObject(new LinkedHashMap<>(8));
JsonObject obj = new JsonObject(new LinkedHashMap<>(7));

https://guava.dev/releases/21.0/api/docs/src-html/com/google/common/collect/Maps.html#line.291

@@ -294,7 +294,7 @@ public static JsonObject of(String k1, Object v1, String k2, Object v2, String k
public static JsonObject of(String k1, Object v1, String k2, Object v2, String k3, Object v3,
String k4, Object v4, String k5, Object v5, String k6, Object v6,
String k7, Object v7, String k8, Object v8) {
JsonObject obj = new JsonObject(new LinkedHashMap<>(8));
JsonObject obj = new JsonObject(new LinkedHashMap<>(12));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JsonObject obj = new JsonObject(new LinkedHashMap<>(12));
JsonObject obj = new JsonObject(new LinkedHashMap<>(11));

https://guava.dev/releases/21.0/api/docs/src-html/com/google/common/collect/Maps.html#line.291

@@ -334,7 +334,7 @@ public static JsonObject of(String k1, Object v1, String k2, Object v2, String k
public static JsonObject of(String k1, Object v1, String k2, Object v2, String k3, Object v3,
String k4, Object v4, String k5, Object v5, String k6, Object v6,
String k7, Object v7, String k8, Object v8, String k9, Object v9) {
JsonObject obj = new JsonObject(new LinkedHashMap<>(9));
JsonObject obj = new JsonObject(new LinkedHashMap<>(14));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JsonObject obj = new JsonObject(new LinkedHashMap<>(14));
JsonObject obj = new JsonObject(new LinkedHashMap<>(13));

https://guava.dev/releases/21.0/api/docs/src-html/com/google/common/collect/Maps.html#line.291

@@ -378,7 +378,7 @@ public static JsonObject of(String k1, Object v1, String k2, Object v2, String k
String k4, Object v4, String k5, Object v5, String k6, Object v6,
String k7, Object v7, String k8, Object v8, String k9, Object v9,
String k10, Object v10) {
JsonObject obj = new JsonObject(new LinkedHashMap<>(10));
JsonObject obj = new JsonObject(new LinkedHashMap<>(16));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
JsonObject obj = new JsonObject(new LinkedHashMap<>(16));
JsonObject obj = new JsonObject(new LinkedHashMap<>(14));

https://guava.dev/releases/21.0/api/docs/src-html/com/google/common/collect/Maps.html#line.291

@@ -1160,14 +1160,11 @@ public String toString() {

@Override
public boolean equals(Object o) {
// null check
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need to remove this, unless it is proved it slows down things

// self check
if (this == o)
return true;
// type check and cast
if (getClass() != o.getClass())
// type check, cast and null check
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to change this, we don't plan to support subclasses

@magicprinc
Copy link
Contributor Author

magicprinc commented Dec 2, 2023

@tsegismont I have indeed made initial capacity bigger than required, but only to prevent resize for the next 1-3 user’s puts. Do you have any statistics how JsonObject is used? I am not sure what is better 🤷‍♀️

Following question: does JsonObject really needs LinkedHashMap? LinkedHashMap uses more memory than HashMap. The only place where you see the difference is interator().
And by the way: JsonObjects brokes this contract already in copy()

About equals: why not make it final, then?
I.e., make final, remove null check, use instanceof ?

1) skip implicit null check
2) MAP_CREATOR
1) use MAP_CREATOR
2) LinkedHashMap → HashMap (can be changed in MAP_CREATOR)
3) better initial capacity
@magicprinc
Copy link
Contributor Author

@tsegismont Please review again: I have found a very interesting solution.

better spliterator() and stream() implementations
Use Spliterator flags and map.size() to make them more performant
@@ -1158,16 +1182,21 @@ public String toString() {
return encode();
}

/**
How to Write an Equality Method in Java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you remove this reference to this article ? it's not needed

How to Write an Equality Method in Java
https://www.artima.com/articles/how-to-write-an-equality-method-in-java
*/
protected boolean canEqual(Object other) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you actually remove this ? we don't actually add such contract for json object.

json object is not meant to be subclasses (although sometimes it is) and we don't encourage it.

// todo replace with HashMap? → Less memory usage, but random keys order, but .stream().sorted()?
// immutable Map.of javadoc: The iteration order of mappings is unspecified and is subject to change
@SuppressWarnings("rawtypes")
public static final IntFunction<Map> MAP_CREATOR = size -> new LinkedHashMap(size < 3
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please inline that directly in map(int), we don't need to have one static method that delegates to one static field

@magicprinc
Copy link
Contributor Author

@tsegismont @vietj I will redo this PR as a single commit with all fixes.

@tsegismont
Copy link
Contributor

@magicprinc please keep only the following changes in your PR:

  • capacity of the map in JsonObject.of methods
  • remove redundant class

We don't need a method for dynamic computation of map capacity. For JsonObject.of methods, we can use a static value which is enough to store the parameters (these methods are generally used to create Json objects which won't be modified).

@magicprinc
Copy link
Contributor Author

I did my best #5001

@magicprinc magicprinc closed this Dec 4, 2023
@vietj
Copy link
Member

vietj commented Dec 4, 2023

thanks, you didn't need to close the PR for a new one, you could have force pushed your branch instead with the rebased version, that would have updated the PR in place

that is preferable because with a new PR with lose the context of the review in progress.

@magicprinc
Copy link
Contributor Author

@vietj I didn't know such thing exists. Thank You!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants