Fixed Poor performance on admin index page and add-on list with add-on icons

Jake B.

Well-known member
Affected version
2.2.5
Seems like whatever is happening to get the base64 data for add-on icons is quite slow. There are around 25 or so add-ons which contain an image icon that show up on the admin index page, most of which are around 128x128px and under 50kb, and according to the trace details each call to #XF\Image\Gd::save is taking anywhere from 250 to 400ms

Screen Shot 2021-06-29 at 10.53.58 AM.webp
 
Some time ago i thinked about suggesting use in add-on lists URL to icon like /admin.php?add-ons/XFRM/icon with simple file responding instead embedding in page. This also allows use cache in browser for icons.
 
Some time ago i thinked about suggesting use in add-on lists URL to icon like /admin.php?add-ons/XFRM/icon with simple file responding instead embedding in page. This also allows use cache in browser for icons.
XF2 did that originally, but it got changed to the current method instead for some reason or another
 
Because of performance, ironically.

250ms to 400ms seems like an extreme amount of time for this to be running per icon. The icons in my add-on list are running the same step in 2-3ms each. Then again, my icons are considerably smaller than 50KB.

Could you provide some of these icons that I can test with to see if this increases the execution time on my add-on list?
 
Thanks!

Ok, will look at this a little closer tomorrow to see if I can trigger anything similar.

However, my immediate thought is that those images are not well optimised. I'm sure there's a range of sizes here, some might be considerably smaller but if the average is ~45KB and people have around 20 of them loading then that's nearly 1MB.

But with a simple online compression tool it looks like they can be optimised considerably. And potentially with better tools even smaller:

1625007785530.webp

If you get them down to an average of 15KB then that's a considerable saving alone and probably worth doing, especially if you're talking dozens of add-ons.

But, still, we'll look into this and see if we can make any improvements there and I'll report back.
 
So, yes, I can reproduce the issue with the supplied icons. I created 19 test add-ons each using one of the four icons. Plus I have 3 XF add-ons using our standard ~1.2KB icon.

These are the results in seconds of how long $image->save() takes for each one:

Code:
2021-06-30 09:43:40 float(0.48349142045851) (Test 1)
2021-06-30 09:43:41 float(0.53236198425293) (Test 10)
2021-06-30 09:43:41 float(0.51950478553772) (Test 11)
2021-06-30 09:43:42 float(0.53217101097107) (Test 12)
2021-06-30 09:43:42 float(0.50297498703003) (Test 13)
2021-06-30 09:43:43 float(0.46560597419739) (Test 14)
2021-06-30 09:43:43 float(0.47917985916138) (Test 15)
2021-06-30 09:43:44 float(0.48830890655518) (Test 16)
2021-06-30 09:43:44 float(0.52034378051758) (Test 17)
2021-06-30 09:43:45 float(0.51168704032898) (Test 18)
2021-06-30 09:43:45 float(0.48771500587463) (Test 19)
2021-06-30 09:43:46 float(0.52005100250244) (Test 2)
2021-06-30 09:43:46 float(0.51728320121765) (Test 3)
2021-06-30 09:43:47 float(0.51793122291565) (Test 4)
2021-06-30 09:43:47 float(0.35379695892334) (Test 5)
2021-06-30 09:43:48 float(0.40026807785034) (Test 6)
2021-06-30 09:43:48 float(0.35393905639648) (Test 7)
2021-06-30 09:43:48 float(0.3932638168335) (Test 8)
2021-06-30 09:43:49 float(0.52607607841492) (Test 9)
2021-06-30 09:43:49 float(0.0027520656585693) (XF)
2021-06-30 09:43:49 float(0.0025899410247803) (XF)
2021-06-30 09:43:49 float(0.00242018699646) (XF)

I then ran the icons through pngquant locally though likely not with the most optimal settings as the icons are still around ~22KB whereas tinypng.com gets them down to about ~12KB:

Code:
2021-06-30 09:48:50 float(0.048039197921753) (Test 1)
2021-06-30 09:48:50 float(0.045054197311401) (Test 10)
2021-06-30 09:48:50 float(0.047561168670654) (Test 11)
2021-06-30 09:48:51 float(0.044196128845215) (Test 12)
2021-06-30 09:48:51 float(0.053647994995117) (Test 13)
2021-06-30 09:48:51 float(0.049530982971191) (Test 14)
2021-06-30 09:48:51 float(0.055483818054199) (Test 15)
2021-06-30 09:48:51 float(0.051875114440918) (Test 16)
2021-06-30 09:48:51 float(0.046474933624268) (Test 17)
2021-06-30 09:48:51 float(0.04341983795166) (Test 18)
2021-06-30 09:48:51 float(0.054211139678955) (Test 19)
2021-06-30 09:48:51 float(0.047724962234497) (Test 2)
2021-06-30 09:48:51 float(0.068761110305786) (Test 3)
2021-06-30 09:48:51 float(0.053205013275146) (Test 4)
2021-06-30 09:48:51 float(0.029088973999023) (Test 5)
2021-06-30 09:48:51 float(0.032469034194946) (Test 6)
2021-06-30 09:48:52 float(0.35818815231323) (Test 7)
2021-06-30 09:48:52 float(0.032851934432983) (Test 8)
2021-06-30 09:48:52 float(0.044275045394897) (Test 9)
2021-06-30 09:48:52 float(0.0024509429931641) (XF)
2021-06-30 09:48:52 float(0.002493143081665) (XF)
2021-06-30 09:48:52 float(0.002593994140625) (XF)
(Note there's an outlier here in that I somehow missed optimising Test 7).

That's quite a big difference.

It's worth noting that the savings would probably be greater. We render these as 64px x 64px. For high DPI displays, providing 128px x 128px images is reasonable. But these are 256px x 256px.

Just to explain in more detail why we use this data URI approach for icons currently and what the approach was originally.

Add-on developers store the icons in a directory that is not typically web accessible. The original approach was we loaded the images from a special URL, e.g. https://domain.com/admin.php?add-ons/icon/.

Each one of these requests is a fresh XF request and triggers a database connection. Some hosts were struggling with this, leading to potentially even worse performance than we're seeing here with the scope for hitting hard server limits. The solution was to move to this data URI approach where we read the image data and encode it to a base64 URI and render it inline. Potentially not as good in some ways seeing as that work has to happen on every page load, vs the original approach which may have benefited from some form of caching.

But, since then, we've not had any other reports of slow loading add-on lists.

I accept the approach isn't perfect - we have discussed other approaches in the past - but unoptimised images that are 2-4 times larger than they need to be certainly exacerbates the issue considerably.

Regardless of what we do next, I strongly recommend optimising all of those icons so they're no larger than 128px square and are compressed to a much smaller size (which is generally lossless to the naked eye).

We'll keep this open for now and discuss other options but certainly that's the best workaround we can recommend at this time.
 
Idea
When accessing https://domain.com/admin.php?add-ons generate/validate/update a cache with icon information.
If there are any changes (icon removed/added/icon file changed) sync those changes to data and load the icons from there.

Granted, that would make the icons public - but do they need to be kept private?

Doing so still would allow the icons to stay where they are (data would just act as a cache) but could significantly reduce per request overhead compared to the current approach.
 
They don't need to be kept private and that is similar to ideas we've had in the past but then it's another layer of code and potential complication.

We have another optimisation coming up. Quite simply we very likely don't need to even touch the Image driver system (and I'm struggling to remember why we did) but that should save a considerable amount of effort.
 
Yeah that's a considerable improvement.

If you wish to test, please change the XF\AddOn\AddOn::getIconUri method to:

PHP:
public function getIconUri()
{
   if (!$this->icon)
   {
      return null;
   }

   $iconPath = $this->getIconPath();
   $data = file_get_contents($iconPath);

   $mimeType = 'image/' . File::getFileExtension($iconPath);
   return 'data:' . $mimeType . ';base64,' . base64_encode($data);
}

Still worth optimising those icons though ;)
 
Thank you for reporting this issue, it has now been resolved. We are aiming to include any changes that have been made in a future XF release (2.2.6).

Change log:
Improve performance of loading icons on the add-on list.
There may be a delay before changes are rolled out to the XenForo Community.
 
I then ran the icons through pngquant locally though likely not with the most optimal settings as the icons are still around ~22KB whereas tinypng.com gets them down to about ~12KB:

Interesting, I believe they should have already been run through an optimizer, but I'll have to double check and make sure that gets taken care of
Yeah that's a considerable improvement.

If you wish to test, please change the XF\AddOn\AddOn::getIconUri method to:

I'll give it a go and see what happens!
 
Yeah that's a considerable improvement.

If you wish to test, please change the XF\AddOn\AddOn::getIconUri method to:
Got that deployed, average transaction time for \XF\Admin\Controller\Index@actionIndex went from 3,053ms to 95ms, and \XF\Admin\Controller\AddOn@actionIndex went from 4,220ms to 173ms. Have a task made to verify all the icons have been optimized as well to see how much performance benefit that adds, but I'm guessing it'll be comparatively minimal
 
Top Bottom