WordPress-Tuning – Text-Domains nur bei Bedarf laden


Was mir wirklich Spaß macht beim Programmieren ist Optimieren – für mehr Geschwindigkeit oder weniger Speicherverbrauch. Und das macht selbstverständlich auch nicht vor WordPress halt. Deshalb habe ich mit die Tage xDebug installiert und ein wenig mit dessen Profiler WordPress genauer angeschaut.

Das erste Ergebnis, das sofort ins Auge stach: Über 25% der Ausführungszeit (beim Darstellen der Startseite vom GCP) wurde von load_textdomain verbraucht. Ein übersetztes WordPress ist also deutlich langsamer, als ein nicht übersetztes. Also habe ich mir das ganze mal genauer angesehen.

Bei jedem Durchlauf werden alle Übersetzungen geladen und allein die Standard-Übersetzungstabelle umfasst über 3000 Texte (z.B. für Deutsch in der Datei de_DE.po). Und es werden alle Übersetzungstexte jedes mal komplett geladen und geparst, egal, ob sie benötigt werden, oder nicht. Blöd dabei, dass die meisten Texte nur im Backend benötigt werden. Und installierte, mehrsprachige Plugins verschlimmern das ganze nur weiter.

Als einen ersten Schritt konnte ich einbauen, dass Text-Domains nur bei Bedarf geladen werden, also erst beim  Ersten versuch einen Text aus dieser zu übersetzen. Das ganze lässt sich relativ einfach sogar als Plugin realisieren, indem man sich in den Filter override_load_textdomain einklinkt.

class MO_onDemand extends MO {
  var $loaded = false;
  var $mofile = NULL;
  var $domain = NULL;

  function loadOnDemand () {
    global $l10n;

    if (!$this->loaded) {
      $mo=new MO();
      if ( !$mo->import_from_file( $this->mofile ) ) 
        return false;
      $l10n[$this->domain]=$mo;
      $this->loaded=true;
      return $mo;
    } else {
      return $l10n[$this->domain];
    }
  }

  function translate_entry(&$entry) {
    $mo=$this->loadOnDemand();
    return $mo->translate_entry(&$entry);
  }

  function translate($singular, $context=null) {
    $mo=$this->loadOnDemand();
    return $mo->translate($singular, $context);
  }

  function select_plural_form($count) {
    $mo=$this->loadOnDemand();
    return $mo->select_plural_form($count);
  }

  function get_plural_forms_count() {
    $mo=$this->loadOnDemand();
    return $mo->get_plural_forms_count();
  }

  function translate_plural($singular, $plural, $count, $context = null) {
    $mo=loadOnDemand();
    return $mo->translate_plural($singular, $plural, $count, $context);
  }
}

function load_textdomain_override( $retval, $domain, $mofile ) {
  global $l10n;
  do_action( 'load_textdomain', $domain, $mofile );
  $mofile = apply_filters( 'load_textdomain_mofile', $mofile, $domain );
  if ( ! is_readable( $mofile ) ) 
    return false;

  $mo = new MO_onDemand();
  $mo->mofile=$mofile;
  $mo->domain=$domain;

  if ( isset( $l10n[$domain] ) )
    $mo->merge_with( $l10n[$domain] );

  $l10n[$domain] = &$mo;

  return true;
}

add_filter('override_load_textdomain', 'load_textdomain_override',10,3);

Ein wichtiger Punkt fehlt hier noch: Die Funktion merge_with der Basisklasse Translations müsste auf jeden Fall noch überschrieben werden, so dass ggf. mehrere Dateien gemerkt werden. Die neue Klasse muss eigentlich auch nicht von MO abgeleitet werden, da sie beim ersten Zugriff auf eine Übersetzung sich selbst im globalen l10n-Array durch eine Instanz von MO ersetzt (was aus Geschwindigkeitsgründen so ist).

Allein dieses „Plugin“ hat die Ausführungszeit für das Anzeigen der Startseite vom GCP (lokale Installation mit aktivem Debugger) von 9,5 auf 7,8 Sekunden beschleunigt. Nur weil die Text-Domains der Plugins nicht geladen werden (da sie auf der Startseite nicht verwendet werden). Der größte Brocken, die Default-Text-Domain ist aber noch da und braucht noch immer fast 2,5 Sekunden zum Laden.