Performance tip #193: Preload your fonts

So, I’m wrapping up a few sample projects that use my upcoming story/dialogue plugin and I decided to run some benchmarks. I know this is obvious to some, but I have been using Corona for almost five years and I never even considered how Corona loads fonts. So, this is for those two or three people wanting to optimise their games and who, like me, didn’t know about this yet.

Simply put, creating a text display object for the first time takes a really long time. Let’s assume I have four custom fonts and the following code:

scene.lua
 

local composer = require( "composer" ) local scene = composer.newScene() function scene:create( event ) local sceneGroup = self.view local text = {} local time = system.getTimer() local font for i = 1, 4 do if i == 1 then font = "fonts/OpenSans-Regular.ttf" elseif i == 2 then font = "fonts/OpenSans-Italic.ttf" elseif i == 3 then font = "fonts/OpenSans-Bold.ttf" else font = "fonts/OpenSans-BoldItalic.ttf" end text[i] = display.newText( "Hello World #"..i.."!", 160, 120+i\*32, font, 24 ) print( "\"Hello World #"..i.."!\" created in "..system.getTimer()-time.."ms." ) time = system.getTimer() end end scene:addEventListener( "create", scene ) return scene

The output is roughly

"Hello World #1!" created in 251.4ms. "Hello World #2!" created in 252.6ms. "Hello World #3!" created in 256.6ms. "Hello World #4!" created in 255.4ms.

So it takes roughly a quarter of a second to create a new text display object when using a new font for the first time, or over 1 second combined for all 4 fonts. This feels like an eternity to me because when I am creating text display objects, I want them immediately and not soon-ish.

So, if I add the following code to main.lua:

local composer = require ("composer") local text = {} local font for i = 1, 4 do if i == 1 then font = "fonts/OpenSans-Regular.ttf" elseif i == 2 then font = "fonts/OpenSans-Italic.ttf" elseif i == 3 then font = "fonts/OpenSans-Bold.ttf" else font = "fonts/OpenSans-BoldItalic.ttf" end text[i] = display.newText( "", 0, 0, font, 24 ) display.remove(text[i]) end composer.gotoScene("scene")

Then the output from scene.lua becomes:

"Hello World #1!" created in 0.5ms. "Hello World #2!" created in 0.4ms. "Hello World #3!" created in 0.5ms. "Hello World #4!" created in 0.3ms.

So, tldr: preload your fonts by creating a display object using them and delete them straight away.

Hopefully this was news to someone else as well and it helps you out. If it were up to me, I’d include a short disclaimer about how fonts are loaded in Corona’s documentation.

This might actually be an OS thing.  i.e. The OS you’re on needs to read the file and load the font, and each OS handles this differently.

Still, this was an interesting post. Thanks for taking the time to write it.

Question 1: how about native fonts?  Did those have no load time?

Question 2: Can you list the file size of each of those files too?  I know some custom fonts are huge.  The size of the file would be an interesting metric to correlate.

Yeah, it could very well be an OS thing. I’ll try running the code on my Mac and Android tomorrow.

  1. Native fonts seemed to have a longer load time on simulator running on Windows 10 compared to preloaded custom fonts. Compared to non-preloaded fonts, they were a lot faster. For instance, by replacing the custom fonts in the loop above with the following:

    if i % 2 == 0 then font = native.systemFont else font = native.systemFontBold end

Then the output was:

"Hello World #1!" created in 4.3ms. "Hello World #2!" created in 4ms. "Hello World #3!" created in 3.7ms. "Hello World #4!" created in 4.4ms.

The times varied between ~3.5ms to ~5ms. There was also no difference if the “if i % 2 == 0” or other conditions were removed or if only “font = native.systemFont” or “font = native.systemFontBold” was used.

  1. I’ll run some additional tests tomorrow and provide you with more detailed results, as well as the project file that I’ll be using. The font files that I used are about 214KB each.

@Spyric - Good to know!  I haven’t delved into fonts much but this is useful info.  Thanks for sharing.

So, I created a benchmarking project to see how the fonts load on different operating systems. If anyone is interested, you can download and try it out yourself. It’ s limited to loading either only the native.systemFont or the 9 custom fonts within it. To run the benchmark again, you need to restart it.

Now, the loading times are indeed OS specific, but the sizes of the font files didn’t seem to have any correlation with the load times.

I ran the code on three of my devices:
Device 1: a 2016 high-end gaming laptop running Windows 10
Device 2: a 2015 Mac mini 
Device 3: a 2016 Samsung Galaxy Tab A 10.1

The results:

Device 2  was clearly the fastest and there didn’t seem to be much of difference between system fonts and custom fonts.

Creating a new text object using native.systemFont for the first time took about ~2.5-5ms and all subsequent new text objects took ~0.08-0.2ms to create. Creating a new text object for the first time using any custom font took ~2.5-5ms and all subsequent new text objects took ~0.1-0.2ms.

Device 3 was the second fastest and there were clear differences between using different fonts.

Creating a new text object using native.systemFont for the first time took about ~4-7ms and all subsequent new text objects took ~1.5-3ms to create. Creating a new text object for the first time using any custom font took ~9-15ms and all subsequent new text objects took ~2-3.5ms.

Device 1 was the slowest by a wide margin in terms of custom fonts.

Creating a new text object using native.systemFont for the first time took about ~0.9-1.5ms and all subsequent new text objects took ~0.2-0.6ms to create. Now, creating a new text object for the first time using any custom font took a whopping ~250-320ms, but all subsequent new text objects took only ~0.2-0.6ms.

 

In conclusion,
Corona seems to cache all fonts the first time they are used, making their subsequent use much faster, so preloading any and all fonts that you are using in your app may be a smart choice. Depending on the OS your app is running on, this initial caching may not take long enough for you or your user to notice even if you don’t preload them, but on Windows you’ll see significant hangups when using fonts for the first time.

I also attached two screenshots of two instances of the benchmark on Windows and Android.

Great benchmark. Did you do multiple runs? The caching might actually happen on the OS side and/or rely a lot on what happend on the testing device - also do both desktop machines use the same kind of drives (SSD or HDD).

On windows, f.i. with a cold cache accessing a HDD may be pretty slow (f.i. I have one HDD in my PC and use it for less performance sensitive data but also as one of my backup targets, and after a backup run it’s very obvious the OS purged all the filecache of those drive and even browsing folders requires noticable more time)

I ran the code 30 times on each device.

The Mac mini has your standard HDD, but my Windows laptop has an SSD, which is one of the many reasons why I would have expected it to outperform the Mac. I just ran the Windows 32 build 10 times on an external HDD, but it didn’t seem to have any effect on the loading speeds.

Oh, right. For clarification, if anyone needed or wanted such, all available native fonts (i.e. https://docs.coronalabs.com/api/library/native/getFontNames.html) took the same time to load as  native.systemFont described above.

So, native fonts are fast depending on the device and OS, and don’t necessarily need to be preloaded, but as someone who is keen on optimisation, I’d always at the very least preload any non-native .ttf and .otf fonts.

I find this somewhat confusing as I only use custom fonts (I support 12 different languages) and have never seen a newText() take 250ms to create!

@SGS, I found this confusing as well, which is why I created this thread. Are you working on a Mac or a PC, because if you are not on a PC, then you won’t see those load times? You could download the fontLoader.zip that I attached and see for yourself.

I’m using win 10.  I mainly use 2 fonts and both are used in main.lua (and alpha animated) so this is why I’ve never seen any slowdown.

I installed the fonts and got much better results but that will not help the average install of an app.

This might actually be an OS thing.  i.e. The OS you’re on needs to read the file and load the font, and each OS handles this differently.

Still, this was an interesting post. Thanks for taking the time to write it.

Question 1: how about native fonts?  Did those have no load time?

Question 2: Can you list the file size of each of those files too?  I know some custom fonts are huge.  The size of the file would be an interesting metric to correlate.

Yeah, it could very well be an OS thing. I’ll try running the code on my Mac and Android tomorrow.

  1. Native fonts seemed to have a longer load time on simulator running on Windows 10 compared to preloaded custom fonts. Compared to non-preloaded fonts, they were a lot faster. For instance, by replacing the custom fonts in the loop above with the following:

    if i % 2 == 0 then font = native.systemFont else font = native.systemFontBold end

Then the output was:

"Hello World #1!" created in 4.3ms. "Hello World #2!" created in 4ms. "Hello World #3!" created in 3.7ms. "Hello World #4!" created in 4.4ms.

The times varied between ~3.5ms to ~5ms. There was also no difference if the “if i % 2 == 0” or other conditions were removed or if only “font = native.systemFont” or “font = native.systemFontBold” was used.

  1. I’ll run some additional tests tomorrow and provide you with more detailed results, as well as the project file that I’ll be using. The font files that I used are about 214KB each.

@Spyric - Good to know!  I haven’t delved into fonts much but this is useful info.  Thanks for sharing.

So, I created a benchmarking project to see how the fonts load on different operating systems. If anyone is interested, you can download and try it out yourself. It’ s limited to loading either only the native.systemFont or the 9 custom fonts within it. To run the benchmark again, you need to restart it.

Now, the loading times are indeed OS specific, but the sizes of the font files didn’t seem to have any correlation with the load times.

I ran the code on three of my devices:
Device 1: a 2016 high-end gaming laptop running Windows 10
Device 2: a 2015 Mac mini 
Device 3: a 2016 Samsung Galaxy Tab A 10.1

The results:

Device 2  was clearly the fastest and there didn’t seem to be much of difference between system fonts and custom fonts.

Creating a new text object using native.systemFont for the first time took about ~2.5-5ms and all subsequent new text objects took ~0.08-0.2ms to create. Creating a new text object for the first time using any custom font took ~2.5-5ms and all subsequent new text objects took ~0.1-0.2ms.

Device 3 was the second fastest and there were clear differences between using different fonts.

Creating a new text object using native.systemFont for the first time took about ~4-7ms and all subsequent new text objects took ~1.5-3ms to create. Creating a new text object for the first time using any custom font took ~9-15ms and all subsequent new text objects took ~2-3.5ms.

Device 1 was the slowest by a wide margin in terms of custom fonts.

Creating a new text object using native.systemFont for the first time took about ~0.9-1.5ms and all subsequent new text objects took ~0.2-0.6ms to create. Now, creating a new text object for the first time using any custom font took a whopping ~250-320ms, but all subsequent new text objects took only ~0.2-0.6ms.

 

In conclusion,
Corona seems to cache all fonts the first time they are used, making their subsequent use much faster, so preloading any and all fonts that you are using in your app may be a smart choice. Depending on the OS your app is running on, this initial caching may not take long enough for you or your user to notice even if you don’t preload them, but on Windows you’ll see significant hangups when using fonts for the first time.

I also attached two screenshots of two instances of the benchmark on Windows and Android.

Great benchmark. Did you do multiple runs? The caching might actually happen on the OS side and/or rely a lot on what happend on the testing device - also do both desktop machines use the same kind of drives (SSD or HDD).

On windows, f.i. with a cold cache accessing a HDD may be pretty slow (f.i. I have one HDD in my PC and use it for less performance sensitive data but also as one of my backup targets, and after a backup run it’s very obvious the OS purged all the filecache of those drive and even browsing folders requires noticable more time)

I ran the code 30 times on each device.

The Mac mini has your standard HDD, but my Windows laptop has an SSD, which is one of the many reasons why I would have expected it to outperform the Mac. I just ran the Windows 32 build 10 times on an external HDD, but it didn’t seem to have any effect on the loading speeds.

Oh, right. For clarification, if anyone needed or wanted such, all available native fonts (i.e. https://docs.coronalabs.com/api/library/native/getFontNames.html) took the same time to load as  native.systemFont described above.

So, native fonts are fast depending on the device and OS, and don’t necessarily need to be preloaded, but as someone who is keen on optimisation, I’d always at the very least preload any non-native .ttf and .otf fonts.

I find this somewhat confusing as I only use custom fonts (I support 12 different languages) and have never seen a newText() take 250ms to create!

@SGS, I found this confusing as well, which is why I created this thread. Are you working on a Mac or a PC, because if you are not on a PC, then you won’t see those load times? You could download the fontLoader.zip that I attached and see for yourself.