sghpjuikit / player

Audio player and management application.
22 stars 2 forks source link

Improve memory footprint #137

Closed sghpjuikit closed 5 years ago

sghpjuikit commented 5 years ago

Umbrella for long-term effort of bringing down memory consumption observed by an OS. Hence this involves

The goal is:

Testing: The use case I'll be running with is DirView loading many images, because it causes quick and high memory peak that our application must be able to get through back to normal.

sghpjuikit commented 5 years ago

I have profiled the application and discovered/confirmed several problems.

1 image grid component has a massive leak. After taking all precautions and confirming (with heap dump) no references to the image data on our code, the problem still persists due to (leaky?) references in deep internals of JavaFX code. Nothing helps, setting ImageView.image to null, clearing all ImageView references... There are some hashmaps containing the images in NGRegion (which is Node.peer). After hours of trying the one solution that worked for me was to use reflection on ImageView.image.platformImage.pixelBuffer/pixelaccessor and set them to null. Consequent call to gc immediatelly disposes all image data, confirmed both in heap dump and OS process manager. Fixing this will involve

2 window icons. We provide 7 size version, while only 1-2 may be necessary. These images also get computed each time a new window is created. I have put this into a lazy property and consider it fixed, but we may remove some icon versions in the future.

3 For autocomplete during tagging, all values of all String representable Metadata fields are grouped in a Map<Metadata.Field,Set<String>>. This involves date fields, comment, tags and other fields that contain long or always-unique strings. I have reduced the autocompletable fields to handpicked few. This does helps memory usage a lot for bigger library.

4 Kotlin intrinsics.checkNonNull. I haven't done rigorous profiling, but i did observe kotlin generated nullchecks to hog a lot of the cpu, in fact once I observed a method with total CPU running time 20s to waste almost all its computation time in the null check. I have already noticed this issue 2 years ago when I was profiling. I'm experimenting with -Xno-call-assertions, -Xno-param-assertions. This may make developer's life slightly less convenient if null gets through somewhere, so we may use this only in production. Right now I'm enabling this to see what will happen.

sghpjuikit commented 5 years ago

1 GridView image disposing has been improved in 1e7b601. Ultimately we do rely on reflection to get rid of the underlying image data reference, but I saw no other way to do this. Image disposing (in GridFileThumbCell at least) definitely works now. The memory is not released immediately, but that is left up to the gc tuning.

4 The observed overhead of Kotlin intrinsics.checkNonNull may be exacerbated by profiling.

I'll continue to profile and improve the memory footprint of the application.

We can also explore CompressedOops option of 64-bit JVM, I remember 32-bit version consuming literally half of the memory in the past, although as far as I know this option is already enabled by default.

xeruf commented 5 years ago

Regarding 4: I have also observed that while profiling an application of mine, which used recursion and a lot of small methods and thus wasted quite some cycles on this checkNonNull call. Especially since almost all projects I work on are pure Kotlin, this is mostly a waste. It only carries advantages if you interact with Java code.

sghpjuikit commented 5 years ago

Yeah.

Another overly memory-demanding objects I have identified are:

xeruf commented 5 years ago
sghpjuikit commented 5 years ago
sghpjuikit commented 5 years ago

Another point of interest coming from heap dump:

xeruf commented 5 years ago

A lot of instances refer to cells in tables. This could also be because we set font across the application using setStyle("...") which may be very inefficient.

Why do you not set the font in css? And yeah, table cells are kind of a nuisance, but since tables are lazy this should only be a problem if the window is big, right?

sghpjuikit commented 5 years ago

Why do you not set the font in css?

Because font has its own settings in ui with font picker and all, allowing user to 'override' skin value. We could dynamically generate a css file with the font style and apply that, but that seems dirty.

May be relevant: /javafxports/openjdk-jfx/pull/424

since tables are lazy

Tables are only virtualized in vertical direction and many columns may bring about unexpected performance problems. This just in: (/javafxports/openjdk-jfx/issues/409). Anyway, there may be room for improvement for us in this regard. I will keep reducing the amount of styling necessary, but it is a long term goal.

xeruf commented 5 years ago

We could dynamically generate a css file with the font style and apply that, but that seems dirty.

That is exactly how I apply themes in https://github.com/Xerus2000/util/blob/master/javafx/src/xerus/ktutil/javafx/Themes.kt

sghpjuikit commented 5 years ago

That is exactly how I apply themes

Do you consider it better approach than Node.setStyle?

xeruf commented 5 years ago

I honestly have no idea, but it sounds like you do setStyle for every single node? That doesn't seem efficient to me.

This was the only way I could find to update the styling of the whole Scene, which then also automatically copies that styling to any child stages.

sghpjuikit commented 5 years ago

You use setStyle on the root of the scene (one node per window). The properties trickle down on their own since they are inherited. As for child windows, not sure. Hard to say which is better actually.

sghpjuikit commented 5 years ago

More optimizations done:

sghpjuikit commented 5 years ago

More optimizations:

I will also experiment using generated css file approach to Node.setStyle for applying font. Would also enable optional overriding of the skin.

sghpjuikit commented 5 years ago

Closing as delivered.

I did some checking. I find current memory consumption ok. I'll work on more optimizations and memory leaks, but I consider this not an issue anymore.

Latest measurements as observed from OS task manager:

The most memory hungry is:

Note: Having 200k PseudoclassState instances is suspicious, but it does not cause much of a memory overhead.

sghpjuikit commented 5 years ago

Recently implemented feature that allows launching widgets in new process can help with memory footprint of long running instance as it isolates one-off widgets into different process.