Kisah tentang apa yang tidak perlu Anda lakukan selama pengembangan

Prolog: Untuk mulai dengan, saya akan berbicara tentang proyek, sehingga ada ide tentang bagaimana kami bekerja pada proyek dan untuk menciptakan kembali rasa sakit yang kami rasakan.

Sebagai seorang pengembang, saya memasuki proyek pada 2015-2016, saya tidak ingat persis, tetapi berhasil 2-3 tahun sebelumnya. Proyek ini sangat populer di bidangnya, yaitu server game. Betapa anehnya itu tidak terdengar, tetapi proyek-proyek pada server game sedang berlangsung hingga hari ini, baru-baru ini saya melihat lowongan dan bekerja sedikit di tim yang sama. Karena server game dibangun di atas game yang sudah dibuat, maka, bahasa scripting digunakan untuk pengembangan yang dibangun ke dalam mesin game.

Kami sedang mengembangkan proyek dari hampir nol pada Garry's Mod (Gmod), penting untuk dicatat bahwa pada saat penulisan, Harry sudah membuat proyek S & Box baru di Unreal Engine. Kami masih duduk di Sumber.
Yang umumnya tidak cocok untuk tema server kami.
gambar

"Apa yang menakutkan dengan ceritamu?" - kamu bertanya.

Kami memiliki tema yang kuat untuk server permainan, yaitu, "Penguntit" dan bahkan dengan elemen permainan peran (RP), pertanyaan segera muncul - "Bagaimana semua orang bisa menerapkan ini pada satu server?"

Mengingat mesin Sumber sudah tua (versi 2013 juga digunakan dalam Gmod 32 bit), Anda tidak akan membuat kartu besar, ada pembatasan kecil pada jumlah Entitas, Mesh, dan banyak lagi.
Siapa yang bekerja pada mesin akan mengerti.
Ternyata tugas itu umumnya tidak mungkin, untuk membuat penguntit multiplayer bersih dengan pencarian, elemen RPG dari aslinya sendiri dan lebih disukai plot kecil.

Pertama-tama, ejaan awal itu sulit (banyak tindakan dari kategori: melempar objek, mengangkat objek ditulis dari awal), berharap akan lebih mudah untuk melanjutkan, tetapi persyaratan bertambah. Mekanisme permainan sudah siap, yang tersisa hanyalah melakukan kecerdasan, peningkatan, dan segala macam hal. Secara umum, semua ditransfer semampu mereka.

gambar

Masalahnya sudah dimulai selama karya rilis versi pertama, yaitu (kelambatan, keterlambatan server).

Sepertinya server yang kuat bisa dengan tenang memproses permintaan dan menahan seluruh Gamemode.

Deskripsi sederhana tentang gamemode
Ini adalah nama sekumpulan skrip yang ditulis untuk menggambarkan mekanisme server itu sendiri
Sebagai contoh: kami ingin tema "Royal Battles" yang sekarang populer, yang berarti bahwa namanya juga harus sesuai dengan mekanisme permainan. "Bibit pemain di pesawat, kamu bisa mengambil barang, pemain bisa berkomunikasi, kamu tidak bisa memakai lebih dari 1 helm, dll." - semua ini dijelaskan oleh mekanisme permainan di server.

Keterlambatan keduanya ada di sisi server karena jumlah pemain yang besar, karena satu pemain memakan banyak RAM sekitar 80-120 mb (tidak termasuk item dalam inventaris, keterampilan, dll.), Dan di sisi klien ada penurunan yang kuat FPS

Kekuatan CPU tidak cukup untuk memproses fisika, perlu menggunakan objek dengan sifat fisik lebih sedikit.

Begitu juga di samping itu skrip yang kami tulis sendiri yang tidak dioptimalkan sama sekali.

gambar

Pertama, tentu saja, kami membaca artikel tentang pengoptimalan di Lua. Bahkan bunuh diri bahwa mereka ingin menulis DLL dalam C ++, tetapi masalah muncul dalam mengunduh DLL dari server ke klien. Menggunakan C ++ untuk DLL, Anda dapat menulis program yang secara diam-diam memotong data; Pengembang Gmod menambahkan ekstensi ke pengecualian untuk mengunduh oleh klien (keamanan, meskipun sebenarnya tidak pernah terjadi). Meskipun akan nyaman dan Gmod akan menjadi lebih fleksibel, tetapi lebih berbahaya.

Selanjutnya, kami melihat profiler (untungnya, orang pintar yang menulisnya) dan ada fungsi yang mengerikan, terlihat bahwa pada awalnya sudah ada fungsi yang sangat lambat di perpustakaan mesin Gmod.

Jika Anda mencoba menulis dalam Gmod, maka Anda sangat menyadari bahwa ada perpustakaan yang disebut matematika.

Dan fungsi paling lambat di dalamnya tentu saja adalah matematika. Penjepit dan matematika.

Mengaduk-aduk kode orang, diketahui bahwa fungsi dilemparkan ke arah yang berbeda, digunakan hampir di mana-mana, tetapi salah!

Ayo berlatih. Misalnya, kami ingin membulatkan koordinat vektor posisi untuk memindahkan entitas (misalnya, pemain).

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 fungsi pembulatan kompleks, tetapi tidak ada yang serius, kecuali tentu saja dalam satu lingkaran dan tidak sering digunakan, tetapi Clamp bahkan lebih sulit.

Kode berikut sering digunakan dalam proyek dan tidak ada yang ingin mengubah apa pun.

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

Misalnya, self menunjuk ke objek pemain dan memiliki variabel lokal yang kami temukan, yang ketika direset ke server diatur ulang, math.Clamp pada dasarnya sebagai loop membuat tugas yang halus, mereka suka membuat antarmuka yang halus di Clamp.

Masalah muncul ketika itu bekerja pada setiap pemain yang mengunjungi server. Kasus yang jarang terjadi, tetapi jika 5-15 (segera tergantung pada konfigurasi server) memasuki server pada satu titik waktu dan fungsi kecil dan sederhana ini mulai berfungsi untuk semua orang, maka server akan mengalami penundaan CPU yang baik. Lebih buruk lagi jika math.Clamp dalam satu lingkaran.

Optimalisasi sebenarnya sangat sederhana, Anda melokalisasi banyak fungsi. Tampaknya primitif, tetapi dalam 3 gamemode dan banyak add-on saya melihat kode lambat ini.

Jika Anda perlu mendapatkan nilai dan menggunakannya di masa mendatang, Anda tidak perlu mendapatkannya lagi jika tidak berubah. Bagaimanapun, seorang pemain yang memasuki server dalam hal apa pun akan menerima rasa lapar sama dengan 100, sehingga kode ini berkali-kali lebih cepat.

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

Semua baik-baik saja, mereka mulai melihat lebih jauh cara kerjanya. Sebagai hasilnya, kami memulai mania untuk mengoptimalkan semuanya.

Kami memperhatikan bahwa standar untuk loop lambat dan kami memutuskan untuk datang dengan sepeda kami sendiri yang akan lebih cepat (kami tidak melupakan blackjack) dan kemudian permainan dimulai.

gambar

Spoiler
Kami bahkan berhasil membuat loop tercepat pada Lua Gmod, tetapi dengan syarat harus ada lebih dari 100 elemen.

Menilai oleh waktu yang dihabiskan pada siklus kami dan penggunaannya dalam kode, kami mencoba dengan sia-sia untuk melakukan ini karena ia menemukan aplikasi hanya dalam menelurkan pada peta anomali setelah melempar dan membersihkannya.
Demikian juga dengan kode. Misalnya, Anda perlu menemukan semua entitas dengan nama di awal anom, kami memiliki anomali dalam nama kelas ini.

Berikut ini untuk skrip normal pada Lua Gmod:

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

Ini untuk perokok:

Segera jelas bahwa kode g * jelas akan lebih lambat daripada standar "untuk berpasangan", tetapi ternyata tidak.

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


Untuk menganalisis opsi loop ini sepenuhnya, Anda perlu menerjemahkannya ke dalam skrip Lua biasa.
Misalnya, anomtable akan memiliki 5 elemen.
Penghapusan diganti dengan penambahan yang biasa. Hal utama adalah untuk melihat perbedaan dalam jumlah instruksi antara dua opsi untuk mengimplementasikan loop for.

Siklus vanilla:

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

Kami sangat bagus:

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

Mari kita lihat kode juru bahasa ( seperti assembler, tidak disarankan untuk melihat di bawah spoiler sebagai programmer tingkat tinggi ).

Untuk jaga-jaga, lepaskan jones dari layar. Saya memperingatkan.

Disassembler siklus vanilla
 ; 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 


Pembongkar siklus sepeda
 ; 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 


Orang yang tidak berpengalaman hanya akan mengatakan bahwa siklus reguler lebih cepat karena ada lebih sedikit instruksi (15 vs 19).

Tetapi kita tidak boleh lupa bahwa setiap instruksi dalam interpreter memiliki siklus prosesor.
Dilihat oleh kode disassembler pada siklus pertama, ada instruksi forloop ditulis sebelumnya untuk bekerja dengan array, array dimuat ke memori, menjadi global, kita melompati elemen dan menambahkan konstanta.

Dalam varian kedua, metode ini berbeda, yang lebih didasarkan pada memori, menerima tabel, mengubah elemen, mengatur tabel, memeriksa nihil dan memanggil lagi.
Siklus kedua kami cepat karena ada terlalu banyak kondisi dan tindakan dalam satu instruksi (R4, R5: = R1 (R2, R3); jika R4 ~ = nil maka mulai PC = 12; R3: = R4 akhir) karena ini makan banyak , makan siklus clock CPU untuk eksekusi, masa lalu sekali lagi lebih terikat ke memori.

Instruksi forloop dengan sejumlah besar elemen menyerah pada siklus kita pada kecepatan perpindahan semua elemen. Ini disebabkan oleh fakta bahwa menangani langsung ke alamat lebih cepat, lebih sedikit daripada barang apa pun dari pasangan. (Dan kami tidak memiliki penolakan)
Secara umum, secara rahasia, setiap penggunaan negasi dalam kode memperlambatnya, ini telah diuji oleh tes dan waktu. Logika negatif akan bekerja lebih lambat karena prosesor ALU memiliki unit komputasi β€œinverter” yang terpisah, untuk operan unary (bukan,!) Untuk bekerja, Anda perlu mengakses inverter dan ini akan memerlukan waktu tambahan.
Kesimpulan: Segala sesuatu yang standar tidak selalu lebih baik, sepeda Anda bisa berguna, tetapi sekali lagi pada proyek nyata, Anda tidak harus datang dengan mereka jika kecepatan rilis penting bagi Anda. Sebagai hasilnya, kami telah menyelesaikan pengembangan penuh dari 2014 hingga saat ini, semacam "menunggu" lainnya. Meskipun sepertinya server game biasa yang diinstal dalam 1 hari dan sepenuhnya dikonfigurasi untuk game dalam 2 hari, Anda harus dapat memperkenalkan sesuatu yang baru.

Proyek jangka panjang ini masih melihat versi kedua dari dirinya sendiri di mana ada banyak optimasi dalam kode, tetapi saya akan berbicara tentang optimasi lain di artikel berikut. Dukungan dengan kritik atau komentar, benar jika saya salah.

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


All Articles