Prolog: Zunächst werde ich über das Projekt sprechen, damit es Ideen darüber gibt, wie wir an dem Projekt gearbeitet haben und um den Schmerz, den wir fühlten, wieder herzustellen.
Als Entwickler habe ich 2015-2016 an dem Projekt teilgenommen. Ich erinnere mich nicht genau, aber es hat 2-3 Jahre zuvor funktioniert. Das Projekt war auf seinem Gebiet sehr beliebt, nämlich auf Spieleservern. Wie seltsam es nicht klang, aber Projekte auf Spieleservern laufen bis heute. Vor kurzem habe ich offene Stellen gesehen und ein bisschen im selben Team gearbeitet. Da Spieleserver auf einem bereits erstellten Spiel basieren, wird für die Entwicklung eine Skriptsprache verwendet, die in die Spiel-Engine integriert ist.
Wir entwickeln fast von Grund auf ein Projekt auf Garrys Mod (Gmod). Es ist wichtig zu beachten, dass Harry zum Zeitpunkt des Schreibens bereits ein neues S & Box-Projekt auf der Unreal Engine erstellt. Wir sitzen immer noch auf Source.
Welches ist in der Regel nicht für unser Server-Thema geeignet.

"Worüber ist deine Geschichte beängstigend?" - Du fragst.
Wir haben ein starkes Thema für den Spieleserver, nämlich "Stalker", und selbst bei Elementen von Rollenspielen (RP) stellt sich sofort die Frage: "Wie kann jeder dies auf einem Server implementieren?".
Da die Source-Engine alt ist (die Version 2013 wird auch in Gmod 32-Bit verwendet), werden keine großen Karten erstellt. Die Anzahl der Entitäten, Netze und vieles mehr unterliegt kleinen Einschränkungen.
Wer am Motor gearbeitet hat, wird verstehen.
Es stellt sich heraus, dass es im Allgemeinen unmöglich ist, einen sauberen Multiplayer-Stalker mit Quests, RPG-Elementen aus dem Original selbst und vorzugsweise einer kleinen Handlung zu erstellen.
Zuallererst war die anfängliche Rechtschreibung schwierig (viele Aktionen aus der Kategorie: Werfen eines Objekts, Heben eines Objekts wurden von Grund auf neu geschrieben), in der Hoffnung, dass es einfacher sein würde, fortzufahren, aber die Anforderungen wuchsen. Die Mechanik des Spiels war fertig, alles, was übrig blieb, war Intelligenz, Upgrade und alles Mögliche zu tun. Im Allgemeinen werden alle übertragen, wie sie konnten.

Die Probleme begannen bereits während der Arbeit der ersten Version, nämlich (Verzögerungen, Serververzögerungen).
Es scheint, als könnte ein leistungsstarker Server Anfragen ruhig verarbeiten und den gesamten Spielmodus halten.
Einfache Beschreibung des SpielmodusDies ist der Name einer Reihe von Skripten, die geschrieben wurden, um die Mechanik des Servers selbst zu beschreiben
Zum Beispiel: Wir wollen das Thema der mittlerweile beliebten „Royal Battles“, was bedeutet, dass der Name auch der Spielmechanik entsprechen sollte. "Wenn Spieler im Flugzeug erscheinen, können Sie Dinge aufheben, Spieler können kommunizieren, Sie können nicht mehr als 1 Helm tragen usw." - All dies wird durch die Spielmechanik auf dem Server beschrieben.
Die Verzögerungen waren auf der Serverseite aufgrund der großen Anzahl von Spielern, da ein Spieler viel RAM von etwa 80-120 MB verbraucht (ohne Berücksichtigung von Elementen im Inventar, Fähigkeiten usw.), und auf der Clientseite gab es einen starken Rückgang FPS
Die CPU-Leistung reichte für die Verarbeitung der Physik nicht aus, es mussten weniger Objekte mit weniger physikalischen Eigenschaften verwendet werden.
Dazu kamen auch unsere selbst geschriebenen Skripte, die überhaupt nicht optimiert wurden.

Zunächst lesen wir natürlich Artikel zur Optimierung in Lua. Es kam sogar zum
Selbstmord, dass sie DLLs in C ++ schreiben wollten, aber das Problem trat beim Herunterladen der DLL vom Server auf Clients auf. Mit C ++ für eine DLL können Sie ein Programm schreiben, das Daten leise abfängt. Gmod-Entwickler haben den Ausnahmen für das Herunterladen durch Clients eine Erweiterung hinzugefügt (Sicherheit, obwohl dies tatsächlich nie geschehen ist). Es wäre zwar praktisch und Gmod würde flexibler, aber gefährlicher.
Als nächstes haben wir uns den Profiler angesehen (zum Glück haben ihn kluge Leute geschrieben) und es gab einen Horror in den Funktionen. Es wurde festgestellt, dass es bereits anfangs sehr langsame Funktionen in der Gmod-Engine-Bibliothek gab.
Wenn Sie versucht haben, in Gmod zu schreiben, wissen Sie genau, dass eine Bibliothek namens math integriert ist.
Und die langsamsten Funktionen sind natürlich math.Clamp und math.Round.
Beim Durchsuchen des Personencodes wurde festgestellt, dass die Funktionen in verschiedene Richtungen geworfen wurden. Sie werden fast überall verwendet, aber falsch!
Lass uns üben. Zum Beispiel möchten wir die Koordinaten des Positionsvektors runden, um das Objekt zu bewegen (zum Beispiel einen Spieler).
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 komplexe Rundungsfunktionen, aber nichts Ernstes, es sei denn natürlich in einer Schleife und nicht oft verwendet, aber Clamp ist noch schwieriger.
Der folgende Code wird häufig in Projekten verwendet und niemand möchte etwas ändern.
self:setLocalVar("hunger", math.Clamp(current + 1, 0, 100))
Zum Beispiel zeigt self auf das Objekt eines Spielers und es hat eine lokale Variable, die wir erfunden haben. Wenn das Zurücksetzen auf den Server zurückgesetzt wird, ist math.Clamp im Wesentlichen als Schleife eine reibungslose Zuordnung, sie möchten eine reibungslose Schnittstelle auf Clamp erstellen.
Probleme treten auf, wenn es bei jedem Spieler funktioniert, der den Server besucht. Ein seltener Fall, aber wenn 5-15 (abhängig von der Serverkonfiguration) zu einem bestimmten Zeitpunkt in den Server eintreten und diese kleine und einfache Funktion für alle funktioniert, hat der Server gute CPU-Verzögerungen. Noch schlimmer, wenn math.Clamp in einer Schleife ist.
Die Optimierung ist eigentlich sehr einfach, Sie lokalisieren stark belastete Funktionen. Es scheint primitiv, aber in 3 Spielmodi und vielen Add-Ons habe ich diesen langsamen Code gesehen.
Wenn Sie den Wert abrufen und in Zukunft verwenden müssen, müssen Sie ihn nicht erneut abrufen, wenn er sich nicht ändert. Immerhin bekommt ein Spieler, der den Server betritt, auf jeden Fall einen Hunger von 100, so dass dieser Code um ein Vielfaches schneller ist.
local value = math.Clamp(current + 1, 0, 100) self:setLocalVar("hunger", value)
Alles ist gut, sie begannen weiter zu untersuchen, wie es funktioniert. Infolgedessen haben wir eine Manie gestartet, um alles zu optimieren.
Wir haben festgestellt, dass der Standard für Loops langsam ist und wir haben uns entschlossen, ein eigenes Fahrrad zu entwickeln, das schneller ist (wir haben Blackjack nicht vergessen), und dann hat das Spiel begonnen.

SpoilerWir haben es sogar geschafft, die schnellste Schleife auf Lua Gmod zu machen, aber unter der Bedingung, dass es mehr als 100 Elemente geben sollte.
Gemessen an der Zeit, die wir für unseren Zyklus aufgewendet haben, und seiner Verwendung im Code haben wir vergeblich versucht, dies zu tun, da er nur im Spawn auf der Karte der Anomalien Anwendung fand, nachdem er sie geworfen und gelöscht hatte.
Und so zum Code. Zum Beispiel müssen Sie alle Entitäten mit einem Namen am Anfang von anom finden. Wir haben Anomalien in diesem Klassennamen.
Hier ist das normale Skript für Lua Gmod:
local anomtable = ents.FindByClass("anom_*") for k, v in pairs(anomtable) do v:Remove() end
Hier ist für den Raucher:
Es ist sofort klar, dass ein solcher
g * -Code
offensichtlich langsamer als der Standard "für Paare" ist, aber wie sich herausstellte, nicht.
local b, key = ents.FindByClass("anom_*"), nil repeat key = next(b, key) b[key]:Remove() until key != nil
Um diese Schleifenoptionen vollständig zu analysieren, müssen Sie sie in ein reguläres Lua-Skript übersetzen.
Zum Beispiel hat eine Tabelle 5 Elemente.
Die Entfernung wird durch die übliche Zugabe ersetzt. Die Hauptsache ist, den Unterschied in der Anzahl der Anweisungen zwischen den beiden Optionen zum Implementieren der for-Schleife zu sehen.
Vanillezyklus:
local anomtable = { 1, 2, 3, 4, 5 } for k, v in pairs(anomtable) do v = v + 1 end
Unser ist großartig:
local b, key = { 1, 2, 3, 4, 5 }, nil repeat key = next(b, key) b[key] = b[key] + 1 until key ~= nil
Schauen wir uns den Interpreter-Code an (
wie bei einem Assembler wird nicht empfohlen, als High-Level-Programmierer unter einem Spoiler zu suchen ).
Entfernen Sie für alle Fälle die Jones von den Bildschirmen. Ich warnte.Vanillezyklus Disassembler ; 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
Fahrradzyklus Disassembler ; 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
Eine unerfahrene Person wird einfach sagen, dass ein regulärer Zyklus schneller ist, weil es weniger Anweisungen gibt (15 vs. 19).
Wir dürfen jedoch nicht vergessen, dass jeder Befehl im Interpreter Prozessorzyklen aufweist.
Gemessen am Disassembler-Code in der ersten Schleife gibt es eine Forloop-Anweisung, die im Voraus für die Arbeit mit dem Array geschrieben wurde. Das Array wird in den Speicher geladen, es wird global, wir springen über die Elemente und fügen eine Konstante hinzu.
In der zweiten Variante unterscheidet sich die Methode, die mehr auf dem Speicher basiert, eine Tabelle empfängt, ein Element ändert, eine Tabelle setzt, auf Null prüft und erneut aufruft.
Unser zweiter Zyklus ist schnell, weil ein Befehl zu viele Bedingungen und Aktionen enthält (R4, R5: = R1 (R2, R3); wenn R4 ~ = null, dann beginne PC = 12; R3: = R4 ende) es frisst viel
, frisst CPU-Taktzyklen zur Ausführung, die Vergangenheit ist wieder mehr an den Speicher gebunden.
Die Anweisungsschleife mit einer großen Anzahl von Elementen ergibt sich unserem Zyklus über die Durchgangsgeschwindigkeit aller Elemente. Dies liegt an der Tatsache, dass die direkte Adressierung an die Adresse schneller ist als alle Goodies von Paaren. (Und wir haben keine Ablehnung)
Im Allgemeinen verlangsamt jede Verwendung von Negation im Code diese im Geheimen, dies wurde bereits durch Tests und Zeit getestet. Negative Logik arbeitet langsamer, da die Prozessor-ALU über eine separate Recheneinheit „Inverter“ für den unären Operanden verfügt (nicht!). Um zu arbeiten, müssen Sie auf den Inverter zugreifen, was zusätzliche Zeit in Anspruch nimmt.
Fazit: Nicht jeder Standard ist immer besser, Ihre Fahrräder können nützlich sein, aber auch bei einem echten Projekt sollten Sie sich diese nicht einfallen lassen, wenn die Freigabegeschwindigkeit für Sie wichtig ist. Infolgedessen haben wir die vollständige Entwicklung von 2014 bis heute abgeschlossen, eine Art weiteres "Warten". Obwohl es sich um einen normalen Spieleserver handelt, der in einem Tag installiert und in zwei Tagen vollständig für das Spiel konfiguriert ist, müssen Sie in der Lage sein, etwas Neues einzuführen.
In diesem langfristigen Projekt gab es noch die zweite Version von sich selbst, in der der Code viele Optimierungen enthält. In den folgenden Artikeln werde ich jedoch auf andere Optimierungen eingehen. Unterstützung mit Kritik oder Kommentar, richtig, wenn ich mich irre.