28 votes

Guide d'optimisation de LuaJIT 2

Je suis à la recherche d'un bon guide sur la façon d'optimiser le code Lua pour LuaJIT 2 . Il devrait se concentrer sur les spécificités de LJ2, comme la manière de détecter les traces qui sont compilées et celles qui ne le sont pas, etc.

Des pistes ? Une collection de liens vers des articles sur Lua ML ferait office de réponse (points bonus pour le résumé de ces liens ici).

Mise à jour : J'ai changé le texte du titre du guide de "profilage" en "optimisation", car cela a plus de sens.

31voto

Necrolis Points 17569

Mise à jour

Mike a récemment créé et publié un merveilleux profileur léger pour LuaJIT, que vous pouvez trouver à l'adresse suivante ici .

Mise à jour

Le wiki a gagné quelques pages supplémentaires dans ce domaine, en particulier celui-ci qui détaille des éléments supplémentaires non mentionnés dans la réponse originale, et qui est basée sur une poste de liste de diffusion par Mike.


LuaJIT a très récemment lancé son propre wiki et liste de diffusion et avec de telles choses viennent beaucoup, beaucoup plus de gemmes sur l'accélération du code pour LuaJIT.

Pour l'instant, le wiki est plutôt mince (mais il est toujours à la recherche de personnes pour l'enrichir), mais une excellente page a été ajoutée récemment. une liste des fonctions de la JNI . Les fonctions JNI provoquent l'abandon du JIT et le retour à l'interprète. Il est donc évident qu'il faut éviter autant que possible les fonctions JNI sur le hotpath, en particulier dans les boucles.

Quelques sujets d'intérêt de la liste de diffusion :

Et pour répéter ce qui est dit plus bas (parce que c'est utile), -jv est le meilleur outil pour l'optimisation des performances, il doit également être votre première étape pour le dépannage.


Réponse originale

Je doute que vous trouviez beaucoup de choses à ce sujet, principalement parce que LJ2 est encore en version bêta, et donc la plupart des profils sont faits naïvement car il n'y a pas de crochets de débogage pour les choses spécifiques à LJ2 comme l'enregistreur de traces.

D'un point de vue positif, le nouveau module FFI permet des appels directs aux timers haute résolution (ou aux API de profilage comme VTune/CodeAnalyst), vous pouvez profiler de cette façon, mais tout ce qui est plus nécessite des extensions au noyau JIT de LJ2, ce qui ne devrait pas être trop difficile, car le code est clair et commenté.


Un des paramètres de la ligne de commande de l'enregistreur de traces (pris à partir de ici ) :

Les commandes -jv et -jdump sont des modules d'extension écrits en Lua. Elles sont principalement utilisées pour déboguer le compilateur JIT lui-même. Pour une description de leurs options et du format de sortie, veuillez lire le document au début de leur source. Ils peuvent être trouvés dans le répertoire lib de la distribution des sources ou installés sous le répertoire jit ou installés sous le répertoire jit. Par défaut, celui-ci est /usr/local/share/luajit-2.0.0-beta8/jit sur les systèmes POSIX.

ce qui signifie que vous pouvez utiliser le code du module des commandes pour former la base d'un module de profilage pour LuaJIT 2.


Mise à jour

Avec la mise à jour de la question, il est un peu plus facile d'y répondre. Commençons donc par la source, LuaJIT.org :

Avant d'optimiser manuellement un code, il est toujours bon de vérifier les ressources de réglage de l'optimisation du JIT :

Compilation

De la Running nous pouvons voir toutes les options pour définir les paramètres du JIT, pour l'optimisation, nous nous concentrons sur la page -O option. Immédiatement, Mike nous dit que l'activation de toutes les optimisations a un impact minimal sur les performances, alors assurez-vous d'exécuter en -O3 (qui est maintenant la valeur par défaut), donc les seules options ici ayant une réelle valeur pour nous sont les seuils JIT et Trace.

Ces options sont très spécifiques au code que vous écrivez, il n'y a donc pas de "paramètres optimaux" génériques en dehors des valeurs par défaut, mais il va sans dire que si votre code comporte de nombreuses boucles, expérimentez avec le déroulement des boucles et chronométrez le temps d'exécution (mais videz le cache entre chaque exécution si vous recherchez des performances de démarrage à froid).

-jv est également utile pour éviter de connaître questions/"fallbacks qui provoquera le renflouement de l'ITC.

Le site lui-même n'offre pas beaucoup d'informations sur la façon d'écrire un code meilleur ou plus optimisé, à l'exception de quelques petites informations dans la section "Code". Tutoriel FFI :

Mise en cache des fonctions

La mise en cache des fonctions est un bon moyen d'améliorer les performances en Lua, mais il est moins important de s'y intéresser en LuaJIT, car le JIT effectue lui-même la plupart de ces optimisations. est Il est important de noter que la mise en cache des fonctions FFI C est mauvais il est préférable de mettre en cache l'espace de noms dans lequel ils résident.

Un exemple de la page :

mauvais :

local funca, funcb = ffi.C.funcb, ffi.C.funcb -- Not helpful!
local function foo(x, n)
  for i=1,n do funcb(funca(x, i), 1) end
end

bon :

local C = ffi.C          -- Instead use this!
local function foo(x, n)
  for i=1,n do C.funcb(C.funca(x, i), 1) end
end

Problèmes de performance de FFI

le site Statut La section détaille diverses constructions et opérations qui dégradent les performances du code (principalement parce qu'elles ne sont pas compilées, mais utilisent la VM de secours à la place).

Nous passons maintenant à le site source de toutes les gemmes LuaJIT, le fichier Liste de diffusion Lua :

  • Éviter les appels C et les appels Lua de la JNI dans les boucles Si vous voulez que le traceur LJ2 se mette en marche et donne un feedback utile, vous devez éviter les fonctions NYI(not yet implement) ou les appels C où le compilateur de trace ne peut pas aller. Donc si vous avez des petits appels C qui peuvent être importés dans Lua et qui sont utilisés dans des boucles, importez-les, au pire ils pourraient être '6% plus lents' que l'implémentation du compilateur C, au mieux ils seront plus rapides.

  • Utiliser les tableaux linéaires sur les ipairs Selon Mike, la méthode pairs/suivants sera toujours plus lente que les autres méthodes (il y a aussi un petit détail sur la mise en cache des symboles pour le traceur).

  • Évitez les boucles imbriquées chaque niveau d'imbrication nécessite un passage supplémentaire pour le traçage, et sera légèrement moins optimisé, évitez spécifiquement les boucles internes avec des itérations inférieures.

  • Vous pouvez utiliser des tableaux à base 0 : Mike indique ici que LuaJIT n'a pas de pénalité de performance pour les tableaux basés sur 0, contrairement à Lua standard.

  • Déclarer les locals dans la portée la plus interne possible Il n'y a pas de véritable explication, mais je crois que cela a à voir avec l'analyse de la vivacité de la SSA. Il contient également des informations intéressantes sur la façon d'éviter un trop grand nombre de locaux (ce qui casse l'analyse de la vivacité).

  • Évitez les nombreuses petites boucles Cela perturbe l'heuristique de déroulement et ralentit le code.

Petits détails :

Des outils de profilage sont disponibles pour le Lua normal, cependant, il y a un projet plus récent qui est officiellement compatible avec LuaJIT (je doute qu'il prenne en compte les fonctionnalités de LuaJIT cependant), luatrace . Le wiki Lua dispose également d'une page sur conseils d'optimisation pour Lua normal, il faudrait tester leur efficacité sous LuaJIT (la plupart de ces optimisations sont probablement déjà réalisées en interne), cependant, LuaJIT utilise toujours la GC par défaut, ce qui en fait un domaine où les gains d'optimisation manuelle peuvent encore être importants (jusqu'à ce que Mike ajoute la GC personnalisée qu'il a mentionné faire ici et là).

Le code source de LuaJIT contient quelques paramètres permettant de manipuler les éléments internes du JIT. Cependant, ces paramètres nécessitent des tests approfondis pour être adaptés à votre code spécifique, en fait, il pourrait être préférable de les éviter complètement, surtout pour ceux qui qui ne sont pas familiers avec les internes du JIT.

7voto

Geoff Leyland Points 103

Ce n'est pas tout à fait ce que vous cherchez, mais j'ai réussi à faire un peu de rétro-ingénierie sur les installations de traçage de jit.*. Ce qui suit est un peu brut, imprécis, sujet à changement et très incomplet. Je vais commencer à l'utiliser dans luatrace bientôt. Ces fonctions sont utilisées dans plusieurs des fichiers de la bibliothèque -j. dump.lua est probablement un bon point de départ.

jit.attach

Vous pouvez attacher des callbacks à un certain nombre d'événements du compilateur avec jit.attach . Le callback peut être appelé :

  • lorsqu'une fonction a été compilée en bytecode ("bc") ;
  • lorsque l'enregistrement de la trace commence ou s'arrête ("trace") ;
  • pendant l'enregistrement d'une trace ("record") ;
  • ou lorsqu'une trace sort par une sortie latérale ("texit").

Définir un rappel avec jit.attach(callback, "event") et effacer le même callback avec jit.attach(callback)

Les arguments passés à la callback dépendent de l'événement signalé :

  • "bc" : callback(func) . func est la fonction qui vient d'être enregistrée.
  • "trace" : callback(what, tr, func, pc, otr, oex)
    • what est une description de l'événement de traçage : "flush", "start", "stop", "abort". Disponible pour tous les événements.
    • tr est le numéro de la trace. Non disponible pour la chasse d'eau.
    • func est la fonction tracée. Disponible pour le démarrage et l'interruption.
    • pc est le compteur du programme - le numéro de bytecode de la fonction en cours d'enregistrement (si c'est une fonction Lua). Disponible pour le démarrage et l'abandon.
    • otr start : le numéro de la trace parent si c'est une trace latérale, abort : code d'abandon (entier) ?
    • oex start : le numéro de sortie de la trace parent, abort : raison de l'abandon (chaîne de caractères)
  • "record" : callback(tr, func, pc, depth) . Les premiers arguments sont les mêmes que pour le démarrage de la trace. depth est la profondeur de l'inlining du bytecode courant.
  • "texit" : callback(tr, ex, ngpr, nfpr) .
    • tr est le numéro de trace comme précédemment
    • ex est le numéro de sortie
    • ngpr et nfpr sont le nombre de registres à usage général et à virgule flottante qui sont actifs à la sortie.

jit.util.funcinfo(func, pc)

Lorsqu'il est adopté func et pc d'un jit.attach le rappel, jit.util.funcinfo renvoie un tableau d'informations sur la fonction, un peu comme debug.getinfo .

Les champs de la table sont :

  • linedefined quant à debug.getinfo
  • lastlinedefined quant à debug.getinfo
  • params : le nombre de paramètres que la fonction prend
  • stackslots : le nombre d'emplacements de pile utilisés par la variable locale de la fonction.
  • upvalues : le nombre de valeurs supérieures que la fonction utilise
  • bytecodes : le nombre de bytecodes de la fonction compilée.
  • gcconsts : ? ?
  • nconsts : ? ?
  • currentline quant à debug.getinfo
  • isvararg : si la fonction est une fonction de type vararg``.
  • source quant à debug.getinfo
  • loc : une chaîne décrivant la source et la ligne courante, comme "<source>:<ligne>".
  • ffid : l'id de la fonction rapide de la fonction (s'il y en a un). Dans ce cas, seulement upvalues ci-dessus et addr ci-dessous sont valables
  • addr l'adresse de la fonction (s'il ne s'agit pas d'une fonction Lua). Si c'est une fonction C plutôt qu'une fonction rapide, seulement upvalues ci-dessus est valable

2voto

tommitytom Points 41

J'ai utilisé ProFi dans le passé et l'a trouvé plutôt utile !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X