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

Use a simpler representation for NodeMap with only one value #4551

Merged
merged 1 commit into from Aug 31, 2018

Conversation

Legioth
Copy link
Member

@Legioth Legioth commented Aug 23, 2018

A HashMap with only one item uses lots of memory since the hash table
has room for 16 items, and each actually used item in the array is a
linked list node. Collections.singletonMap gets rid of this overhead,
but still uses memory for caching keySet, entrySet and values collection
view instances.

Reduces BasicElementView memory use from 429706 to 378338 bytes and the
memory use in BeverageBuddy with an edit dialog open from 262257 to
230435 bytes.


This change is Reviewable

@Legioth Legioth force-pushed the singleMapMemory branch 2 times, most recently from 1dfe1e4 to 34467d2 Compare August 28, 2018 08:07
@Legioth Legioth changed the title Use singleton map for NodeMap with only one value Use a simpler representation for NodeMap with only one value Aug 28, 2018
}

@Override
public Serializable get(String key) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

MINOR Remove this method to simply inherit it. rule

@SomeoneToIgnore SomeoneToIgnore self-assigned this Aug 31, 2018
Copy link
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

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

Reviewed 1 of 1 files at r1.
Reviewable status: 5 unresolved discussions, 0 of 1 LGTMs obtained


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 67 at r1 (raw file):

        private final String key;

        private Serializable value;

Looks like we don't change the value after setting it, so we can have it as final also.


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 101 at r1 (raw file):

        @Override
        public Stream<Serializable> streamValues() {
            return Stream.of(value);

This is able to throw NPE when the value is null.
Should we assert it in the constructor? Alternatively, we can return an empty stream in this case.


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 112 at r1 (raw file):

Previously, vaadin-bot (Vaadin Bot) wrote…

MAJOR Prefix this call to "put" with "super.". rule

Looks legit, also there's another similar one. Also I wonder why do we extend the HashMap at all instead of hiding it as a field – we might expose the methods we don't want to expose at least.


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 117 at r1 (raw file):

Previously, vaadin-bot (Vaadin Bot) wrote…

MINOR Remove this method to simply inherit it. rule

This is related to the previous Sonar message – either we store hashmap as a field and have these methods or we remove those two.


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 189 at r1 (raw file):

            values = new SingleValue(key, value);
        } else {
            if (!(values instanceof HashMapValues)) {

Optional, since require noticeable changes to the PR.

If I understand what's happening here correctly, we start with a single-sized collection and then replace it with multiple sized and nothing else is done.

If that's true, It's possible to simplify the code and remove casts and instanceof checks completely.

What I would do is to remove the SingleValue and HashMapValues completely and, most probably, Values interface also.
Then I'd create a simple class that has a values field and the methods required (put, get etc.).
Then, on the element added, I'd check the size of the current map, if it's absent ot zero, initialize it with a single value (or singleton map), otherwise replace it with a regular map.

This will help to encapsulate the resize logic into a single class that can be unit tested, reduce the code in this class and remove all the current sonar warnings, instanceof checks, casts and HashMap extensions.

A HashMap with only one item uses lots of memory since the hash table
has room for 16 items, and each actually used item in the array is a
linked list node. Collections.singletonMap gets rid of this overhead,
but still uses memory for caching keySet, entrySet and values collection
view instances.

Reduces BasicElementView memory use from 429706 to 378338 bytes and the
memory use in BeverageBuddy with an edit dialog open from 262257 to
230435 bytes.
}

@Override
public boolean containsKey(String key) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

MINOR Remove this method to simply inherit it. rule

@vaadin-bot
Copy link
Collaborator

SonarQube analysis reported 2 issues

  • MINOR 2 minor

Watch the comments in this conversation to review them.

Copy link
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

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

Reviewed 2 of 2 files at r2.
Reviewable status: 4 unresolved discussions, 0 of 1 LGTMs obtained

Copy link
Member Author

@Legioth Legioth left a comment

Choose a reason for hiding this comment

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

Reviewable status: 4 unresolved discussions, 0 of 1 LGTMs obtained


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 67 at r1 (raw file):

Previously, SomeoneToIgnore (Kirill Bulatov) wrote…

Looks like we don't change the value after setting it, so we can have it as final also.

It's the opposite: It's a bug that put("foo", "bar); put("foo", "baz") will make it allocate a full-size HashMap instead of reassigning this field. Fixing the bug and adding a test for the case.


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 101 at r1 (raw file):

Previously, SomeoneToIgnore (Kirill Bulatov) wrote…

This is able to throw NPE when the value is null.
Should we assert it in the constructor? Alternatively, we can return an empty stream in this case.

This compiles into an invocation of Stream.of(Object) which gives a stream containing a null value. Not to be confused with Stream.of(Object ...) which would indeed throw.

Added a test for the case just to be on the safe side.


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 112 at r1 (raw file):

Previously, SomeoneToIgnore (Kirill Bulatov) wrote…

Looks legit, also there's another similar one. Also I wonder why do we extend the HashMap at all instead of hiding it as a field – we might expose the methods we don't want to expose at least.

Done.

The reason for extending instead of wrapping HashMap is to keep memory overhead at a minimum, at the expense of simplicity.


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 117 at r1 (raw file):

Previously, SomeoneToIgnore (Kirill Bulatov) wrote…

This is related to the previous Sonar message – either we store hashmap as a field and have these methods or we remove those two.

This is a false positive from sonar because it doesn't consider type erasure. Without this method, the compiler complains that Values.get isn't implemented.


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 189 at r1 (raw file):

Previously, SomeoneToIgnore (Kirill Bulatov) wrote…

Optional, since require noticeable changes to the PR.

If I understand what's happening here correctly, we start with a single-sized collection and then replace it with multiple sized and nothing else is done.

If that's true, It's possible to simplify the code and remove casts and instanceof checks completely.

What I would do is to remove the SingleValue and HashMapValues completely and, most probably, Values interface also.
Then I'd create a simple class that has a values field and the methods required (put, get etc.).
Then, on the element added, I'd check the size of the current map, if it's absent ot zero, initialize it with a single value (or singleton map), otherwise replace it with a regular map.

This will help to encapsulate the resize logic into a single class that can be unit tested, reduce the code in this class and remove all the current sonar warnings, instanceof checks, casts and HashMap extensions.

This is an extreme memory optimization that unfortunately causes some additional complexity in the code. The additional indirection that you suggest would cost us 16 bytes for the object header and another 8 bytes for an inner values field. (and half of that in 32-bit mode).

Copy link
Member Author

@Legioth Legioth left a comment

Choose a reason for hiding this comment

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

Dismissed @vaadin-bot from a discussion.
Reviewable status: 3 unresolved discussions, 0 of 1 LGTMs obtained

Copy link
Contributor

@SomeoneToIgnore SomeoneToIgnore left a comment

Choose a reason for hiding this comment

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

:lgtm:

Dismissed @vaadin-bot from 2 discussions.
Reviewable status: all discussions resolved, 0 of 1 LGTMs obtained, and 1 stale


flow-server/src/main/java/com/vaadin/flow/internal/nodefeature/NodeMap.java, line 189 at r1 (raw file):

Previously, Legioth (Leif Åstrand) wrote…

This is an extreme memory optimization that unfortunately causes some additional complexity in the code. The additional indirection that you suggest would cost us 16 bytes for the object header and another 8 bytes for an inner values field. (and half of that in 32-bit mode).

Oh, ok, got no idea that this is so extreme.

@SomeoneToIgnore SomeoneToIgnore merged commit aca37a5 into master Aug 31, 2018
@SomeoneToIgnore SomeoneToIgnore deleted the singleMapMemory branch August 31, 2018 12:13
@SomeoneToIgnore SomeoneToIgnore added this to the 1.1.0.beta3 milestone Sep 3, 2018
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