Skip to content

Commit e730632

Browse files
committed
Prod-Profillauf: 746 ms statt 4188 ms, kein Laufzeitproblem in Produktion
Derselbe Speichervorgang braucht mit APP_ENV=prod und ohne Xdebug 746 ms gegenueber 4188 ms im dev-Modus - Faktor 5,6. Damit ist das Thema im Kern erledigt; die langen Zeiten waren groesstenteils eine Eigenschaft der Entwicklungsumgebung. Die Verteilung kehrt sich um: Unser Code liegt in prod bei 29 Prozent statt 5,6 Prozent, Logging faellt von 16,7 auf 2,8 Prozent, Debug-Dispatcher und Profiler auf null. Die dev-Zahlen taugen also nicht zur Priorisierung - das ist jetzt ausdruecklich vermerkt. Eine klare Fundstelle bleibt: RequestScopeDeterminator wird rund 6000-mal je Speichervorgang ausgewertet, obwohl sich die Antwort innerhalb einer Anfrage nicht aendert.
1 parent c585e0e commit e730632

1 file changed

Lines changed: 83 additions & 17 deletions

File tree

docs/performance-editmask.md

Lines changed: 83 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
> `setProperty` in MetaModels) wurde **versucht und wieder verworfen**, siehe
66
> [Verworfen: der Vergleich auf der Speicherform](#verworfen-der-vergleich-auf-der-speicherform).
77
>
8-
> **Danach gemessen statt geraten** — und das Ergebnis stellt alles Weitere infrage:
9-
> [Unser Code macht 5,6 % der Laufzeit aus](#wo-die-zeit-wirklich-hingeht), der Rest ist
10-
> Entwicklungs-Infrastruktur. Wer hier weiterarbeitet, sollte **zuerst im prod-Modus messen**.
8+
> **Danach gemessen statt geraten.** Kernbefund:
9+
> [Derselbe Speichervorgang braucht in prod 746 ms statt 4.188 ms](#derselbe-vorgang-in-prod)
10+
> — Faktor 5,6. In Produktion gibt es kein Laufzeitproblem. Wer dennoch optimiert, muss **in
11+
> prod messen**: Im dev-Modus verdeckt die Werkzeugschicht alles andere.
1112
1213
## Der Befund
1314

@@ -191,14 +192,74 @@ Herkunft:
191192
| **MetaModels** | **959 ms** | **3,5 %** |
192193
| **dc-general** | **570 ms** | **2,1 %** |
193194

194-
**Unser Code macht 5,6 % aus.** Über 40 % sind Entwicklungs-Infrastruktur, die in Produktion
195-
gar nicht läuft. Auffällig sind **14.320 Log-Einträge pro Speichervorgang**; sie erklären auch
196-
einen großen Teil der Symfony-Zeile (`Request::getUri()` u. ä. wird 14.215-mal aufgerufen,
197-
einmal je Log-Eintrag durch den Request-Processor).
195+
Im dev-Modus macht unser Code **5,6 %** aus; über 40 % sind Entwicklungs-Infrastruktur, die in
196+
Produktion gar nicht läuft. Auffällig sind **14.320 Log-Einträge pro Speichervorgang**; sie
197+
erklären auch einen großen Teil der Symfony-Zeile (`Request::getUri()` u. ä. wird 14.215-mal
198+
aufgerufen, einmal je Log-Eintrag durch den Request-Processor).
199+
200+
> **Diese Zahlen taugen nicht zur Priorisierung.** Sie beschreiben den dev-Modus, nicht das
201+
> Produkt. Der prod-Lauf im nächsten Abschnitt kommt zu einer ganz anderen Verteilung — dort
202+
> sind es **29 %** statt 5,6 %. Wer aus dem dev-Profil ableitet, woran er arbeiten sollte,
203+
> arbeitet am Profiler.
198204
199205
Einschränkung: Xdebug instrumentiert jeden Funktionsaufruf und überzeichnet daher Code mit
200206
vielen kleinen Aufrufen — also gerade die Instrumentierung selbst. Ihr Anteil ist eher zu hoch
201-
angesetzt. Am Verhältnis ändert das nichts: Es ist zu deutlich, um am Ergebnis zu rütteln.
207+
angesetzt.
208+
209+
## Derselbe Vorgang in prod
210+
211+
`APP_ENV=prod`, Xdebug aus, sechs Läufe nach zwei Aufwärmläufen:
212+
213+
| | Median | Min | Max |
214+
|---|---:|---:|---:|
215+
| dev | 4.188 ms | 4.147 | 5.932 |
216+
| **prod** | **746 ms** | 707 | 806 |
217+
218+
**Faktor 5,6.** Ein Speichervorgang mit 27 Widgets dauert in Produktion drei Viertel einer
219+
Sekunde. Das ist kein Laufzeitproblem — das gesamte Thema war zu einem großen Teil eine
220+
Eigenschaft der Entwicklungsumgebung.
221+
222+
Die Verteilung im prod-Profil sieht völlig anders aus als in dev:
223+
224+
| Herkunft | dev | **prod** |
225+
|---|---:|---:|
226+
| Contao-Core | 5,2 % | **20,8 %** |
227+
| Symfony (übrige) | 23,1 % | 18,5 % |
228+
| **MetaModels** | 3,5 % | **15,9 %** |
229+
| PHP-intern / Aufwärmen | 10,3 % | 14,5 % |
230+
| **dc-general** | 2,1 % | **13,3 %** |
231+
| Doctrine | 3,8 % | 6,7 % |
232+
| Logging / Monolog | 16,7 % | **2,8 %** |
233+
| Debug-EventDispatcher | 17,7 % | **0 %** |
234+
| Profiler / VarDumper | 7,5 % | **0 %** |
235+
| PhpParser | 9,2 % | 0 % |
236+
237+
Unser Code ist in prod **29 %** — der größte zusammenhängende Block. Von den 14.320
238+
Log-Einträgen bleiben 2,8 % Restkosten; das Logging war ein dev-Artefakt.
239+
240+
### Konkrete Fundstellen in prod
241+
242+
| Eigenzeit | Aufrufe | Stelle |
243+
|---:|---:|---|
244+
| 196 ms | 834 | `EventDispatcher->callListeners` (996 Dispatches gesamt) |
245+
| ~196 ms | 2.583 | Composer-Autoloader (`findFile`, `loadClass`, Closure) |
246+
| ~235 ms | 40 / 26.887 | Contao-Twig: `getInheritanceChains`, `getFirst`, `ThemeNamespace->match` |
247+
| ~112 ms | **~6.000** | `RequestScopeDeterminator` + `ScopeMatcher->isBackendRequest` |
248+
249+
Die letzte Zeile ist **unsere** und die einzige, die klar nach einem Fehler aussieht:
250+
`RequestScopeDeterminator->currentScopeIsUnknown()` läuft **5.811-mal** und
251+
`getCurrentScope()` **6.287-mal** für einen einzigen Speichervorgang. Die Antwort ändert sich
252+
innerhalb einer Anfrage nicht — ein Zwischenspeichern je Request wäre naheliegend und
253+
risikoarm. Größenordnung: rund 3,5 % der Rechenzeit, also etwa 25 ms von 746 ms.
254+
255+
Der Composer-Autoloader mit 2.583 Klassenladungen deutet darauf hin, dass im Devstack kein
256+
optimierter Classmap erzeugt wird (`composer dump-autoload -o`) — eine reine
257+
Bereitstellungsfrage, kein Code.
258+
259+
> **Beim Umschalten auf prod:** Der prod-Container-Cache war veraltet und quittierte den ersten
260+
> Versuch mit einem 500 (`WidgetBuilder::__construct()`, zu wenige Argumente — Stand vor dem
261+
> DI-Umbau). `cache:clear --env=prod` genügt; `cache:warmup` allein baut einen vorhandenen
262+
> Container nicht neu.
202263
203264
### Xdebug kostet Faktor 2,6
204265

@@ -229,15 +290,20 @@ Webserver tatsächlich geladen hat.
229290

230291
## Was offen bleibt
231292

232-
1. **Ein Profillauf im prod-Modus.** Alles oben ist im dev-Modus gemessen, wo über 40 % der
233-
Zeit auf Werkzeuge entfallen, die in Produktion fehlen. Erst prod zeigt die echte
234-
Verteilung der Anwendungslast — vorher ist jede weitere Optimierung Raten.
235-
2. **Die 14.320 Log-Einträge je Speichervorgang.** Auch in Produktion nicht umsonst, und die
236-
Zahl allein ist ein Geruch.
237-
3. **Die Änderungserkennung je Attributtyp** — begrenzt durch die 332 ms Datenbankzeit.
238-
4. **Die 76 `getWidget`-Aufrufe.** Zu klären, welche Durchläufe das sind und ob sich einer
239-
davon einsparen lässt.
240-
5. **Der Sprachwechsel je Property** bei übersetzten Modellen — noch nicht gemessen.
293+
Vorweg: **In Produktion gibt es kein Laufzeitproblem** (746 ms). Alles Folgende ist Kür, kein
294+
Pflichtprogramm — und lohnt nur, wenn es zugleich den Code klarer macht.
295+
296+
1. **`RequestScopeDeterminator` je Request zwischenspeichern.** ~6.000 Auswertungen derselben,
297+
innerhalb einer Anfrage unveränderlichen Frage. Der einzige Fund, der klar nach einem
298+
Fehler aussieht; rund 25 ms.
299+
2. **996 Event-Dispatches je Speichervorgang** — mit 196 ms der größte Einzelposten. Ob das zu
300+
viel ist, ist eine Architekturfrage, keine Optimierungsfrage.
301+
3. **Optimierter Composer-Classmap im Devstack** (`dump-autoload -o`) — Bereitstellung, kein
302+
Code, ~6 %.
303+
4. **Die Änderungserkennung je Attributtyp** — begrenzt durch die 332 ms Datenbankzeit im dev-
304+
Profil, in prod entsprechend weniger. Nach dem prod-Ergebnis kaum noch lohnend.
305+
5. **Die 76 `getWidget`-Aufrufe** und **der Sprachwechsel je Property** bei übersetzten
306+
Modellen — beides unverändert offen, beides nach diesen Zahlen nachrangig.
241307

242308
## Messung wiederholen
243309

0 commit comments

Comments
 (0)