#Replacing a huge enumerator

1 messages · Page 1 of 1 (latest)

near cape
#

I have a monstrously big enumerator, almost 1400 entries, that I use to represent font icons from FontAwesome.

Each enum constant has two values:

  • the icon's description, e.g. "fas-user"
  • the Unicode character, e.g. '\uEE0D'

I'm facing performance issues with this solution because when the class is first loaded by the JVM it takes I think around 50ms.

I realized this issue in a GUI app. When showing a popup that uses icons from that enum there's a short but visible delay which is not ideal.

Question is, do you have any solution/replacement?

I tried transforming the enum to a standard class and putting every icon in a static HashMap. Early testing does not show the same overhead as the enum, but I'm not 100% sure.

tame wedgeBOT
#

<@&987246399047479336> please have a look, thanks.

#

Here is an AI assisted attempt to answer your question 🤖. Maybe it helps! In any case, a human is on the way 👍. To continue talking to the AI, you can use </chatgpt:1108714622413963314>.

#
TJ-Bot
I have a monstrously big enumerator, almost 1400 entries, that I use to represent font icons from FontAwesome. Each enum constant has two values: - the icon's description, e.g. "fas-user" - the Unicode character, e.g. '\uEE0D' I'm facing performance issu

You may want to consider using a Map instead of an enum for this scenario. With a Map, you can map the icon description to the Unicode value, allowing for more flexibility and scalability compared to having a huge enum. Here's an example of how you can implement this:

Map<String, String> fontIcons = new HashMap<>();
fontIcons.put("fas-user", "\uf007");
// Add more entries as needed

String unicodeValue = fontIcons.get("fas-user");

This way, you can easily retrieve the Unicode value by providing the icon description. It also allows you to dynamically add or remove entries without having to modify the code extensively.

Useful links:

sharp iris
#

i have my doubts

#

just turn on ur profiler and have a look where the bottleneck is at

#

perhaps ur doing sth like Enum.valueOf(...)

#

or doing other sort of inefficient lookups

#

or Enum.values()

#

i highly doubt the enum itself is the cause

mental saffron
#

Each enum constant has two values: the icon's description, e.g. "fas-user" the Unicode character, e.g. '\uEE0D' It only happen 1 time yes?

near cape
#

After hours of debugging and profling I traced down the issue to a couple lines that reference the enum. If I comment them, I don't see the delay anymore.

Your messages got me thinking, and so I went checking the enum code. Oh boy, I wrote some real bad code.
Let me show. So my enum constants appear like this:
USER("fas-user", '\uEE0D')

I made the Icon class so that one can specify the icon both by the enum constant or by its description/name.
In my enum I put a static cache to speed up the lookup, this is the code:

private static Map<String, Character> cache;

public static char toCode(String desc) {
  return Optional.ofNullable(cache().get(desc))
    .orElseThrow(() -> new IllegalArgumentException("Icon description '" + desc + "' is invalid!"));
}

public static Map<String, Character> cache() {
  if (cache == null) {
    cache = Arrays.stream(values()).parallel()
      .collect(Collectors.toUnmodifiableMap(
        FontAwesomeSolid::getDescription,
        FontAwesomeSolid::getCode
      ));
  }
  return cache;
}

So, basically, I'm building the entire cache when requesting even a single icon. I honestly don't know why I wrote such crap.
The strange thing is that the profiler does not show a hotspot on this method. In IntelliJ I see the mark next to the enum class and next to some random constant 🤷🏻‍♀️

mental saffron
#

you can just directly use the enum to get the value

near cape
#

Indeed.
Specifying the icon by its name/description is a nice convenience though.

mental saffron
#
  ONE("Hi");

  String name;

  TestEnum(String name){
    this.name = name;
  }
}```

then later

TestEnum.ONE.name();

#

Specifying the icon by its name/description wait

#

when ?

#

out of the application?

#

Because if you mean this, you can just do TestEnum.valueOf("ONE")

#

1400 is a lot

#

but a static switch function can do the job.

#

static public Type parse(final String name)

near cape
#

My icon class allows both these usages:

  • new MyIcon(IconEnum.MY_ICON)
  • new MyIcon("fas-my-icon")

To allow for the second one, a lookup is needed. Another way could be string manipulation, but I don't know how much more performant it would be

Yes 1400 is a lot I know

mental saffron
#

dont allow the second and you are golden.

#

is it used a lot?

#

if you grep 'new MyIcon'

#

is it a public repo?

#

1400 icons, like why

#

@near cape ?

#

I tried transforming the enum to a standard class and putting every icon in a static HashMap. Early testing does not show the same overhead as the enum, but I'm not 100% sure. actually does this lag at all?

#

I realized this issue in a GUI app. When showing a popup that uses icons from that enum there's a short but visible delay which is not ideal. is the problem only in the ui elements that need to be created?

near cape
#

is it use a lot?
It is fairly used, especially in resources external to the application.

is it a public repo?
Yes

1400 icon, like why
Because that's the font packet, I'm not the one making it

#

actually does this lag at all?
I see an improvement on the rendering pipeline, about 60/70ms better

mental saffron
#

Is the gui a web app, or swing/javafx?

near cape
#

JavaFX

mental saffron
#

and where do you actually see the lag? 1 time, multiple times?

#

does it happen every time you open the list?

near cape
#

Only the first time the popup opens. Which means the first time the skin is created, which is where the icon is also created

near cape
mental saffron
#

remove Arrays.stream

#

it is slow

#

ok, dont make cache like that

near cape
#

Yeah, I figured that haha

mental saffron
#

make a static private final map in the enum

#

add a static block

#

in this block build the map

#

you actually are near

#

Arrays.stream(values()).parallel()
.collect(Collectors.toUnmodifiableMap(
FontAwesomeSolid::getDescription,
FontAwesomeSolid::getCode
));
in a static function

#

and do not use the stream api

near cape
#

A basic for loop then?

mental saffron
#
  private static Map<String, Character> cache = buildCache();

  private static Map<String, Character> buildCache() {
    Map<String, Character> cache = new HashMap<>();

    for (var value: values()){
      cache.put(value.description, value.code);
    }
    return cache;
  }```
#

It is dumb, but stream api is just slow.

#

parallel() make it worse

#

Collectors.toUnmodifiableMap is not really needed, it is private

#
    public Map<String, Character> getCache() {
        return cache();
    }``` or not...
#

just why?

near cape
#

The result is the same. Building the entire cache is bad I think

mental saffron
#

exactly the same?

#

because it does so much less

#

what does the gui use?

#

the map building should be less than 1-5ms

near cape
#

Yes, maybe even slightly worse
The best so far is this one

mental saffron
#

it is nearly the same

near cape
mental saffron
#

just less by add but with a loop

#

create a jmh

sharp iris
#

FYI values() is problematic as it creates a new array each time its called. so this should only be done once, for example during startup when creating a cache

#

so the classic "lookup an enum by one of its properties" needs to be done with a proper Map instead of calling and iterating values() each time

mental saffron
#

Wait.. unicode as char???

#

some codepoint are multiples chars

near cape
sharp iris
#

u can give it some form of init method and call that whenever ur ready

#

and do it that way

#

if thats the problem

near cape
#

The issue it that this is a library
I'd have to tell my users to init things before doing anything else for ideal performance

sharp iris
#

i doubt a user will have a problem with the first use of an enum to take a few ms longer than usual

#

u dont really have a way around anyways, do u

#

java isnt really ideal for use in amazon lambdas or similar (serverless)

mental saffron
near cape
#

I see three solutions:

  1. I keep both the enum and the class with the prebuilt map
  2. I keep only the latter and I loose the benefits of enums
  3. I keep the enum but for sensitive areas (like showing a popup because the skin is created just before it is shown) I do not use my icon system but rather SVGs
mental saffron
#

Why not add an init function to init all caches? like in swing ui libraries?

#

you allow preinit, or it lazy load and lag a bit.

near cape
#

It could be a solution
With pre-init I see an improvement of around 180ms on the render pipeline

near cape
#

Let me see if I can setup jmh

mental saffron
#

id("me.champeau.jmh") version "0.7.3" jmh is the way to go.

near cape
#

Cool 👍

quick sand
quick sand
#
  1. The parallel() is almost certainly a performance cost rather than a gain (for 1400 elements).
  2. Compute the Map once
  3. favour an unmodifiable Map

May something like this...

enum X {
    A("Foo", 'X'), B("Bar", 'Y');

    public static final Map<String, X> ALL_BY_DESCRIPTION = Arrays.stream(values())
        .collect(Collectors.toUnmodifiableMap(
            X::description,
            Function.identity(),
            (_, _) -> {
                throw new IllegalStateException();
            }));

    private final String description;
    private final char code;

    private X(String description, char code) {
        this.description = description;
        this.code = code;
    }

    public String description() {
        return this.description;
    }

    public char code() {
        return this.code;
    }
}

And then access compile-time known elements with...

char code = X.A.code();

And runtime 'lookup by description' elements with...

String description = ...
char code = X.ALL_BY_DESCRIPTION.get(description).code();
near cape
#

The absolute most efficient way is to use the prebuilt map in IconsMap and create the skins before they are actually needed. This way the popup is shown almost instantly

The pulse time to show the popup drops from 124ms to 76ms
The layout computation time drops from 278ms to 125ms

That's insane