Concepts - Optimization Tips

En İyi Zamanı Bilmek

Optimizasyon performansı artırabilir, ancak kodun anlaşılmasını ve bakımını da zorlaştırabilir. Doğruluk ve okunabilirlik genellikle önce gelmelidir.

Optimize etmeden önce şunu sorun:

  • Kod tamamlanmış ve düzgün çalışıyor mu?
  • Bu kodun bir önemi var mı?
  • Profilleme bunu bir engelle mi tanımladı?
  • Geliştirme, okunabilirlik kaybını haklı çıkarıyor mu?
  • Diğer geliştiricilerin daha sonra bu kodu değiştirmeleri gerekecek mi?

Bir kere çalıştırılan kodda birkaç mikrosaniye tasarruf etmek nadiren işe yarar. Tick, Think, HUDPaint, görüntüleme kancaları veya büyük bir döngü, saniyede birçok kez meydana geldiği için önemli olabilir.

Optimize Etmeden Önce Önlem Alın

Sadece görünüşüne dayanarak kodun yavaş olduğunu düşünmeyin.

SysTime LuaJIT derlemesi, mevcut sunucu yükü ve Lua ile motor arasındaki aramalar sonucu etkileyebileceği için bir benchmark'ı birden fazla kez çalıştırın.

Küçük referans değerleri yararlıdır, ancak iyileştirmeler nihayetinde kodun kullanıldığı gerçek sistemde ölçülmelidir.

local iterations = 100000 local startedAt = SysTime() local result = 0 for i = 1, iterations do result = result + i end local elapsed = SysTime() - startedAt -- Sonuç kullanımı, karşılaştırmalı çalışmanın kullanılmamış olarak değerlendirilmesini engeller. print( "Result:", result ) print( "Total time:", elapsed ) print( "Average time:", elapsed / iterations )

En Önemli İpuçları

Yerel Değişkenleri Kullanın

Lua değişkenleri küreseldir. local Lokal değişkenler mevcut kapsam veya işlevle sınırlıdır. Lua VM'nin erişimi için daha ucuz olmakla birlikte eklentiler arasındaki tesadüfi çatışmaları önlüyorlar.

Yerel değerler function registers'da depolanır veya yükseltme değeri olarak yakalanırlar. Küresel fonksiyonlar normalde bir tablo araması kullanılarak işlevin ortamından alınır.

Bilgilerin dosyalar arasında gerçekten paylaşılması gerektiğinde tek bir küresel eklenti tablosunu kullanın.

someCounter = 0 -- Küresel: mevcut küresel ortamda depolanır. local someCounter = 0 -- Lokal: sadece bu kapsamda görünür. -- Dosyaların veri paylaşması gerektiğinde, birçok küresel yerine bir eklenti tablosunu açığa çıkarın. MyAddon = MyAddon or {} MyAddon.RoundState = 0 -- Dosya iç uygulama ayrıntılarını yerel tutun. local internalCounter = 0

Tekrarlanan İş ve Görevlerden Kaçının

Bir değer değişmezse, bir kez hesaplayın veya oluşturun ve tekrar kullanın.

Genellikle kaçırılan önbellek tablosu

Geçici tablolar oluşturmak gibi, Renks, Vektörs veya AçıSıkça uygulanan kodlarda, tahsis ve çöp toplama çalışmalarını artırır. Renk ve Malzemes görüntüleme kancalarının dışında önbelleğe alınmalıdır.

Sonuçları manuel olarak önbelleğe almayın. player. GetAll Garry'nin Modü sadece oyuncular üzerinde tekrarlamak için sağlar. player. Iterator, iç Lua tarafı önbelleği kullanır ve yeni bir döndürülen tablo oluşturmaktan kaçınır.

-- Yanlış: nesneleri yaratır veya her kareye pahalı bir arama yapar. hook.Add( "HUDPaint", "OptimizationExample.BadHUD", function() surface.SetMaterial( Material( "icon16/heart.png" ) ) draw.SimpleText( "Healthy", "DermaDefault", 32, 32, Color( 100, 255, 100 ), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) end ) -- Daha iyi: istikrarlı nesneleri bir kez oluşturun ve tekrar kullanın. local heartMaterial = Material( "icon16/heart.png" ) local healthyColor = Color( 100, 255, 100 ) hook.Add( "HUDPaint", "OptimizationExample.GoodHUD", function() surface.SetMaterial( heartMaterial ) draw.SimpleText( "Healthy", "DermaDefault", 32, 32, healthyColor, TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP ) end )
-- Sık sık uygulanan bir kancada olumsuz: -- player. GetAll tekrarlamak için bir tablo gönderir. hook.Add( "Tick", "OptimizationExample.BadPlayerIteration", function() local alivePlayers = 0 for _, ply in ipairs( player.GetAll() ) do if ply:Alive() then alivePlayers = alivePlayers + 1 end end DoSomethingWithCount( alivePlayers ) end ) - Daha iyi: -- player. Iterator Garry'nin Mod'un içsel olarak korunan oyuncu önbelleğini kullanıyor. hook.Add( "Tick", "OptimizationExample.GoodPlayerIteration", function() local alivePlayers = 0 for _, ply in player.Iterator() do if ply:Alive() then alivePlayers = alivePlayers + 1 end end DoSomethingWithCount( alivePlayers ) end )
-- Bu anonim çağrı iyi. hook. Add çalıştırıldığında bir kez oluşturulur. hook.Add( "Think", "OptimizationExample.NormalClosure", function() UpdateGameState() end ) -- Olumsuz: her tikte bir masa ve birden fazla kapanış yaratır. hook.Add( "Tick", "OptimizationExample.BadTemporaryClosures", function() local actions = {} for _, ply in player.Iterator() do actions[#actions + 1] = function() UpdatePlayer( ply ) end end for i = 1, #actions do actions[i]() end end ) -- Daha iyi: gecikmiş geri çağrılar gereksiz olduğunda işi doğrudan gerçekleştirin. hook.Add( "Tick", "OptimizationExample.GoodDirectWork", function() for _, ply in player.Iterator() do UpdatePlayer( ply ) end end )

Değişken Nesneleri Tekrar Kullanın

Vektör aritmetiği operatörü genelde yeni bir vektör gönderir. Vector:Add, Vector:Mul, ve Vector:Set Ek geçici nesnelerden kaçınabilirsiniz.

local basePosition = Vector( 100, 200, 300 ) local labelOffset = Vector( 0, 0, 64 ) -- Bir kopyasını ve sonra ekleme sonucu için başka bir vektör oluşturur. local positionWithExtraAllocation = Vector( basePosition ) + labelOffset -- Bir kopyasını oluşturur, sonra başka bir toplama sonucu olmadan bu kopya değiştirir. local positionWithFewerAllocations = Vector( basePosition ) positionWithFewerAllocations:Add( labelOffset ) -- Eğer kalıcı olarak değişmek niyetinde değilseniz bunu yapmayın. -- basePosition:Add( etiketOffset)

Arama yerine arama tablolarını kullanın

Daha derinlemesine inceleniyor: Liste Biçimi Tabloları.

Bir değerin bilinen bir anahtarı olduğunda, bu anahtarın altında saklayın.

Bir tablonun arama süresi ortalama olarak yaklaşık sabit bir zamandır. Aray aramak, eşleşme bulunana kadar girişleri kontrol etmeyi gerektirir ve bu nedenle girdiler sayısıyla doğrusal büyür.

local actionList = { { Name = "heal", Handler = HealPlayer }, { Name = "armor", Handler = GiveArmor }, { Name = "respawn", Handler = RespawnPlayer } } -- Olumsuz: Aray'ı istenen eylemi bulana kadar tarar. local function RunActionBySearching( actionName, ply ) for i = 1, #actionList do local action = actionList[i] if action.Name == actionName then action.Handler( ply ) return true end end return false end -- Daha iyi: onları bulmak için kullanılan değere göre indeks işleyicileri. local actionHandlers = { heal = HealPlayer, armor = GiveArmor, respawn = RespawnPlayer } local function RunActionByLookup( actionName, ply ) local handler = actionHandlers[actionName] if not handler then return false end handler( ply ) return true end

Sık Kullanılan İşlevleri Yerelleştirin

Tekrarlanan tablo ve küresel aramalar, sıkça function in olarak adlandırılan bir (a) kaydedilerek kaldırılabilir.

Bu, aynı sabit function is sıcak bir yolun içinde birçok kez çağrıldığında en yararlıdır. Bununla birlikte bir denge vardır; her function makes kodunun okunması zorlaşır ve anlamlı bir iyileştirme sağlamayabilir.

local setDrawColor = surface.SetDrawColor local drawRect = surface.DrawRect hook.Add( "HUDPaint", "OptimizationExample.LocalizedFunctions", function() for i = 1, 100 do setDrawColor( 20, 20, 20, 200 ) drawRect( i * 4, 20, 3, 20 ) end end ) -- Yardımcı olan her function without kanıtı yerleştirmekten kaçının. -- Doğrudan aramalar daha net ve normalde iyi: hook.Add( "HUDPaint", "OptimizationExample.DirectFunctions", function() surface.SetDrawColor( 20, 20, 20, 200 ) surface.DrawRect( 20, 50, 200, 20 ) end )

Kısıtlı Döngüleri Azaltın

Döngüler programlama için gereklidir.

Bununla birlikte, n büyüklüğündeki bir koleksiyon üzerindeki iki döngü yaklaşık olarak 2 işlem gerçekleştirir.

Örneğin:

  • 3 oyuncunun her oyuncuya karşılaştırılması: 9 kıyaslama
  • 20 oyuncunun her oyuncuya karşılaştırılması: 400 kıyaslama
  • Her oyuncu ile karşılaştırıldığında 100 oyuncu: 10.000 kıyaslama

Köşeli döngüler her zaman yanlış değildir. Çiftli karşılaştırmalar bazı sistemlerde onları gerektirir. Birini kullanmadan önce, aynı işin şu yollarla yapılıp yapılmadığını kontrol edin:

  • Bir arama tablosu
  • Tek bir geçiş.
  • Önce verileri filtrele.
  • Uzaysal bölünme
  • Sadece değiştirilmiş nesneleri işleme alıyor.

Örnek:

-- Olumsuz: -- P oyuncular ve E varlıkları için, bu yaklaşık olarak P * E tekrarlarını gerçekleştirir. local function UpdateOwnedEntitiesNested() for _, ply in player.Iterator() do for _, ent in ents.Iterator() do if ent:GetOwner() == ply then UpdateOwnedEntity( ply, ent ) end end end end - Daha iyi: - Her varlığı bir kez ziyaret edin ve doğrudan sahibini alın. local function UpdateOwnedEntitiesSinglePass() for _, ent in ents.Iterator() do local owner = ent:GetOwner() if IsValid( owner ) and owner:IsPlayer() then UpdateOwnedEntity( owner, ent ) end end end

Ağ Kullanımı Azaltın

Sadece müşterilerin ihtiyaç duyduğu bilgileri ağlayın ve sadece gerektiğinde gönderin.

Tercihim:

  • Değişiklikler her tik yerine meydana geldiğinde gönderilir
  • Yayınlama yerine ilgili alıcılara gönderme
  • Tüm yapılar yerine kompakt tanımlayıcılar göndermek
  • Tam fonksiyonları kullanmak gibi net. WriteBool, net. WriteUInt, ya da net. WriteEntity
  • Müşterilerin hesaplamak için yeterli bilgiye sahip oldukları değerleri elde etmelerine izin vermek

net. WriteTable Bilinen alanları tam türlerini kullanarak yazmak genellikle daha küçük ve doğrulanması kolaydır. net. WriteTable ve net. WriteUInt.

Performans tek ağ kaygısı değildir. Sunucu tarafında doğrulanmadan bir istemciden alınan değerlere asla güvenmeyin.

Okumayı düşünün. Net Kütüphane Kullanımı, Özellikle Geliştirme kısmı.

if SERVER then util.AddNetworkString( "OptimizationExample.RoundStateChanged" ) local currentRoundState = 0 local function SetRoundState( newState ) -- Bu örnekte geçerli değerler 0 ila 7. newState = math.Clamp( math.floor( newState ), 0, 7 ) - Değişiklik yoksa haber göndermeyin. if newState == currentRoundState then return end currentRoundState = newState net.Start( "OptimizationExample.RoundStateChanged" ) net.WriteUInt( currentRoundState, 3 ) net.Broadcast() end -- Sadece bir oyun olayı durumu değiştirdiğinde SetRoundState'i ara. -- Mevcut değeri yeniden göndermek için Tick'ten çağırmayın. end if CLIENT then net.Receive( "OptimizationExample.RoundStateChanged", function() local newRoundState = net.ReadUInt( 3 ) UpdateRoundInterface( newRoundState ) end ) end

Mikro Optimizasyonlar

Mikro optimizasyonlar daha büyük sorunların ele alınmasından sonra gelmelidir.

En uygun durumları şunlardır:

  • Sistem zaten doğru ve eksiksiz.
  • Profilleme, kodu önemli olarak tanımlar.
  • Kod çok sık çalıştırılıyor.
  • Optimize edilmiş sürüm sürdürülebilir kalır.

Sıralı dizilere doğrudan uygulayın

Görevi: #tbl + 1 Çağırmaktan daha hızlı olabilir. table.insert yoğun bir diziye eklenirken.

Bu sadece eklemeye eşittir. Kullanmaya devam edin table.insert Belirli bir pozisyonda yerleştirildiğinde veya niyeti daha net olduğunda.

local values = {} local value = 7 -- Açık ve okunur: table.insert( values, value ) -- Sadece ekleme yoğun dizileri için potansiyel olarak daha hızlı: values[#values + 1] = value -- Belirli bir konumda yerleştirildiğinde bunlar eşdeğer değildir: table.insert( values, 1, value )

Tekrarlanan Matematiği Basitleştirin

Bazı matematiksel eşdeğer ifadeler daha az işlem gerektirir:

  • Karşılaştırma ile çarpım bölünmenin yerini alabilir.
  • x*x, katlanımı iki ile değiştirebilir.
  • Faktörlendirme tekrarlanan çarpımları ortadan kaldırabilir.
local x = 10 local y = 20 local z = 30 -- Sabitle bölme: local halfA = x / 2 local halfB = x * 0.5 -- Karesi: local squaredA = x ^ 2 local squaredB = x * x -- Faktörleştirme bir çarpımı ortadan kaldırır: local resultA = x * y + x * z + y * z local resultB = x * ( y + z ) + y * z -- Profillemenin -- alternatif tam olarak bu sıcak yolda anlamlı bir gelişme sağlar.

Önemli İlgili Kaynaklar

Tüm sunucular · GmodTR Launcher · Rehber · GMod Wiki · GmodTR nedir? · Sunucu ekle · Discord