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

First load very slow on older hardware #57

Open
maniac103 opened this issue Feb 12, 2025 · 4 comments
Open

First load very slow on older hardware #57

maniac103 opened this issue Feb 12, 2025 · 4 comments
Labels
🐛 bug Something isn't working

Comments

@maniac103
Copy link

JEmoji version

1.7.0

Steps to reproduce

When running (an app using) the library on an older device, the first EmojiManager static method invocation takes a very long time due to that class' static initializer. On a Sony Xperia X compact times up to 2 seconds were seen - see here for details.

What is expected?

Short initialization time (say < 100ms)

What is actually happening?

Very long initialization time (up to 2 seconds)

Any additional comments?

A lot of the time seems to be spent generating lookup maps for every possible use case; however, apps do typically use only a very specific subset of use cases. In our example, we only need 'replaceAliases`, so we don't need anything e.g. dealing with HTML entities. One solution therefore could be replacing the static map initialization by getter methods, initializing the static maps lazily once they're actually needed. Doing so would reduce both computing effort and memory usage in case only a subset of the library capabilities is actually needed.

@felldo
Copy link
Owner

felldo commented Feb 12, 2025

This might be a bit tough to do. A simple unprecise time calculation of each statement in the static constructor shows

93ms emojis
5ms EMOJI_UNICODE_TO_EMOJI
4ms EMOJIS_LENGTH_DESCENDING
13ms EMOJI_FIRST_CODEPOINT_TO_EMOJIS_ORDER_CODEPOINT_LENGTH_DESCENDING
5ms EMOJI_HTML_DECIMAL_REPRESENTATION_TO_EMOJI
5ms EMOJI_HTML_HEXADECIMAL_REPRESENTATION_TO_EMOJI
4ms EMOJI_URL_ENCODED_REPRESENTATION_TO_EMOJI
47ms MAX_HTML_DECIMAL_SINGLE_EMOJIS_CONCATENATED_LENGTH
4ms MIN_HTML_DECIMAL_CODEPOINT_LENGTH
10ms EMOJI_ALIAS_TO_EMOJIS <-- Can be removed
7ms POSSIBLE_EMOJI_ALIAS_STARTER_CODEPOINTS
12ms ALIAS_EMOJI_TO_EMOJIS_ORDER_CODEPOINT_LENGTH_DESCENDING
4ms ALIAS_EMOJI_MAX_LENGTH
2ms POSSIBLE_EMOJI_URL_ENCODED_STARTER_CODEPOINTS
1ms MINIMUM_EMOJI_URL_ENCODED_LENGTH
1ms MAXIMUM_EMOJI_URL_ENCODED_LENGTH
10ms ALLOWED_EMOJI_URL_ENCODED_SEQUENCES
237ms Total time

I can possibly get rid of these computations when pre-generating these values too.

  • 47ms MAX_HTML_DECIMAL_SINGLE_EMOJIS_CONCATENATED_LENGTH
  • 4ms MIN_HTML_DECIMAL_CODEPOINT_LENGTH
  • 7ms POSSIBLE_EMOJI_ALIAS_STARTER_CODEPOINTS
  • 4ms ALIAS_EMOJI_MAX_LENGTH
  • 2ms POSSIBLE_EMOJI_URL_ENCODED_STARTER_CODEPOINTS
  • 1ms MINIMUM_EMOJI_URL_ENCODED_LENGTH
  • 1ms MAXIMUM_EMOJI_URL_ENCODED_LENGTH
  • 10ms ALLOWED_EMOJI_URL_ENCODED_SEQUENCES

which adds up to 76ms. So this will probably lead to a reduction of 60ms with the new code = 170ms time for the new initialization in the static constructor.

These are alias specific

- 7ms POSSIBLE_EMOJI_ALIAS_STARTER_CODEPOINTS
- 12ms ALIAS_EMOJI_TO_EMOJIS_ORDER_CODEPOINT_LENGTH_DESCENDING
- 4ms ALIAS_EMOJI_MAX_LENGTH

and with these we still end up with probably around 120ms just for the aliases which I don't think can be reduced anymore because the necessary emojis list call which is is the most time consuming task is required for everything and can't be left out.

What Java version do you use? I noticed that when I switch from 8 to 17 the time goes from 230ms to 160ms without changing anything else. With Java 21 its around 180ms but generally a newer Java version should help a bit too.

I don't know how android development works, but would it be an option to use the library once on startup in another thread to initialize it?

@maniac103
Copy link
Author

What Java version do you use?

Since this is Android, we don't have a classic JRE and we don't have choice over it. On Android, a runtime called ART compiles the Java code to native code, with varying degrees of optimization depending on Android version. I am seeing relatively quick load times on an Android 15 emulator (~100ms on my host machine), but slow times on an Android 7 emulator (> 1s on the same host machine).

I don't know how android development works, but would it be an option to use the library once on startup in another thread to initialize it?

Unfortunately that doesn't really work for us: the app in question is a GitHub client, which shows the news feed as first screen. Showing that news feed requires parsing the GitHub API response, part of which is replacing the emoji aliases. So whether we do the class initialization in the UI thread (when showing the UI for the GH event list) or in a background thread doesn't really matter because in the latter case the UI thread will need to wait for the initialization of the class anyway.
We'll play with a for a bit though to see whether the changed timing might help.

The fact that speed depends on Android version in my eye hints to the compiler having issues in optimizing the initializer, similarly to what's described here. I wonder whether a lazily initialized singleton holding the required data may work better, just to avoid the static initializer?

Besides that,

So this will probably lead to a reduction of 60ms with the new code = 170ms time for the new initialization in the static constructor.

a 25% improvement in loading time seems quite impressive already :-)

@felldo
Copy link
Owner

felldo commented Feb 13, 2025

The largest improvement I got was by pre computing the constant variables. This got me down to about 150ms-160ms.

compiler having issues in optimizing the initializer

This is interesting. But it seem somewhat logical how it works / that it has issues. When applying the suggested fix it got probably another 10ms faster. Maybe on android the impact is even larger with no static constructors.

But the biggest issue is still initializing the auto generated emojis list which stays at approximately 100ms. I think that's because 5500+ emoji objects are created.

When assigning values only when they are actually used for the first time, I it took around 110ms to only initialize alias specific variables. So the difference is somewhat around 20-40ms for being fully initialize vs on demand.

For now I've rewritten the code which should yield the best performance on startup which means only using alias methods takes ~110ms on my pc. Lets see how this behaves in an android environment. Version 1.7.1 has been released and should be available in a few minutes or hours depending on the maven central repository

@maniac103
Copy link
Author

For now I've rewritten the code which should yield the best performance on startup which means only using alias methods takes ~110ms on my pc. Lets see how this behaves in an android environment. Version 1.7.1 has been released and should be available in a few minutes or hours depending on the maven central repository

Great, thanks a lot for this! We'll try and let you know about the results.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🐛 bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants