Alléger les script.

Répondre
Partager Rechercher
olala...

Le nwnscript est plus ou moins compilé. Le fichier qui sert se teremine par ncs.
Si tu as script test.nss le fichier compile s'apel test.ncs.

Dans le compilé on ne trouve que ce qui sert reelement a la machine c-a-d les lignes de codes et uniquement les lignes de codes. Tout ce qui est commentaire n'est absolument pas pris en compte par le compilateur.

Maintenant un bon code n'est pas un code compact sur une seule ligne. En C on peut faire ce genre de truc c'est pas forcement plus rapide et surtout c'est illisible, ca ne sert a rien...Je sais bien qu'a une epoque c'etait un peu le but du jeu en C (je crois meme me souvenirs qu'il y a avait des concours la dessus) mais cela ne sert a strictement rien en terme de langage machine ou autre...

Un code allege en nombre de ligne, ne sera pas celui qui tournera le plus vite. Un exemple tres con
Code PHP:

for (i=0i<5i++)
{
traitement

En terme de rapidite d'execution (sauf si tu as un bon compilateur mais je pense pas que cela soit le cas de celui de NWN (les bons compilateurs C le transforme en ce qui suit)) c'est moins bon que
Code PHP:

i=1;
traitement;
i++;
traitement;
i++;
traitement;
i++;
traitement
Mais il faut mettre en balance la lisibilite du programme... en gros on perds en rapidite d'execution mais on gagne en ecriture et maintenabilite...

Donc allege c'est pas cela, en fait tu n'alleges pas le nombre de ligne de code mais le nombre d'acces...
Ex:
Code PHP:

if (GetHitDic(oPC) > 5)
{
     
NewXP OldXP GetHitDice(oPC)*1000;

Cela par exemple sera a priori moins rapide que
Code PHP:

int iHD GetHitDice(oPC);
if (
iHD 5)
{
    
NewXP OldXP iHD*1000;

tu as une ligne de plus ici, mais a la place de te brancher 2 fois sur la meme fonction qui en plus te renvoie le meme resultat, ben la tu l'appel une seule fois, et un appel de fonction c'est relativement cher (svg des registres avant l'appel des fonctions, jmp a l'adress de la fonction, traitement, rejump a l'adress de depart et restauration des registres dans le meme etat qu'avant l'appel (en gros)). Donc la clairement tu as une ligne de plus mais normalement c'est plus rapide.

Mais attention il y a des contre-exemples...
Typiquement les strting conditionnal genere par le wizard
Code PHP:

int startingcondityionnal()
{
if ( 
GetHitDice(GetPcSpeaker()) <= 5)
    return 
FALSE
return TRUE;

ca non seulement c'est pourri mais en plus il reflechi a l'envers ce truc je trouve cela d'un chiant
Ca au dessous, ca fait la meme chose est c'est clairement plus rapide...
Code PHP:

int startingcondityionnal()
{
return (
GetHitDice(GetPcSpeaker()) > 5);

Citation :
Publié par Garrath
.Je sais bien qu'a une epoque c'etait un peu le but du jeu en C (je crois meme me souvenirs qu'il y a avait des concours la dessus) mais cela ne sert a strictement rien en terme de langage machine ou autre...
Qui a parlé des Tours de Hanoï récursives écrites en une ligne en C ?

Pour revenir sur ton deuxième exemple avec le GetHitDice en double, j'ai pu noter cette TRES mauvaise habitude dans bon nombre de scripts de Bioware...
Content
Ah, les grands concours d'obfuscation de C...heureusement que j'ai pas connu ça !!
Maintenant on a des trucs encore mieux pour faire en une ligne incompréhensible ce qu'on aurait pu faire en 200 lignes claires, ça s'appelle le Perl. (un excellent langage par ailleurs, mon préféré )
Sinon, vu les commentaires, je vois qu'on en est plus à mon époque, où les premiers nwnx sortaient difficilement de leur coquille et où les utilisateurs réclamaient à cor et à cri des fonctionnalités dont le manque criant rendait le NWNScript quasi-inutilisable pour du code sérieux (d'ailleurs est-ce qu'on a enfin des tableaux ? J'ai la flemme de vérifier les changelog). Mais évidemment, les conseils pour optimiser ses scripts restent les mêmes, et je plussoie absolument Jorimad, le meilleur moyen d'optimiser, c'est de réfléchir avant à son algorithme, ça peut pas faire de mal, ça fait tourner le jus de cerveau, et généralement on va plus vite dans la phase de programmation effective après.

PS : Je suis d'accord aussi sur l'utilisation de variables intermédiaires lorsqu'elles sont réutilisées, c'est effectivement une faute fréquente du débutant (honte sur Bioware d'ailleurs), facile à corriger, et qui peut apporter des vrais gains dans certains cas (quand la variable sert dans une boucle qui est beaucoup itérée). De plus, je trouve que bien nommée, cela apporte une meilleure clarté et unité au code.

--
Jedaï, Maskadeur à la (semi-)retraite
Citation :
Publié par Jedaï
PS : Je suis d'accord aussi sur l'utilisation de variables intermédiaires lorsqu'elles sont réutilisées, c'est effectivement une faute fréquente du débutant (honte sur Bioware d'ailleurs), facile à corriger, et qui peut apporter des vrais gains dans certains cas (quand la variable sert dans une boucle qui est beaucoup itérée). De plus, je trouve que bien nommée, cela apporte une meilleure clarté et unité au code.
Surtout quand c'est un accès répété à la DB dans un tri pourri utilisant une quadruple boucle imbriquée (complexité en O(n4)), cf. le tri CNR du top ten ...
Un tri en O(n^4), comment ils ont réussi ça ? Je croyais que même le tri à bulle était en O(n²) !! Et un tri à bulle, c'est pas trop compliqué à implémenter tout de même...

--
Jedaï
Bien que je devrais ouvrir un post "Alléger le module", j'ai une question subsidiaire :

On m'a répondu sur le sujet de la "mémoire cache", qu'un script inclue la totalité d'un "include" lors sa compilation. Je suppose qu'il en est de même pour l'(es) "include(s)" appelé(s) par le premier "include", etc. etc. etc...

Ne vaut-il pas mieux, pour certains scripts, de garder des "includes" à titre référentiel uniquement, les décortiquer, et insérer les fonctions utiles dans le script à compiler, directement ? En concentrant ma question sur la performance d'une telle manipulation, et en appelant également l'avis des "benchmarkers", qu'y gagnerait-on ?


Cela me vient, car en estimant qu'une bibliothèque étendue comporte 1500 à 2000 lignes de code, le script compilé appelant une telle bibliothèque inscrit donc ces 1500-2000 lignes.

Je pense particulièrement aux scripts standards, que l'on trouve dans les évènements OnCombatRoundEnd, OnHeartBeat, OnPerception, et autres, appelés fréquemment.
Oui, il est utile de décortiquer les librairies.

Voici les quelques points que j'ai notés :
- Fais l'essai sur un script qui ne fait que prendre le GetHitDice d'un personnage, sur un OnUsed par exemple. Maintenant, prends le même script, inclue le "nw_i0_generic" et applique un GetCharacterLevel à la place, fonction qui au passage ne fait rien de plus qu'un GetHitDice. Mesure la différence en temps, et en espace, compare la taille des .ncs dans le répertoire temporaire, tu vas être surprise.
- Il y a également le problème du nombre d'identifiants qui risque de bloquer au bout d'un moment à force de faire des includes d'includes, c'est ce qui m'est arrivé. J'avais d'ailleurs posté un sujet à ce propos il y a quelques semaines.
- Sur un script tout simple qui ne faisait que chercher un GetLocalString, j'avais utilisé une fonction d'une librairie (XCNR) qui est assez conséquente. Le script s'exécutait en quelques dizaines de micro-secondes. J'ai éliminé cette fonction et donc la librairie de ce script pour faire le GetLocalString à la main, le temps d'éxécution est passé à quelques micro-secondes...

Voilà ce que j'ai pu noter sur ce sujet, donc oui, évitez au possible les includes volumineux. D'ailleurs maintenant, j'élimine systématiquement les GetCharacterLevel par exemple, j'essaie d'éliminer aussi toutes les librairies standards de Bioware un peu lourde lorsque je n'ai besoin que d'une seule fonction dedans .
Le pb que tu risques d'avoir apres c'est un pb de maintenance, c-a-d que si tu dois modifier une fonction X que tu as recopie dans plein de script, ben il va falloir modifier tous les scripts ou tu as recopie cette fonction...

Alors une solution serait de cibler correctement les fonctions et de les mettres dans des includes petits... voir en poussant le truc a l'extreme de faire un include par fonction (raisonement pousse a l'extreme) mais c'est pas super beau, on va se retrouver avec des millions de scripts.

Bon le hic du system de include tel qu'il est fait la ne devrait pas bcq influer sur le temps d'execution reel d'un script, mais uniquement sur son temps de chargement en memoire et aussi sur la place prise en memoire. Mais le temps d'execution reel ne devrait pas changer bcq...
Dans le cas du GetCharacterLevel par rapport au GetHitDice la il y a 2 choses qui jouent en defaveur du GetCharacterLevel. La premiere c'est que GatCharacterLevel est dans une bibliotheque assez consequente (donc du coup chargement du compile un peu plus long), la 2nd c'est que lors de l'utilisation de GetCharacterLevel on fait une svg des registres, de la pile et un jump en plus que par l'appel direct de GetHitDice. Donc forcement la difference de temps est consequente. Mais ce n'est pas forcement lie a l'utilisation d'une grosse bibliotheque...Mais pluto a l'encaspulage sans valeur ajoutee d'une fonction par une autre

Bon une voie pour ne pas etre embete avec ce gnere de connerie d'include trop gros serait de faire en sorte que la compilation ne prenne que ce qui l'interresse (comme les compilateur C actuel (en tout cas sur unix )).
J'aimerais bien voir ce que donne le compilateur de Torlack la dessus (ou celui du PRC maintenant)


EDIT :
Citation :
- Il y a également le problème du nombre d'identifiants qui risque de bloquer au bout d'un moment à force de faire des includes d'includes, c'est ce qui m'est arrivé. J'avais d'ailleurs posté un sujet à ce propos il y a quelques semaines.
normalement ca c'est corrige dans la 1.65, en tout cas c'est que j'ai cru comprendre
Citation :
Bon une voie pour ne pas etre embete avec ce gnere de connerie d'include trop gros serait de faire en sorte que la compilation ne prenne que ce qui l'interresse (comme les compilateur C actuel (en tout cas sur unix )).
J'aimerais bien voir ce que donne le compilateur de Torlack la dessus (ou celui du PRC maintenant)
Ce serait l'idéal.
Citation :
Publié par Garrath
Dans le cas du GetCharacterLevel par rapport au GetHitDice la il y a 2 choses qui jouent en defaveur du GetCharacterLevel. La premiere c'est que GatCharacterLevel est dans une bibliotheque assez consequente (donc du coup chargement du compile un peu plus long), la 2nd c'est que lors de l'utilisation de GetCharacterLevel on fait une svg des registres, de la pile et un jump en plus que par l'appel direct de GetHitDice. Donc forcement la difference de temps est consequente. Mais ce n'est pas forcement lie a l'utilisation d'une grosse bibliotheque...Mais pluto a l'encaspulage sans valeur ajoutee d'une fonction par une autre
La différence de temps d'exécution ne semble pas s'expliquer uniquement par la gestion de la pile et des jump. Ce sont des opérations extrêmement rapides, beaucoup plus qu'un GetLocalx par exemple. Or j'imagine que GetHitDice ne fait guère plus qu'un GetLocalx. M'est avis qu'il y a plusieurs ordres de grandeur d'écart entre une manipulation de registres / jump et un GetLocalx. A vue de nez, les premiers sont de l'ordre de la nano-seconde, tandis que les seconds de la micro-seconde.

Citation :
Bon une voie pour ne pas etre embete avec ce gnere de connerie d'include trop gros serait de faire en sorte que la compilation ne prenne que ce qui l'interresse (comme les compilateur C actuel (en tout cas sur unix )).
J'ai halluciné grave lorsque je me suis rendue compte que ce n'était pas le cas .
Citation :
Publié par Sherazade
J'ai halluciné grave lorsque je me suis rendue compte que ce n'était pas le cas .
ben tu sais les compilo C ne le font pas forcement depuis bien longtemps non plus. En tout cas lorsque j'etais en IUT il ne le faisait pas (au moins celui qu'on avait)
(Le longtemps est relatif, vu que cela fait 12 ans que je suis sortis de l'IUT )
Pour publier un petit test :

test_script_1.nss :
Code PHP:

void main()
{
    
object oPlayer GetLastUsedBy();
    
int nLevel GetHitDice(oPlayer);
    
SpeakString("Niveau : "+IntToString(nLevel));

test_script_2.nss :
Code PHP:

#Include "nw_i0_generic"

void main()
{
    
object oPlayer GetLastUsedBy();
    
int nLevel GetCharacterLevel(oPlayer);
    
SpeakString("Niveau : "+IntToString(nLevel));

test_script_3.nss :
Code PHP:

int GetCharacterLevel(object oTarget)
{
    return 
GetHitDice(oTarget);
}

void main()
{
    
object oPlayer GetLastUsedBy();
    
int nLevel GetCharacterLevel(oPlayer);
    
SpeakString("Niveau : "+IntToString(nLevel));

test_script_1.ncs : 124 octets
test_script_2.ncs : 851 octets
test_script_3.ncs : 174 octets

nw_c2_default1.ncs (OnHeartBeat générique des créatures) : 94 991 octets


Effarant...


[Edit : Apparemment, sur le forum Bioware, ce problème ne suscite aucun intérêt, ou peu... Les américains forment des bons groupes, mais peu pointilleux]
En plus Deyonara ce qui serait bien c'est d'avoir le temps moyen d'execution de chacun des scripts
avec pour le fun un petit dernier
Code PHP:

void main() 

    
object oPlayer GetLastUsedBy(); 
    
SpeakString("Niveau : "+IntToString(GetHitDice(oPlayer))); 

Répondre

Connectés sur ce fil

 
1 connecté (0 membre et 1 invité) Afficher la liste détaillée des connectés