Project

General

Profile

Actions

Story #100973

open

VariableProcessor performance

Added by Alexander Schnitzler 11 months ago. Updated 10 months ago.

Status:
New
Priority:
Should have
Category:
Performance
Target version:
-
Start date:
2023-06-08
Due date:
% Done:

100%

Estimated time:
(Total: 0.00 h)
TYPO3 Version:
12
PHP Version:
8.2
Tags:
Sprint Focus:

Description

Introduction

Well, these days I faced a performance issue in one of my sites and I'd like to share my findings as a foundation for performance improvements.

First of all a short description of my use case:
A customer of mine has a site (basically an app) which was split into around 30 plugins with switchable controller actions. The navigation was built with actual pages in the backend, each site containing one of those plugins.

During the upgrade to 12, we decided to get rid of all pages, integrate all plugins into one and solve the removal of switchable controller actions with routing.
The routing configuration for that plugin contains 263 routes. So this is not the everyday use case but it's what out there in the wild.

However, after the upgrade we realized the site was horribly slow compared to the 8.7 instance although switching from PHP 7.3 to 8.2. So I knew something was off and a digged into it with blackfire.

I quickly realized the culprit: \TYPO3\CMS\Extbase\Routing\ExtbasePluginEnhancer::getVariant()


Findings

To understand the issue, we need to understand how the routing works. First, we have a rootline like /page/controller/action. In this case the rootline is /page/ containing of both /page/ and /. Now, without plugin enhancer option limitToPages set to /page/, the enhancer does the following during route matching (a process that cannot be cached):
Take all pages of the rootline (2 in this case) and multiply those by the amount of configured routes (263 in this case). That means, 526 route variants aka candidates are created.

Now, the amount of variants is not the main issue. I was able to half the number by using limitToPages and have route variants only be calculated for /page/ but the problem was still huge. Now, what I describe here is just the url matching, not the generation. In my case, the generation is the much bigger issue but the matching is a process that isn't cachable. Whenever TYPO3 receives a request to /page/controller/action, it needs to match it against the configured rules. Let me be clear on this. Part of this is cachable but at the moment it is not.

Link generation can often be cached. Or let me rephrase: That's often user land code and the user needs to take care of this, not the framework. But also here, parts are cachable.

So let's concentrate on actual numbers. I have 26.126 calls to \TYPO3\CMS\Extbase\Routing\ExtbasePluginEnhancer::getVariant(), the majority (96.8%) coming from url generation, not matching. That's 92% of all wall time. The most problematic path is getVariant() calling applyRouteAspects() which calls deflateKeys() and getVariant() calling deflateKeys() directly. So there are 51.565 calls on that single method. Now things are getting wild. Those 51.565 calls, result in 810.379 calls to deflateValues().

Now I had two options. Should I try to find issues on upper or lower levels? Checking the upper level means, going back to getVariant() and understand why it's called 26.126 times. I do generate a lot of url's on my site and given the amount of configured routes I still thought something was off there but I decided to check the lower level and seek room for optimisation there.

deflateValues()

So, what did I do to check that method for optimisations? My main questions are:

- Is the set of method arguments small enough to create a cache identifier from it?
- Is further execution side effect free? This means, is the method deterministic and only dependent on the set of incoming parameters?

Both is the case!
I hashed the set of arguments and counted the amount of calls to that method with the same set of arguments. And boom. Didn't expect this. There were fewer than 100 different argument variants. I quickly built a runtime cache on the class (static properties) and the result are striking. -810.379 calls to addNestedValue, -810.379 calls to addHash. Around the same amount of calls less to str_contains and preg_match. That's -40% in CPU and wall time.


to be continued


Subtasks 1 (0 open1 closed)

Bug #100974: Add 1st level cache to VariableProcessor::deflateValues()ResolvedAlexander Schnitzler2023-06-08

Actions
Actions #1

Updated by Alexander Schnitzler 11 months ago

  • Subtask #100974 added
Actions

Also available in: Atom PDF