Skip to content

Optimizing CPU and memory usage

Igor Zinken edited this page Apr 28, 2020 · 6 revisions

While mobile devices are getting more and more powerful with increased memory size and processing power, you can't expect your audience to all own the latest and baddest one. The following pointers are strongly encouraged for optimizing your engine, though it can be argued that it is always good practice to minimize overhead, regardless of how luxurious the environments capabilities are!

General guidelines / good practices

object pooling

Pre-initialize objects whose initialization is expensive and keep them ready in a pool. If an object is required, it is retrieved from the pool and its properties updated accordingly to fit the new usage context. After the object usage has run its course, it is returned to the pool instead of destroyed. On the Java side of things, this is especially convenient as it avoids the hit of the garbage collector. This is suggested for instruments and processing chains.

prevent memory allocations / deallocations in render thread

Initialize all used objects and variables outside of the engines thread loop, and dispose of them outside of the thread. This basically means: do not use the callbacks fired from the engine's Observers to directly create / destroy objects.

Use the SampleManager to load your projects samples upfront. Create all known AudioEvents upfront.

Advanced engine usage using cacheable audio

Disclaimer: the below is a description of a proposal to not keep resynthesizing static audio, but instead cache it and read from the cache instead. You're basically creating a reusable sample from your generated audio. This frees up CPU during audio rendering, but comes at the expense of requiring more memory. This is useful when you are creating an application using runtime synthesis (e.g. not sample only) or are creating effects processors with heavy DSP processing.

caching of audio events

If throughout your program you need a value - which is calculated at runtime - and the value remains constant ( either throughout the entire program lifetime or for n iterations ), do not keep recalculating it. For instance, each time a single A note that lasts for a full quaver of a measure is sequenced for output, only have the instrument render the corresponding audio (into a single buffer lasting for the entire event duration) once. Read from this buffer for subsequent triggers of the same event. Only invalidate this cached buffer when the properties of its environment have changed. This can be done using a CacheableAudioEvent or the cache of an AudioChannel. Also see how BaseProcessors can cache their output if their process is deterministic and operates on the same source.

doing work in advance

Keeping the previous suggestion of caching in mind, you can use parallel threads to prepare objects which will eventually end up in the engines output thread. For instance : if a sequence has four measures and the sequencer is currently playing the second measure, you can pre-cache the buffers of the audio events present in the third measure and make sure they are ready ahead of time.

Clone this wiki locally