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.