L'histoire de ce que vous n'avez pas besoin de faire pendant le développement

Prologue: Pour commencer, je parlerai du projet afin qu'il y ait des idées sur la façon dont nous avons travaillé sur le projet et pour recréer la douleur que nous avons ressentie.

En tant que développeur, je suis entré dans le projet en 2015-2016, je ne me souviens pas exactement, mais cela a fonctionné 2-3 ans plus tôt. Le projet était très populaire dans son domaine, à savoir les serveurs de jeux. Comme cela ne semblait pas étrange, mais les projets sur les serveurs de jeux sont en cours à ce jour, récemment, j'ai vu des postes vacants et j'ai travaillé un peu dans la même équipe. Étant donné que les serveurs de jeux sont construits sur un jeu déjà créé, un langage de script est donc utilisé pour le développement qui est intégré au moteur de jeu.

Nous développons presque à partir de zéro un projet sur Garry's Mod (Gmod), il est important de noter qu'au moment de la rédaction, Harry crée déjà un nouveau projet S & Box sur l'Unreal Engine. Nous sommes toujours assis sur Source.
Ce qui n'est généralement pas adapté à notre thème de serveur.
image

"De quoi ton histoire fait-elle peur?" - demandez-vous.

Nous avons un thème fort pour le serveur de jeu, à savoir "Stalker" et même avec des éléments de jeux de rôle (RP), la question se pose immédiatement - "Comment tout le monde peut-il implémenter cela sur un serveur?".

Étant donné que le moteur source est ancien (la version 2013 est également utilisée dans Gmod 32 bits), vous ne ferez pas de grosses cartes, il y a de petites restrictions sur le nombre d'entité, de maillage et bien plus encore.
Qui a travaillé sur le moteur comprendra.
Il s'avère que la tâche est généralement impossible, faire un harceleur multijoueur propre avec des quêtes, des éléments RPG à partir de l'original lui-même et de préférence une petite intrigue.

Tout d'abord, l'orthographe initiale a été difficile (de nombreuses actions de la catégorie: lancer un objet, soulever un objet ont été écrites à partir de zéro), en espérant qu'il serait plus facile de continuer, mais les exigences ont augmenté. La mécanique du jeu était prête, il ne restait plus qu'à faire de l'intelligence, de la mise à niveau et toutes sortes de choses. En général, tous transférés comme ils le pouvaient.

image

Les problèmes ont déjà commencé lors des travaux de la première version, à savoir (retards, retards du serveur).

Il semble qu'un serveur puissant puisse traiter calmement les demandes et conserver l'intégralité du mode de jeu.

Description simple du mode de jeu
Il s'agit du nom d'un ensemble de scripts écrits pour décrire la mécanique du serveur lui-même
Par exemple: nous voulons le thème des «Royal Battles» désormais populaires, ce qui signifie que le nom devrait également correspondre à la mécanique du jeu. "L'apparition de joueurs dans l'avion, vous pouvez ramasser des choses, les joueurs peuvent communiquer, vous ne pouvez pas porter plus d'un casque, etc." - tout cela est décrit par la mécanique du jeu sur le serveur.

Les retards étaient du côté du serveur en raison du grand nombre de joueurs, car un joueur mange beaucoup de RAM d'environ 80-120 Mo (sans compter les éléments de l'inventaire, les compétences, etc.), et du côté client, il y a eu une forte diminution FPS

La puissance du processeur n'était pas suffisante pour traiter la physique, il était nécessaire d'utiliser moins d'objets ayant des propriétés physiques.

De plus, nos scripts auto-écrits n'étaient pas du tout optimisés.

image

Tout d'abord, bien sûr, nous avons lu des articles sur l'optimisation dans Lua. Même il est venu au suicide qu'ils voulaient écrire des DLL en C ++, mais le problème est survenu lors du téléchargement de la DLL du serveur vers les clients. En utilisant C ++ pour une DLL, vous pouvez écrire un programme qui intercepte silencieusement des données; les développeurs de Gmod ont ajouté une extension aux exceptions pour le téléchargement par les clients (sécurité, bien qu'en fait cela ne se soit jamais produit). Bien que ce soit pratique et que Gmod devienne plus flexible, mais plus dangereux.

Ensuite, nous avons regardé le profileur (heureusement, des gens intelligents l'ont écrit) et il y avait une horreur dans les fonctions, on a remarqué que déjà initialement il y avait des fonctions très lentes dans la bibliothèque du moteur Gmod.

Si vous avez essayé d'écrire dans Gmod, vous savez très bien qu'il existe une bibliothèque intégrée appelée math.

Et les fonctions les plus lentes sont bien sûr math.Clamp et math.Round.

En fouillant dans le code des gens, on a remarqué que les fonctions étaient lancées dans des directions différentes, elle est utilisée presque partout, mais de façon incorrecte!

Passons à la pratique. Par exemple, nous voulons arrondir les coordonnées du vecteur de position pour déplacer l'entité (par exemple, un joueur).

local x = 12.5 local y = 14.9122133 local z = 12.111 LocalPlayer():SetPos( Vector( Math.Round(x), Math.Round(y), Math.Round(z) ) 

3 fonctions d'arrondi complexes, mais rien de grave, sauf bien sûr en boucle et peu utilisées, mais Clamp est encore plus difficile.

Le code suivant est souvent utilisé dans les projets et personne ne veut rien changer.

 self:setLocalVar("hunger", math.Clamp(current + 1, 0, 100)) 

Par exemple, self pointe vers l'objet d'un joueur et il a une variable locale que nous avons inventée, qui lors de la réinitialisation sur le serveur est réinitialisée, math.Clamp essentiellement comme une boucle fait une affectation fluide, ils aiment faire une interface fluide sur Clamp.

Des problèmes surviennent quand il fonctionne sur chaque joueur qui visite le serveur. Un cas rare, mais si 5-15 (dépend immédiatement de la configuration du serveur) entre dans le serveur à un moment donné et que cette petite et simple fonction commence à fonctionner pour tout le monde, alors le serveur aura de bons retards CPU. Encore pire si math.Clamp est dans une boucle.

L'optimisation est en fait très simple, vous localisez des fonctions fortement chargées. Cela semble primitif, mais en 3 modes de jeu et dans de nombreux modules, j'ai vu ce code lent.

Si vous devez obtenir la valeur et l'utiliser à l'avenir, vous n'avez pas besoin de l'obtenir à nouveau si elle ne change pas. Après tout, un joueur entrant dans le serveur recevra de toute façon une faim égale à 100, donc ce code est beaucoup plus rapide.

 local value = math.Clamp(current + 1, 0, 100) self:setLocalVar("hunger", value) 

Tout va bien, ils ont commencé à regarder plus loin comment cela fonctionne. En conséquence, nous avons démarré une manie pour tout optimiser.

Nous avons remarqué que la norme pour la boucle est lente et nous avons décidé de créer notre propre vélo qui sera plus rapide (nous n'avons pas oublié le blackjack) et le jeu a commencé.

image

Spoiler
Nous avons même réussi à faire la boucle la plus rapide sur Lua Gmod, mais à condition qu'il y ait plus de 100 éléments.

A en juger par le temps passé sur notre cycle et son utilisation dans le code, nous avons essayé en vain de le faire car il n'a trouvé d'application que dans le spawn sur la carte des anomalies après les avoir lancées et supprimées.
Et donc pour le code. Par exemple, vous devez trouver toutes les entités avec un nom au début de l'anom, nous avons des anomalies dans ce nom de classe.

Voici le script normal pour Lua Gmod:

 local anomtable = ents.FindByClass("anom_*") for k, v in pairs(anomtable) do v:Remove() end 

Voici pour le fumeur:

Il est immédiatement évident qu’un tel code g * sera évidemment plus lent que le standard "for in pairs", mais il s’est avéré que non.

 local b, key = ents.FindByClass("anom_*"), nil repeat key = next(b, key) b[key]:Remove() until key != nil 


Pour analyser pleinement ces options de boucle, vous devez les traduire en un script Lua normal.
Par exemple, anomtable aura 5 éléments.
La suppression est remplacée par l'ajout habituel. L'essentiel est de voir la différence dans le nombre d'instructions entre les deux options pour implémenter la boucle for.

Cycle de vanille:

 local anomtable = { 1, 2, 3, 4, 5 } for k, v in pairs(anomtable) do v = v + 1 end 

Notre est génial:

 local b, key = { 1, 2, 3, 4, 5 }, nil repeat key = next(b, key) b[key] = b[key] + 1 until key ~= nil 

Regardons le code de l'interpréteur ( comme un assembleur, il n'est pas recommandé de regarder sous un spoiler en tant que programmeur de haut niveau ).

Au cas où, supprimez les jones des écrans. Ai-je prévenu.

Démonteur cycle vanille
 ; Name: for1.lua ; Defined at line: 0 ; #Upvalues: 0 ; #Parameters: 0 ; Is_vararg: 2 ; Max Stack Size: 7 1 [-]: NEWTABLE R0 5 0 ; R0 := {} 2 [-]: LOADK R1 K0 ; R1 := 1 3 [-]: LOADK R2 K1 ; R2 := 2 4 [-]: LOADK R3 K2 ; R3 := 3 5 [-]: LOADK R4 K3 ; R4 := 4 6 [-]: LOADK R5 K4 ; R5 := 5 7 [-]: SETLIST R0 5 1 ; R0[(1-1)*FPF+i] := R(0+i), 1 <= i <= 5 8 [-]: GETGLOBAL R1 K5 ; R1 := pairs 9 [-]: MOVE R2 R0 ; R2 := R0 10 [-]: CALL R1 2 4 ; R1,R2,R3 := R1(R2) 11 [-]: JMP 13 ; PC := 13 12 [-]: ADD R5 R5 K0 ; R5 := R5 + 1 13 [-]: TFORLOOP R1 2 ; R4,R5 := R1(R2,R3); if R4 ~= nil then begin PC = 12; R3 := R4 end 14 [-]: JMP 12 ; PC := 12 15 [-]: RETURN R0 1 ; return 


Démonteur de cycle de vélo
 ; Name: for2.lua ; Defined at line: 0 ; #Upvalues: 0 ; #Parameters: 0 ; Is_vararg: 2 ; Max Stack Size: 6 1 [-]: NEWTABLE R0 5 0 ; R0 := {} 2 [-]: LOADK R1 K0 ; R1 := 1 3 [-]: LOADK R2 K1 ; R2 := 2 4 [-]: LOADK R3 K2 ; R3 := 3 5 [-]: LOADK R4 K3 ; R4 := 4 6 [-]: LOADK R5 K4 ; R5 := 5 7 [-]: SETLIST R0 5 1 ; R0[(1-1)*FPF+i] := R(0+i), 1 <= i <= 5 8 [-]: LOADNIL R1 R1 ; R1 := nil 9 [-]: GETGLOBAL R2 K5 ; R2 := next 10 [-]: MOVE R3 R0 ; R3 := R0 11 [-]: MOVE R4 R1 ; R4 := R1 12 [-]: CALL R2 3 2 ; R2 := R2(R3,R4) 13 [-]: MOVE R1 R2 ; R1 := R2 14 [-]: GETTABLE R2 R0 R1 ; R2 := R0[R1] 15 [-]: ADD R2 R2 K0 ; R2 := R2 + 1 16 [-]: SETTABLE R0 R1 R2 ; R0[R1] := R2 17 [-]: EQ 1 R1 K6 ; if R1 == nil then PC := 9 18 [-]: JMP 9 ; PC := 9 19 [-]: RETURN R0 1 ; return 


Une personne inexpérimentée dira simplement qu'un cycle régulier est plus rapide car il y a moins d'instructions (15 vs 19).

Mais il ne faut pas oublier que chaque instruction de l'interpréteur a des cycles de processeur.
A en juger par le code du désassembleur dans la première boucle, il y a une instruction forloop écrite à l'avance pour travailler avec le tableau, le tableau est chargé en mémoire, il devient global, nous sautons par-dessus les éléments et ajoutons une constante.

Dans la deuxième variante, la méthode est différente, qui est plus basée sur la mémoire, elle reçoit une table, change un élément, définit une table, vérifie zéro et appelle à nouveau.
Notre deuxième cycle est rapide, car il y a trop de conditions et d'actions dans une instruction (R4, R5: = R1 (R2, R3); si R4 ~ = nil alors commence PC = 12; R3: = fin R4) à cause de cela il mange beaucoup , mange des cycles d'horloge CPU pour l'exécution, le passé est encore plus lié à la mémoire.

L'instruction forloop avec un grand nombre d'éléments s'abandonne à notre cycle sur la vitesse de passage de tous les éléments. Cela est dû au fait que l'adressage direct à l'adresse est plus rapide, moins que les goodies des paires. (Et nous n'avons aucun déni)
En général, en secret, toute utilisation de la négation dans le code le ralentit; cela a déjà été testé par des tests et du temps. La logique négative fonctionnera plus lentement car le processeur ALU dispose d'une unité de calcul distincte «onduleur», pour l'opérande unaire (pas,!) Pour fonctionner, vous devez accéder à l'onduleur et cela prendra du temps supplémentaire.
Conclusion: Tout ce qui est standard n'est pas toujours meilleur, vos vélos peuvent être utiles, mais là encore sur un vrai projet, vous ne devriez pas les inventer si la vitesse de sortie est importante pour vous. En conséquence, nous avons achevé le développement complet de 2014 à nos jours, une sorte de "attente". Bien qu'il ressemble à un serveur de jeu standard installé en 1 jour et entièrement configuré pour le jeu en 2 jours, vous devez être en mesure d'introduire quelque chose de nouveau.

Ce projet à long terme a encore vu la deuxième version de lui-même où il y a beaucoup d'optimisations dans le code, mais je parlerai d'autres optimisations dans les articles suivants. Support avec critique ou commentaire, corrigez si je me trompe.

Source: https://habr.com/ru/post/fr431786/


All Articles