Performance problems with newTableView

Hi all,

I am porting an Android game developed in Corona SDK to Windows Phone 8 using Corona Cards.

I have already switched from ttf to bitmap fonts using this library:

https://github.com/autismuk/Font-Manager

  • I am having some performance problems populating a newTableView. The same code on Android runs smoothly.

I have created a sample Visual studio project:

https://www.dropbox.com/s/zmj14nr4s4r4od2/TestTableView.rar

This is a pretty simple newTableView. In this example I have hardcoded 40 rows in the table view, but since it contains Facebook friends it can grow considerably. Simply scrolling the table view is quite chunky on Windows Phone and very fluid on Android.

  • Another performance problem I am having is downloading user avatar pictures from the internet. Here is the function I use to download the avatars and to draw it in the tableview rows:

    local function drawAvatar(source,rowData,row) local avatarIco if fileExists(source…"_"…rowData.fb_user_id…".png",system.DocumentsDirectory) then avatarIco = display.newImageRect(row.rowLayer1, source…"_"…rowData.fb_user_id…".png",system.DocumentsDirectory,100,100) avatarIco.anchorX = 0 avatarIco.anchorY = 0 avatarIco.x = 10 avatarIco.y = 10 local mask = graphics.newMask( “images/mask_ico108.png” ) avatarIco:setMask( mask ) avatarIco.maskScaleX = 1 avatarIco.maskScaleY = 1 avatarIco.maskX = 0 avatarIco.maskY = 0 else avatarIco = display.newImageRect(row.rowLayer1,“images/avatar.png”,100,100) avatarIco.anchorX = 0 avatarIco.anchorY = 0 avatarIco.x = 10 avatarIco.y = 10 local mask = graphics.newMask( “images/mask_ico108.png” ) avatarIco:setMask( mask ) avatarIco.maskScaleX = 1 avatarIco.maskScaleY = 1 avatarIco.maskX = 0 avatarIco.maskY = 0 local function networkListener( event ) if ( event.isError ) then elseif ( event.phase == “began” ) then elseif ( event.phase == “ended” ) then if row ~= nil and row.rowLayer1 ~= nil then local avatarIcoLocal = display.newImageRect(row.rowLayer1,source…"_"…rowData.fb_user_id…".png",system.DocumentsDirectory,100,100); avatarIcoLocal.anchorX = 0 avatarIcoLocal.anchorY = 0 avatarIcoLocal.x = 10 avatarIcoLocal.y = 10 local mask = graphics.newMask( “images/mask_ico108.png” ) avatarIcoLocal:setMask( mask ) avatarIcoLocal.maskScaleX = 1 avatarIcoLocal.maskScaleY = 1 avatarIcoLocal.maskX = 0 avatarIcoLocal.maskY = 0 avatarIco.alpha = 0 end end end local params = {} local avatarURL if source == “FB” then avatarURL = “https://graph.facebook.com/"..rowData.fb_user_id.."/picture?height=50&type=large&width=50” elseif source == “GN” then avatarURL = “https://graph.facebook.com/"..rowData.fb_user_id.."/picture?height=50&type=large&width=50” end network.download( avatarURL, “GET”, networkListener, params, source…"_"…rowData.fb_user_id…".png", system.DocumentsDirectory ) end end

Also this code seems to be very slow when executed on windows phone, probably due to the multiple “network.download” calls.

  • I have also another question regarding newTableView in general… I would like to be able to do something when a row goes off-screen (like cancelling network.download), but I have not found a way to achieve this (except doing some math on the “moved” event based on rows height scroll position). Is it possible?

Francesco,

I’m unable to extract your “TestTableView.rar” file.  Perhaps it’s not really a rar file?  Can you try zipping it instead?

I’ve tested the TableView and the performance seems fine to me.  I’ve tested with our “Interface\WidgetDemo” sample app that is included with the Corona Simulator.  That sample app creates a TableView with 75 rows.  I’ve replaced its calls to display.newText() with images.  On a Lumia 620 device, a low-end WP8 device, I’m easily getting 60 FPS when scrolling it.  My Lumia 920 device gets about 50 FPS, but that specific device never gives me 60 FPS in any Silverlight based app, even when the app is rendering nothing (I’ve proved that this device’s rendering thread, which is controlled by Microsoft, has a bad habit of invoking late on every 4th frame).

Are you testing with a real device or the WP8 emulator?  I ask because the WP8 emulator will *never* give you 60 FPS.

I also urge you to download some other Corona made apps from the WP8 app store to see how they perform on your devices.  This way you can gauge their performance.  The below apps have solid 60 FPS performance.  Sure, they’re not using TableViews (at least I don’t think so), but a TableView is simply made up of existing Corona display objects such as lines and rectangles.

   http://www.windowsphone.com/en-us/store/app/feed-omnomster-the-hungry-monster/05696854-39a9-464b-887c-35dce28a6148

   http://www.windowsphone.com/en-us/store/app/bloody-monsters/5c3c1f4b-eaa7-4767-a58c-9ce9e2c496c8

Regarding network.download(), the big difference between WP8 and Android here is that the WP8 operating system’s HTTP APIs use what’s called connection pooling.  What this means is that the OS will try to share/re-use the same TCP connection between HTTP requests.  So, if you have several HTTP requests all trying to connect to the same TCP/IP endpoint (in your case, facebook), then the operating system will queue your HTTP requests for that same connection and only execute them one at a time.  The intent on Microsoft’s end is for this to be less resource intensive, which would provide better performance on low-end WP8 devices.  Unfortunately, I’m not aware of any work-around for this behavior.  At least with the APIs provided by the operating system.  Note that this is not a performance issue per-se, it would just cause your network requests/download to be delayed if you have several attempting to connect to the same endpoint/host. 

Hi Joshua,

I have uploaded again the project:

https://www.dropbox.com/s/e55zbptp53tgx1s/TestTableView.zip?dl=0

Probably I am doing something wrong, or maybe the code is too heavy…

My concern is that on Android it runs a lot smoother than on WP8…

Thank you,

Francesco

Thanks for re-uploading your project.  I was able to download and extract it this time.

So, yeah, I can definitely see a performance issue in your table view.  I’m seeing the framerate drop from 60 FPS to about 35-40 FPS when scrolling it.  I’ll play with it some more next week to see what the root cause is.

Francesco,

I discovered that most of the performance issues are coming from the “fontmanager.lua” bitmap font library that you are using.  I timed how long a function call to display.newBitmapText() and object:setText() took… and it usually took about 15-30 milliseconds per string, depending on the number of characters in the string.  The more characters in the bitmap font string, the longer it took.  I dug into the “fontmanager.lua” file and discovered that it’s doing a lot of text parsing and regular expression per character as it lays-out the string.  Meaning that it’s extremely CPU intensive, and unfortunately, the most popular WP8 devices are low-end devices.  I’m not seeing anything Corona related that’s slowing down the library.  It’s looks like the library’s Lua script is just too CPU intensive.

I tested out your sample app with the “bmFont2” library and scrolling was a lot faster…

   http://www.bmglyph.com/corona-bitmap-font-with-graphics-2-0/

I haven’t tried TextCandy since it is commercial, but I’m curious if it’s faster as well.  You may want to give these bitmap font libraries a try and see what works best for you. 

Also, in our newest daily build of WP8 CoronaCards, we were able to slightly improve the speed of our file existence checking internally in our C/C++ code by about 0.5-1.0 millisecond.  It’s a tiny performance improvement, but it is a noticeable improvement when you load a lot of images frequently.  Especially in your TableView where each row contains several images and scrolling several rows ends up generating a lot of image objects.  Just note that while this does smooth the performance of the TableView a bit, your biggest performance issues is with the “Font-Manager” library.  That’s your biggest bottleneck.

Anyways, I hope this helps!

Hi Joshua,

thank you for your replay!

This is very helpful! I am going to try bmFont2 today.

The other problem I have is with loading (asyncrhnously?) the user avatars from facebook.

I do start a network.download in “onRowRender” callback. The function I use to manage avatar download is “drawAvatar” that you can find in my first post of this thread. If i remember correctly on the implementation of Corona SDK for WP8 the network calls are queued and executed one after the other and not concurrently. Is this correct?

Also I would like to be able to cancel the network call when the row goes off screen but the table view doesn’t have such callback.

I have concerns that network.download is called many times on the same url, should I track the urls being downloaded and act appropriately, or is it managed by the SDK?

Can you please clarify me how network.download works in Corona SDK for WP8 and eventually suggest me an approach to load user avatars from the internet with a cache mechanism to use in a corona table view?

Thank you,

Francesco Comi

Just to be clear, network.request() and network.download() are definitely asynchronous and are executed on another thread on WP8 and all other platforms.  The key difference on WP8 is that HTTP requests to be sent to the same domain/IPaddress are queued on the same thread.  It’s still threaded out, but the operations are queued because the WP8 operating system will only open up 1 socket connection to that 1 domain/IPaddress.  If you did 2 HTTP requests to 2 different domains, then they would be executed in parallel.  I hope that makes sense.

In your case, ideally, you should only download a user’s avatar once for the lifetime of the application… or perhaps after a certain expiration date like once a day.  You shouldn’t download an avatar every time the end-user scrolls to a new row.  Mostly due to data usage because most cellular provider have a limit to how much a user can download per month.  So, I’m thinking you should maintain your own cache of avatar images separately from your TableView.  You can store each user’s facebook id, avatar image file path that was downloaded, and the expiration time (time to download a new avatar image in case it has changed) to a JSON or SQLite file which you query on startup.  What makes this a good solution is that you minimize the end-user’s data usage and you download avatar images less often, allowing you to display avatar images much more quickly in you tableview.

The next trick is how to display a downloaded avatar image after the TableView’s onRowRender listener has been invoked and knowing when that row has been removed.  I don’t think our TableView provides a listener for when a row is removed.  However, all of our display objects (including groups) have a “finalize” event that gets dispatched when the object’s removeSelf() function gets called.  You can use this to determine if the TableView has removed the row’s display objects.

   http://docs.coronalabs.com/api/event/finalize

So, if the your row’s display.newGroup() object has been finalized, then you know that you no longer need to display an avatar image into that row because it has been scrolled off the screen.  Now, I still think you should let the download finish in case the user scrolls back.  You just don’t need to do a display.newImage() on it anymore.

Does this help?

Hi Joshua,

thank you for the clarifications.

I am actually caching the images myself and downloading them again only after 1 week.

The only concern I had was if the onRowRender was called multiple times on the same row before the download finishes. I will implement something to check what I am already downloading.

I didn’t know about the finalize event, it is very interesting and I am going to try it.

Regarding the bitmap fonts I am trying with bmf.lua (taken from this thread http://forums.coronalabs.com/topic/4525-bitmap-font/), since the bmFont2 (if I understand correctly) requires bmGlyph, that is a commercial product and we already use Glyph Designer.

Unfortunately bmf.lua doesn’t support text resizing… so I will probably have to do some hacking on the library.

It looks like the following forum thread involves Corona developers that are actively using/developing the Font-Manager library.  Perhaps you should bring it up there.

   http://forums.coronalabs.com/topic/47691-looking-for-feedbacksuggestions-on-my-free-bitmap-font-library-bit-like-textcandy/

Since using a bitmap font library is effectively a must on WP8, this might be a good time for them to improving the performance of the library compared to the 2 other ones that we advertise here…

   http://docs.coronalabs.com/daily/coronacards/wp8/portapp.html#optimizing-text-performance

The best way to time how long it takes to generate a bitmap font generated string is like this…

local startTime = system.getTimer() -- Create the bitmap font string display object. print("Total Time: " .. tostring(system.getTimer() - startTime) .. " ms")

And like I said before, I’m pretty sure the performance issue is caused by all of the string parsing and regular expression that I’m seeing with that library.  While it makes sense to parse the string that you pass into the library to extract the characters, it does a lot of wasteful parsing to fetch configuration information that it could have cached to a Lua table for quick lookup.

Francesco,

For you info, we’ve discovered a bug with the “finalize” event on all platforms where the “finalize” event of an object will not get dispatched (ie: it’s Lua listener will not get called) if you call removeSelf() on its parent group.  The object was correctly cleaned up from memory.  It was merely an event dispatching issue, which might affect you if you’re listening for the “finalize” event of an object within the row’s group.

We fixed this issue in daily build #2544.

Francesco,

I’m unable to extract your “TestTableView.rar” file.  Perhaps it’s not really a rar file?  Can you try zipping it instead?

I’ve tested the TableView and the performance seems fine to me.  I’ve tested with our “Interface\WidgetDemo” sample app that is included with the Corona Simulator.  That sample app creates a TableView with 75 rows.  I’ve replaced its calls to display.newText() with images.  On a Lumia 620 device, a low-end WP8 device, I’m easily getting 60 FPS when scrolling it.  My Lumia 920 device gets about 50 FPS, but that specific device never gives me 60 FPS in any Silverlight based app, even when the app is rendering nothing (I’ve proved that this device’s rendering thread, which is controlled by Microsoft, has a bad habit of invoking late on every 4th frame).

Are you testing with a real device or the WP8 emulator?  I ask because the WP8 emulator will *never* give you 60 FPS.

I also urge you to download some other Corona made apps from the WP8 app store to see how they perform on your devices.  This way you can gauge their performance.  The below apps have solid 60 FPS performance.  Sure, they’re not using TableViews (at least I don’t think so), but a TableView is simply made up of existing Corona display objects such as lines and rectangles.

   http://www.windowsphone.com/en-us/store/app/feed-omnomster-the-hungry-monster/05696854-39a9-464b-887c-35dce28a6148

   http://www.windowsphone.com/en-us/store/app/bloody-monsters/5c3c1f4b-eaa7-4767-a58c-9ce9e2c496c8

Regarding network.download(), the big difference between WP8 and Android here is that the WP8 operating system’s HTTP APIs use what’s called connection pooling.  What this means is that the OS will try to share/re-use the same TCP connection between HTTP requests.  So, if you have several HTTP requests all trying to connect to the same TCP/IP endpoint (in your case, facebook), then the operating system will queue your HTTP requests for that same connection and only execute them one at a time.  The intent on Microsoft’s end is for this to be less resource intensive, which would provide better performance on low-end WP8 devices.  Unfortunately, I’m not aware of any work-around for this behavior.  At least with the APIs provided by the operating system.  Note that this is not a performance issue per-se, it would just cause your network requests/download to be delayed if you have several attempting to connect to the same endpoint/host. 

Hi Joshua,

I have uploaded again the project:

https://www.dropbox.com/s/e55zbptp53tgx1s/TestTableView.zip?dl=0

Probably I am doing something wrong, or maybe the code is too heavy…

My concern is that on Android it runs a lot smoother than on WP8…

Thank you,

Francesco

Thanks for re-uploading your project.  I was able to download and extract it this time.

So, yeah, I can definitely see a performance issue in your table view.  I’m seeing the framerate drop from 60 FPS to about 35-40 FPS when scrolling it.  I’ll play with it some more next week to see what the root cause is.

Francesco,

I discovered that most of the performance issues are coming from the “fontmanager.lua” bitmap font library that you are using.  I timed how long a function call to display.newBitmapText() and object:setText() took… and it usually took about 15-30 milliseconds per string, depending on the number of characters in the string.  The more characters in the bitmap font string, the longer it took.  I dug into the “fontmanager.lua” file and discovered that it’s doing a lot of text parsing and regular expression per character as it lays-out the string.  Meaning that it’s extremely CPU intensive, and unfortunately, the most popular WP8 devices are low-end devices.  I’m not seeing anything Corona related that’s slowing down the library.  It’s looks like the library’s Lua script is just too CPU intensive.

I tested out your sample app with the “bmFont2” library and scrolling was a lot faster…

   http://www.bmglyph.com/corona-bitmap-font-with-graphics-2-0/

I haven’t tried TextCandy since it is commercial, but I’m curious if it’s faster as well.  You may want to give these bitmap font libraries a try and see what works best for you. 

Also, in our newest daily build of WP8 CoronaCards, we were able to slightly improve the speed of our file existence checking internally in our C/C++ code by about 0.5-1.0 millisecond.  It’s a tiny performance improvement, but it is a noticeable improvement when you load a lot of images frequently.  Especially in your TableView where each row contains several images and scrolling several rows ends up generating a lot of image objects.  Just note that while this does smooth the performance of the TableView a bit, your biggest performance issues is with the “Font-Manager” library.  That’s your biggest bottleneck.

Anyways, I hope this helps!

Hi Joshua,

thank you for your replay!

This is very helpful! I am going to try bmFont2 today.

The other problem I have is with loading (asyncrhnously?) the user avatars from facebook.

I do start a network.download in “onRowRender” callback. The function I use to manage avatar download is “drawAvatar” that you can find in my first post of this thread. If i remember correctly on the implementation of Corona SDK for WP8 the network calls are queued and executed one after the other and not concurrently. Is this correct?

Also I would like to be able to cancel the network call when the row goes off screen but the table view doesn’t have such callback.

I have concerns that network.download is called many times on the same url, should I track the urls being downloaded and act appropriately, or is it managed by the SDK?

Can you please clarify me how network.download works in Corona SDK for WP8 and eventually suggest me an approach to load user avatars from the internet with a cache mechanism to use in a corona table view?

Thank you,

Francesco Comi

Just to be clear, network.request() and network.download() are definitely asynchronous and are executed on another thread on WP8 and all other platforms.  The key difference on WP8 is that HTTP requests to be sent to the same domain/IPaddress are queued on the same thread.  It’s still threaded out, but the operations are queued because the WP8 operating system will only open up 1 socket connection to that 1 domain/IPaddress.  If you did 2 HTTP requests to 2 different domains, then they would be executed in parallel.  I hope that makes sense.

In your case, ideally, you should only download a user’s avatar once for the lifetime of the application… or perhaps after a certain expiration date like once a day.  You shouldn’t download an avatar every time the end-user scrolls to a new row.  Mostly due to data usage because most cellular provider have a limit to how much a user can download per month.  So, I’m thinking you should maintain your own cache of avatar images separately from your TableView.  You can store each user’s facebook id, avatar image file path that was downloaded, and the expiration time (time to download a new avatar image in case it has changed) to a JSON or SQLite file which you query on startup.  What makes this a good solution is that you minimize the end-user’s data usage and you download avatar images less often, allowing you to display avatar images much more quickly in you tableview.

The next trick is how to display a downloaded avatar image after the TableView’s onRowRender listener has been invoked and knowing when that row has been removed.  I don’t think our TableView provides a listener for when a row is removed.  However, all of our display objects (including groups) have a “finalize” event that gets dispatched when the object’s removeSelf() function gets called.  You can use this to determine if the TableView has removed the row’s display objects.

   http://docs.coronalabs.com/api/event/finalize

So, if the your row’s display.newGroup() object has been finalized, then you know that you no longer need to display an avatar image into that row because it has been scrolled off the screen.  Now, I still think you should let the download finish in case the user scrolls back.  You just don’t need to do a display.newImage() on it anymore.

Does this help?

Hi Joshua,

thank you for the clarifications.

I am actually caching the images myself and downloading them again only after 1 week.

The only concern I had was if the onRowRender was called multiple times on the same row before the download finishes. I will implement something to check what I am already downloading.

I didn’t know about the finalize event, it is very interesting and I am going to try it.

Regarding the bitmap fonts I am trying with bmf.lua (taken from this thread http://forums.coronalabs.com/topic/4525-bitmap-font/), since the bmFont2 (if I understand correctly) requires bmGlyph, that is a commercial product and we already use Glyph Designer.

Unfortunately bmf.lua doesn’t support text resizing… so I will probably have to do some hacking on the library.

It looks like the following forum thread involves Corona developers that are actively using/developing the Font-Manager library.  Perhaps you should bring it up there.

   http://forums.coronalabs.com/topic/47691-looking-for-feedbacksuggestions-on-my-free-bitmap-font-library-bit-like-textcandy/

Since using a bitmap font library is effectively a must on WP8, this might be a good time for them to improving the performance of the library compared to the 2 other ones that we advertise here…

   http://docs.coronalabs.com/daily/coronacards/wp8/portapp.html#optimizing-text-performance

The best way to time how long it takes to generate a bitmap font generated string is like this…

local startTime = system.getTimer() -- Create the bitmap font string display object. print("Total Time: " .. tostring(system.getTimer() - startTime) .. " ms")

And like I said before, I’m pretty sure the performance issue is caused by all of the string parsing and regular expression that I’m seeing with that library.  While it makes sense to parse the string that you pass into the library to extract the characters, it does a lot of wasteful parsing to fetch configuration information that it could have cached to a Lua table for quick lookup.

Francesco,

For you info, we’ve discovered a bug with the “finalize” event on all platforms where the “finalize” event of an object will not get dispatched (ie: it’s Lua listener will not get called) if you call removeSelf() on its parent group.  The object was correctly cleaned up from memory.  It was merely an event dispatching issue, which might affect you if you’re listening for the “finalize” event of an object within the row’s group.

We fixed this issue in daily build #2544.