HTML5 Canvas · Performans ve optimizasyon
Nesne havuzlama: döngü içinde tahsisatı kesmek ve yeniden kullanmak
Canvas tabanlı oyun ve görselleştirmede her karede yüzlerce küçük nesne (
mermi, parçacık, geçici çizim kaydı, çarpışma sonucu ) üretmek doğaldır. JavaScript’te her
{} veya new çağrısı çöp toplayıcı (
GC ) için iş oluşturur; düşük gecikme hedefinde ani takılmaların sık
nedeni budur.
Nesne havuzlama, önceden ayrılmış nesneleri «edin → kullan → iade et»
döngüsüyle
yeniden dolaşıma sokmaktır. Piksel maliyeti ve bağlam sırası başka başlıklarda işlenir:
Yeniden çizim maliyeti,
Batching mantığı; burada odak
bellek tahsisatı ve nesne ömrüdür.
Güncelleme fazının zamanlaması kare yönetimi ile uyumludur; gereksiz çizimi kesmek yeniden çizim bayrağı ile ilişkilidir — havuz, görünmez kalan sahnede bile edilen gereksiz tahsisleri azaltır. Görünmez tampon üretimi için komşu Offscreen Canvas düşünülür.
Özet: havuz ne zaman mantıklıdır?
| Belirti | Muhtemel sorun | Havuz katkısı |
|---|---|---|
| Kare başına yüzlerce geçici nesne | Yüksek tahsis + GC dalgalanması | Aynı yapıları yeniden kullanma |
| Parçacık / mermi patlaması | Kısa ömürlü nesne seli | Üst sınırlı havuz + sıfırlama |
| Çizim komut listesi oluşturma | Her kare yeni dizi / nesne | Listeyi havuzdan derleyip iade |
Nesne havuzlama nedir ve çöp toplayıcı baskısı
Havuz deseni, boştaki nesneler için bir yeniden kullanım deposu tutar.
İhtiyaç olduğunda depodan bir öğe alınır (
acquire ), iş bitince temizlenip depoya geri konur (
release ). Böylece sürekli { x: 0, y: 0 } üretmek yerine
aynı nesnenin alanları güncellenir — çöp toplayıcıya düşen kısa ömürlü nesne sayısı düşer.
Bu, Canvas’a özgü bir API değildir; fakat 60 veya 120 kare hedefinde döngü içindeki mikro
tahsisler bir araya gelince kullanıcıya takılma olarak yansır.
Havuz «her yerde şart» değildir: düşük frekanslı kullanıcı arayüzü güncellemesi veya seyrek oluşturulan grafiklerde karmaşıklık borcu kazançtan büyük olabilir. Kazanım genelde yüksek churn ( çok oluştur / çok bırak ) bölgelerinde görülür — özellikle fizik sonrası geçici sonuç nesneleri, çarpışma listesi girişleri ve tek karelik görsel efekt kayıtları tipiktir. Önce performans panelinde tahsis ve GC düzenini gözlemlemek, sonra havuzu dar bir alanda denemek doğru sıradır.
Havuzlanmış nesneler hâlâ JavaScript nesnesidir; TypeScript veya sınıf kullanıyorsanız aynı kurallar geçerli — önemli olan sözleşme disiplini: hangi alanın havuz dışı görünür durum, hangisinin yalnızca dahili önbellek olduğu net yazılmalıdır.
Edinme ve iade yaşam döngüsü
Yaşam döngüsü üç adımdan oluşur: (1) boş listeden nesne al veya fabrika ile yeni üret; (2) kare veya olay süresince kullan; (3) iş bitince sıfırlayıp boş listeye iade et. Atlama yapılan her adım — özellikle iade — bellek sızıntısı veya çift kullanım hatasına yol açar. «Yalnızca tek sahibim» kuralı: havuzdan çıkan nesneyi aynı anda iki sistem referans etmemelidir; aktarım yapılıyorsa ya sahipliği açıkça sabitleyin ya da havuz dışı kopya politikası seçilmelidir.
İade sırasında referansları koparmak önemlidir: başka bir nesneye tutulan büyük dizi veya
grafik referansı bırakmak, havuzda «yanlışlıkla paylaşılan» kalıcı durum doğurur. Bu yüzden
reset işlevi (
aşağıdaki fabrika örneğinde ) hem sayısal alanları sıfırlamalı hem de isteğe bağlı nesne
alanlarını null yapmalıdır.
Çok kare boyunca yaşayan oyun varlıkları genelde havuz dışında kalır; havuz kısa ömürlü yardımcı yapılar içindir. Uzun ömürlü dünya nesnesini havuza atmak mimari karışıklık yaratır — havuz ile «varlık yönetimi»ni karıştırmayın. Oyun durumu düşüncesi ile çelişmeden, havuzu düşük seviyeli çizim ve fizik yardımcıları için kullanın.
Güvenli havuz fabrikası ve üst sınır politikası
Üretim kodunda havuzun sonsuz büyümesine izin vermeyin: ani patlamalarda ( binlerce parçacık ) bellek tükenebilir. Üst sınır ( cap ), boş listede tutulacak maksimum nesne sayısını sınırlar; fazlası iade edilirken düşürülür veya hiç havuzlanmaz — ürün politikası seçilir. Aşağıdaki fabrika, fabrika işlevini ve isteğe bağlı sıfırlayıcıyı dışarıdan alır; kullanıcı girdisi doğrudan fabrikaya bağlanmaz ( güvenlik için fabrika içinde sabit başlangıç kullanın ).
acquire boş listeden çıkarır; boşsa fabrikayı çağırır.
release önce sıfırlar, sonra üst sınır uygunsa boş listeye iter.
İsteğe bağlı prewarm(n) ile başlangıç dalgasını önceden doldurarak ilk saniye
takılmalarını yumuşatabilirsiniz — mobilde bellek bütçesi ile tartılır.
/**
* @template T
* @param {() => T} factory - Her çağrıda yeni boş nesne (kullanıcı girdisi bağlamayın).
* @param {{ maxFree?: number, reset?: (obj: T) => void }} [options]
*/
export function createObjectPool(factory, options = {}) {
const maxFree = Math.max(0, Math.floor(Number(options.maxFree) || 256));
const reset =
typeof options.reset === 'function'
? options.reset
: () => {
/* no-op */
};
/** @type {T[]} */
const free = [];
function acquire() {
const obj = free.length ? free.pop() : factory();
return obj;
}
/** İş bitince çağrılır; referansları reset içinde temizleyin. */
function release(obj) {
if (obj == null) return;
reset(obj);
if (free.length < maxFree) free.push(obj);
}
function prewarm(count) {
const n = Math.max(0, Math.floor(Number(count) || 0));
for (let i = 0; i < n && free.length < maxFree; i++) free.push(factory());
}
return { acquire, release, prewarm, freeCount: () => free.length };
}
Sıfırlama işlevi ve düz nesne şeması
En okunabilir yaklaşım, havuzlanan yapıyı düz nesne (
plain object ) olarak tutmaktır: sayısal alanlar (
konum, hız, süre ) doğrudan özellik olarak güncellenir; referans tutulan alanlar (
hedef varlık işaretçisi ) iade sırasında koparılır. Sınıf örnekleri de havuzlanabilir;
yeter ki reset tutarlı ve hızlı olsun — constructor yan etkileri üretmemelidir.
Örnek şema: parçacık için x,
y,
vx,
vy,
life,
active. Kare güncellemesinde yalnız active === true olanlar
işlenir;
ömür bitince release çağrılır ve listeden çıkarılır. Liste olarak yoğun dizi (
dense array ) veya seyrek işaretçi dizisi ürün kararıdır — tam sayı
matematiği veya TypedArray ile yoğun tampon kullanımı bu sayfanın
konusu dışında bırakılır; havuz yine de üst düzey meta nesneler için kullanılabilir.
Aşağıdaki örnek yalnız deseni gösterir: gerçek oyunda üst sınır, ömür politikası ve çarpışma kodu eklenmelidir. Harici kaynak veya DOM erişimi yoktur.
import { createObjectPool } from './object-pool.js';
function particleFactory() {
return { x: 0, y: 0, vx: 0, vy: 0, life: 0, active: false };
}
function resetParticle(p) {
p.x = 0;
p.y = 0;
p.vx = 0;
p.vy = 0;
p.life = 0;
p.active = false;
}
export function createParticlePool(maxFree = 512) {
return createObjectPool(particleFactory, { maxFree, reset: resetParticle });
}
/** Örnek: spawn — pozitif ömür ve pozisyon doğrulaması çağıran tarafta yapılmalıdır. */
export function spawnParticle(pool, px, py, vx, vy, frames) {
const p = pool.acquire();
p.x = px;
p.y = py;
p.vx = vx;
p.vy = vy;
p.life = Math.max(0, Math.floor(frames));
p.active = true;
return p;
}
Çizim komut listesi ile havuzun birleştirilmesi
Batching mantığı
içindeki
komut listesi her kare için geçici diziler üretebilir. Havuz kullanımı: liste düğümlerini
havuzdan al, sıralayıp çiz, sonra tüm düğümleri iade et — böylece push ile
büyüyen dizinin her elemanı için sürekli nesne yaratımı kesilir. Liste kapasitesi (
dizi .length = 0 ile sıfırlama ) ile havuz (
nesne ömrü ) farklı kavramlardır; ikisi birlikte kullanılabilir.
Pratik akış: güncelleme aşamasında aktif havuz nesnelerinden komut nesneleri türetilir veya
doğrudan sıralama anahtarı taşıyan geçici kayıtlar havuzdan alınır; çizim flush’undan sonra
hepsi release edilir. Bir nesneyi hem mantık hem çizim için paylaşıyorsanız,
çizim kaydı nesnesini havuzda tutup içine referans yazmak yerine kopya kimlik (
id ) taşımak daha güvenlidir — böylece havuz iadesi ile mantık
nesnesinin yaşamı karışmaz.
Gereksiz karelerde listeyi hiç oluşturmamak kirli bayrak ile uyumludur: havuz edinimi de atlanır; hem CPU hem bellek kazançlıdır.
Yüksek frekanslı varlık senaryoları ve ölçek
Parçacık ve mermi sistemleri klasik havuz adayıdır: ömür kısa, oluşturma sık, görsel olarak benzer kayıtlar. Çarpışma testleri için geçici «temas sonucu» nesneleri de havuzlanabilir — fizik motorunun kendi API’sine müdahale etmeden, yalnız kendi yardımcı yapılarınız için kullanın.
Kullanıcı arayüzü düğümleri genelde düşük churn gösterir; burada havuz yerine bileşen ömrünü yönetmek daha okunabilir olabilir. Ölçek binlerce kaydırsa ( büyük liste sanallaştırması ) liste satırı meta nesneleri için sınırlı havuz düşünülebilir — ancak erişilebilirlik ve odak yönetimi ile çakışmayın.
Mobil cihazlarda havuz üst sınırını düşük tutmak ve ani patlamada efektleri kısıtlamak ( «parçacık sayısı tavanı» ) kullanıcı deneyimi ile performansın uzlaşımıdır — teknik olarak doğru kod, cihazı ısıtmaktan kaçınmak için ürün politikası gerektirir.
Tuzaklar, ölçüm ve kontrol listesi
Çift iade: Aynı nesneyi iki kez release etmek, listede
yineleme
ve üst üste «aktif» kullanım riski doğurur. Koruma: nesneye dahili bayrak (
__pooledReleased ) veya havuz dışı tek sahipli kuyruk disiplini.
Sızıntı: Edinilen nesne her zaman bir çıkış yolunda iade edilmeli; erken
return ve hata dallarında bile release çağrısı unutulmamalıdır.
Tükenmiş kapasite: Üst sırada fazla nesne düşürülünce efekt seyrekleşir;
bunu görsel olarak tolere edilebilir kılın veya üst sınarı dinamik ayarlayın.
Havuzu ekledikten sonra mutlaka karşılaştırmalı ölçüm yapın: bazen beklenmedik şekilde kazanç düşük çıkar ( motor zaten nesneleri optimize ediyor olabilir ). Batching ve redraw stratejisi ile çelişen «her şeyi yeniden tahsis et» alışkanlığını havuz tek başına çözmez — üç başlık birlikte düşünülür.
Bu sayfanın sınırı
Tam teşekküllü ECS, varlık bileşen grafı veya çok iş parçacıklı bellek modelleri burada işlenmez. Odak: Canvas oyun döngüsünde kısa ömürlü JavaScript nesnelerinin güvenli yeniden kullanımıdır.
- Havuz üst sınırı ve ani patlama politikası tanımlandı mı?
releasetüm çıkış yollarında çağrılıyor mu?- İade sırasında dış referanslar koparılıyor mu?
- Havuz nesnesi iki sistemde eşzamanlı paylaşılmıyor mu?
- GC ve kare süresi ölçümü havuz öncesi / sonrası karşılaştırıldı mı?