Le topic de SPH étant devenu assez "lourd", je vais prendre pour exemple le topic Attention à WindowEvent de notre ami MicroDevWeb.
Dans ce topic, on peut lire ceci:
Ce code occupe le CPU à 100% (un core à 100% = 25% d'un quad-core), ce qui semble un gouffre à CPU pour tout le monde, et anormal...MicroDevWeb a écrit : Lorsque l'on développe un jeux par exemple, on évite le WaitWindowEvent() qui bloque la boucle jusqu'au retour d'un événement Window
Si vous lancez ce code (qui ne fait absolument rien) et que vous ouvrer (CTRL+ALT+Delete) votre gestionnaire de tache (window) aller sur l'onglet Processus et chercher Purebasic_compilation.... vous verrez que dans "Processeur" votre processeur est occupé (chez moi du moins) à 25%.Code : Tout sélectionner
OpenWindow(0,0,0,800,600,"tetes",#PB_Window_SystemMenu) Repeat Repeat Event=WindowEvent() If Event=#PB_Event_CloseWindow End EndIf Until Event=0 ;Traitement du jeux ForEver
Pourtant... Occuper le CPU à 100%, c'est TRES EXACTEMENT ce que DEMANDE le programme tapé ci dessus. VOUS avez demandé à ce que l'ordinateur bloque le CPU à 100%. Pourquoi ça? Tout l'amalgame vient de la parenté de WindowsEvent() avec WaitWindowsEvent(). WaitWindowsEvent() ATTENDS qu'il se passe quelque chose pour continuer. Tant qu'il ne se passe rien, le programme attend, et le CPU reste à 0%. Mais comme le dit MicroDevWeb, ce n'est pas possible pour un jeu, on ne doit pas bloquer le code.
Ce code ne fait pas absolument rien. Au contraire, WindowEvent() regarde si il se passe quelque chose dans la fenêtre, en boucle, et le plus rapidement possible, des milliers de fois par seconde, si il se passe quelque chose. Ca veut par définition dire en bloquant le CPU à 100%.
Voici donc la solution proposée par notre ami:
Code : Tout sélectionner
OpenWindow(0,0,0,800,600,"tetes",#PB_Window_SystemMenu)
Repeat
Delay(1)
Repeat
Event=WindowEvent()
If Event=#PB_Event_CloseWindow
End
EndIf
Until Event=0
;Traitement du jeux
ForEver
Maintenant, essayons de benchmarker ces 2 programmes. C'est vrai, Delay(1), c'est le plus petit Delay() qu'on puisse ajouter, ça ne doit pas être beaucoup plus lent. Voici un petit programme qui exécute chacun de ces 2 programmes 10 secondes, et compare leur rapidité.
Code : Tout sélectionner
OpenWindow(0,0,0,800,600,"tetes",#PB_Window_SystemMenu)
;boucle sans Delay()
count1=0
starttime=ElapsedMilliseconds()
While ElapsedMilliseconds()-starttime<10000
Repeat
Event=WindowEvent()
If Event=#PB_Event_CloseWindow
End
EndIf
Until Event=0
;Traitement du jeux
count1+1
Wend
;boucle avec Delay(1)
count2=0
starttime=ElapsedMilliseconds()
While ElapsedMilliseconds()-starttime<10000
Delay(1)
Repeat
Event=WindowEvent()
If Event=#PB_Event_CloseWindow
End
EndIf
Until Event=0
;Traitement du jeux
count2+1
Wend
MessageRequester("", "la première boucle s'est exécutée "+Str(count1)+" fois en 10 secondes")
MessageRequester("", "la deuxième boucle s'est exécutée "+Str(count2)+" fois en 10 secondes")
MessageRequester("", "la première boucle est "+Str(count1/count2)+" fois plus rapide que la deuxième")
"la première boucle s'est exécutée 42495326 fois en 10 secondes"
"la deuxième boucle s'est exécutée 9983 fois en 10 secondes"
"la première boucle est 4256 fois plus rapide que la deuxième"
HEIN? 4256 fois pour un simple Delay(1) qui est le plus petit Delay() possible? Mais comment est-ce possible?
C'est simple. Delay(1) attends 1 milliseconde. A chaque fois que votre boucle principale recommence, votre ordinateur attendra 1/1000 de seconde, pendant lequel il ne fera RIEN.
Vous noterez un deuxième souci si vous lancez ce programme chez vous. Votre première valeur est sans doute très différente de la mienne, mais pour la seconde, vous aurez aussi à peine moins de 10.000. Que vous ayez un Pentium, un i3, un i7, vous aurez TOUS à peu prêt 10.000. Le Delay(1) rend notre programme incapable de tirer parti de la puissance de différents PC. C'est parce que le Delay(1), en attendant 1/1000 de seconde, limite l'exécution de la boucle à 1000 fois pas seconde.
Les plus incisif me feront remarquer que de toute façon, pour la boucle de notre ami MicroDevWeb, les résultats restent de toute façon bien assez rapide, et ils auront raison. Maintenant, puisque MicroDevWeb parle de jeu, prenons cet exemple:
Un jeu, c'est idéalement 60 images par seconde. Donc, on va partir du principe que notre boucle principale va s'exécuter 60 fois par seconde. La encore les gens incisifs me diront la chose suivante:
"Si le Delay(1) limite la boucle principale à un maximum de 1000 fois par seconde, alors ce n'est pas grave, car 1000 fois c'est bien assez pour un programme qui a besoin de 60 fois par seconde".
Oui. Mais non. Pour que votre jeu fonctionne à 60 images par seconde, il faut donc que votre ordinateur calcule chaque image en 1000/60 = 16.6 millisecondes. Sur ces 16.6ms disponibles, votre Delay(1) consomme déjà 1ms. Il ne reste à votre ordinateur que 15.6ms. Si vous avez un ordinateur très rapide, ce ne sera pas un problème.
Mais imaginons que votre ordinateur soit lent ou le jeu complexe. Imaginons que vous ayez un ordinateur qui a besoin de 16.5ms pour calculer une image, soit prêt de 100% de sa puissance! Que va t'il se passer? Après 16.5ms de calcul, Delay(1) va venir ajouter 1ms d'ATTENTE supplémentaire! Au final, votre boucle aura mis 17.5ms à s'exécuter! Que va t'on constater?:
-Le jeu ne fonctionnera plus à 60 images par secondes. Il va commencer à saccader.
-Paradoxalement, votre CPU ne montera pas à 100%!!! Car le Delay(1) va lui laisser du temps de repos!
Vous avez donc un ordinateur qui ne fonctionne pas à 100%, et un jeu qui pourtant saccade. Quel paradoxe...
Et encore! Dans l'exemple de notre ami MicroDevWeb, le Delay() n'est que de 1, et il est juste après le premier repeat. Sur d'autre topic, certains conseillent de mettre des Delay() supérieur, 2... 4... 16!!! et les mettent carrément dans la deuxième boucle! Voyons ça.
Dans le topic #WM_centreBUTTONDOWN ? notre ami Ollivier suggère d'utiliser ceci:
Code : Tout sélectionner
Repeat
Repeat
Ev = WindowEvent()
If Ev
Delay(16)
; Traitement évènement
Endif
Until Not Ev
Until Quit
Alors que faire pour n'être ni bloqué à 100%, ni perdre en performances?
La théorie est simple. La pratique encore plus grâce à PureBasic.
La théorie, c'est qu'on doit arrêter son CPU pour un temps variable et adapté à ses besoins. On ne doit surtout pas mettre un Delay(). Delay() est invariable! Il ralentira votre programme même si celui-ci est déjà trop lent! Et rassurez vous, pour celà, votre Os et PureBasic sont merveilleusement bien fichus!
Pour votre jeu, tout tiens dans FlipBuffers(). FlipBuffers() est une fonction extrêmement intelligente qui ne s'exécute pas plus de 60 fois par secondes, en se synchronisant avec votre écran. Ainsi donc, sans rentrer dans les détails techniques, FlipBuffers() s'adaptera au temps de calcul de votre image et fera en quelque sorte un "delay" automatiquement adapté!
Si votre image à pris 3ms à être calculée, FlipBuffers() fera virtuellement un "Delay(13.6)". Si votre image à nécessité 15.8 de calcul, alors FlipBuffers() remplira le rôle d'un "Delay(0. 8 )". Et votre jeu tournera TOUJOURS à 60 fps, et votre charge CPU ira parfaitement de 0% à 100% en fonction de la complexité de votre jeu.
Notre ami MicroDevWeb n'avait qu'à transformer son code ainsi pour régler efficacement son problème:
Code : Tout sélectionner
InitSprite()
OpenWindow(0,0,0,800,600,"tetes",#PB_Window_SystemMenu)
OpenWindowedScreen (WindowID(0) ,0 , 0, WindowWidth(0), WindowHeight(0),0, 0, 0, #PB_Screen_WaitSynchronization)
Repeat
Repeat
Event=WindowEvent()
If Event=#PB_Event_CloseWindow
End
EndIf
Until Event=0
;Traitement du jeux
FlipBuffers()
ForEver
Et de toute façon, dans 99% des jeux, vous aurez besoin d'un FlipBuffers()
Si votre application n'est pas un jeu, mais par exemple une interface graphique où rien ne se passe tant que vous ne faites rien, alors il faut sans hésiter revenir à WaitWindowEvent() qui se chargement de mettre votre programme en pause.
La règle générale
On ne Delay() JAMAIS, JAMAIS, JAMAIS un programme. On doit trouver le moyen de l'arrêter temporairement tout en pouvant le reprendre INSTANTANEMENT lorsqu'il le faut. Delay() ne répond pas à ce deuxième critère.
Si vous êtes confrontés à un programme qui vous semble charger votre CPU à 100% inutilement, il ne faut PAS mettre de Delay(), il vous faut trouver un moyen de mettre intelligement votre programme en attente. FlipBuffers() et WaitWindowsEvent() résoudront 99% des problèmes des gens, mais les Os disposent de très nombreuses fonctions vouées à ce but!
L'argument du Delay() pour le multitâche
On s'en fout, on s'en fout, et ON S'EN FOUT! C'est le problème de l'Os! Les Os sont suffisement évolués pour répartir à 50/50 deux tâches consommant 100% du CPU! On ne peut PAS limiter les performances de son application pour un éventuel multitâche alors qu'elle sera la plupart du temps la seule à fonctionner! Et de toute façon, sâchez que le Delay() n'aidera pas l'Os pour un sous, mais je n'ai ni l'envie ni le temps de vous expliquer pourquoi! Et c'est parce que vous n'avez pas envie de vous emmerder avec ces détails techniques que vous codez en Basic!
Pas de Delay() pour le multitâche! Point!
J'invite les derniers septiques à me mettre des exemples de code fonctionnels où un delay() seront parfaitement obligatoire, et nous verront ce qu'il y a à en dire...
Merci de m'avoir lu
