Beim Profilen einer Update-Schleife in meinem MMBasic-Spiel ist mir ein Muster aufgefallen, das im Code mehr als einmal vorkommt: derselbe Array-Index wird pro Iteration fünf-, sechsmal aufgelöst. Sieht banal aus. Wegcachen brachte bei mir aber spürbar Frame-Zeit zurück, und der Eingriff macht den Code obendrein lesbarer.
Das Muster
In meiner Update-Schleife für aktive Asteroiden hatte ich Code wie diesen:
FOR idx = 0 TO active_count - 1
IF aactive(active_list(idx)) THEN
INC ax(active_list(idx)), avx(active_list(idx))
INC ay(active_list(idx)), avy(active_list(idx))
INC arot(active_list(idx)), arot_speed(active_list(idx))
IF arot(active_list(idx)) >= 360 THEN INC arot(active_list(idx)), -360
' ... noch ein paar Zugriffe auf active_list(idx) ...
END IF
NEXT idx
active_list(idx) taucht da fünf-, sechsmal pro Iteration auf. Jeder Zugriff geht durch die Array-Indizierung des Interpreters. Inhaltlich ist der Wert aber während der Iteration konstant, einmal lesen würde reichen.
Einmal in eine Variable, danach immer die nutzen
Der Refactor ist trivial:
FOR idx = 0 TO active_count - 1
ast_idx = active_list(idx)
IF aactive(ast_idx) THEN
INC ax(ast_idx), avx(ast_idx)
INC ay(ast_idx), avy(ast_idx)
INC arot(ast_idx), arot_speed(ast_idx)
IF arot(ast_idx) >= 360 THEN INC arot(ast_idx), -360
' ... weiterer Code mit ast_idx ...
END IF
NEXT idx
ast_idx ist eine simple Integer-Variable. Der Interpreter liest sie pro Zugriff schneller, als jedes Mal active_list(idx) aufzulösen. Bei einer einzelnen Iteration ist das egal, bei tausenden pro Sekunde nicht mehr.
Allein dieser Eingriff hat in meiner Update- und Kollisions-Schleife ein paar Frame-Zeit-Prozent gebracht, und die Zeile liest sich nebenbei besser, weil ast_idx aussagt, was gemeint ist. In Schleifen, die nur ein paar Mal pro Frame laufen, sehe ich den Effekt nicht — den Cache-Aufwand sieht man dort genauso wenig wie den Gewinn. Ich setze das also gezielt nur dort, wo wirklich oft iteriert wird, und sonst nicht.