Belleği Gmail ölçeğinde etkili şekilde yönetme

John McCutchan
John McCutchan
Loreena Lee
Loreena Lee

Giriş

JavaScript, otomatik bellek yönetimi için çöp toplama işlemini kullanır ancak uygulamalarda etkili bellek yönetiminin yerini almaz. JavaScript uygulamaları, yerel uygulamalarda görülen bellek sızıntısı ve şişme gibi bellekle ilgili sorunlardan muzdariptir. Ayrıca, çöp toplama duraklamaları ile de uğraşmak zorundadır. Gmail gibi büyük ölçekli uygulamalar, küçük uygulamalarınızda karşılaştığınız sorunlarla aynı sorunlarla karşılaşır. Gmail ekibinin, bellek sorunlarını tespit etmek, izole etmek ve düzeltmek için Chrome Geliştirme Araçları'nı nasıl kullandığını öğrenmek için okumaya devam edin.

Google I/O 2013 Oturum

Bu materyali Google I/O 2013'te sunduk. Aşağıdaki videoya göz atın:

Gmail, bir sorun oluştu…

Gmail ekibi ciddi bir sorunla karşı karşıyaydı. Kaynakları sınırlı dizüstü ve masaüstü bilgisayarlarda Gmail sekmelerinin birden fazla gigabayt bellek tükettiğine dair anekdotlar giderek daha sık duyuluyordu. Bu durum genellikle tarayıcıdaki tüm işlemleri durduruyordu. CPU'ların %100'e sabitlenmesi, yanıt vermeyen uygulamalar ve Chrome'un üzgün sekmeleri ("O öldü, Jim.") ile ilgili hikayeler. Ekip, sorunu düzeltmek bir yana, teşhis etmeye bile nereden başlayacağını bilmiyordu. Sorunun ne kadar yaygın olduğu hakkında hiçbir fikirleri yoktu ve mevcut araçlar büyük uygulamalara uygun değildi. Ekip, Chrome ekipleriyle güçlerini birleştirdi ve birlikte hafıza sorunlarını önceliklendirmek için yeni teknikler geliştirdi, mevcut araçları iyileştirdi ve sahadan hafıza verilerinin toplanmasını sağladı. Ancak araçlara geçmeden önce JavaScript bellek yönetiminin temellerini ele alalım.

Bellek Yönetimi ile İlgili Temel Bilgiler

JavaScript'te belleği etkili bir şekilde yönetebilmek için temel bilgileri anlamanız gerekir. Bu bölümde, ilkel türler ve nesne grafiği ele alınacak, genel olarak bellek şişmesi ve JavaScript'te bellek sızıntısı için tanımlar verilecektir. JavaScript'teki bellek bir grafik olarak düşünülebilir. Bu nedenle grafik teorisi, JavaScript bellek yönetiminde ve yığın profilleyicide rol oynar.

Basit Türleri

JavaScript'de üç temel tür vardır:

  1. Sayı (ör. 4, 3,14159)
  2. Boole (doğru veya yanlış)
  3. Dize ("Merhaba Dünya")

Bu ilkel türler başka değerlere referans veremez. Nesne grafiğinde bu değerler her zaman yaprak veya sonlandırma düğümleridir. Yani hiçbir zaman giden kenarları yoktur.

Yalnızca bir kapsayıcı türü vardır: nesne. JavaScript'de nesne, ilişkisel bir dizidir. Boş olmayan nesne, diğer değerlere (düğümler) giden kenarları olan bir iç düğümdür.

Diziler Hakkında

JavaScript'deki diziler aslında sayısal anahtarları olan nesnelerdir. JavaScript çalışma zamanları, dizi benzeri nesneleri optimize ettiği ve bunları arka planda diziler olarak temsil ettiği için bu basitleştirmedir.

Terminoloji

  1. Değer: Basit bir tür (nesne, dizi vb.) örneği.
  2. Değişken: Bir değere referans veren bir ad.
  3. Özellik: Nesnedeki bir değere referans veren ad.

Nesne Grafiği

JavaScript'teki tüm değerler nesne grafiğinin bir parçasıdır. Grafik, köklerle başlar (ör. pencere nesnesi). GC köklerinin ömrünü yönetmeniz mümkün değildir. Çünkü bu kökler tarayıcı tarafından oluşturulur ve sayfa yüklendiğinde yok edilir. Global değişkenler aslında penceredeki özelliklerdir.

Nesne grafiği

Bir Değer Ne Zaman Çöp Olur?

Bir kökten değere giden yol olmadığında değer çöp olur. Diğer bir deyişle, köklerden başlayarak stack frame içinde etkin olan tüm nesne özelliklerini ve değişkenlerini kapsamlı bir şekilde aradığınızda bir değere ulaşılamaz. Bu değer, çöp haline gelmiştir.

Çöp grafiği

JavaScript'de bellek sızıntısı nedir?

JavaScript'de bellek sızıntısı genellikle sayfanın DOM ağacından erişilemeyen ancak bir JavaScript nesnesi tarafından referans verilen DOM düğümleri olduğunda ortaya çıkar. Modern tarayıcılar, yanlışlıkla sızıntı oluşturmayı giderek zorlaştırsa da bu işlem düşündüğünüzden daha kolaydır. DOM ağacına aşağıdaki gibi bir öğe eklediğinizi varsayalım:

email.message = document.createElement("div");
displayList.appendChild(email.message);

Ardından öğeyi görüntüleme listesinden kaldırırsınız:

displayList.removeAllChildren();

email mevcut olduğu sürece, mesajın referans verdiği DOM öğesi sayfanın DOM ağacından ayrılmış olsa bile kaldırılmaz.

Bloat nedir?

İdeal sayfa hızı için gerekenden fazla bellek kullandığınızda sayfanız şişmiştir. Bellek sızıntıları da dolaylı olarak şişmeye neden olur ancak bu durum tasarımdan kaynaklanmaz. Boyut sınırı olmayan bir uygulama önbelleği, bellek şişmesinin yaygın bir kaynağıdır. Ayrıca sayfanız, ana makine verileri (ör. resimlerden yüklenen piksel verileri) nedeniyle şişebilir.

Atık toplama nedir?

Atık toplama, JavaScript'te belleğin yeniden kullanılmasıdır. Bu işlemin ne zaman yapılacağına tarayıcı karar verir. Bir koleksiyon sırasında, sayfanızdaki tüm komut dosyası yürütme işlemleri askıya alınır. Bu sırada, canlı değerler GC köklerinden başlayarak nesne grafiğinin taranmasıyla bulunur. Ulaşılamayacak tüm değerler "çöp" olarak sınıflandırılır. Çöp değerlere ait bellek, bellek yöneticisi tarafından geri alınır.

V8 Çöp Toplayıcısı Ayrıntılı Açıklaması

Çöp toplamanın nasıl gerçekleştiğini daha iyi anlamak için V8 çöp toplayıcısını ayrıntılı olarak inceleyelim. V8, nesil toplayıcı kullanır. Bellek, genç ve yaşlı olmak üzere iki nesle ayrılır. Genç nesilde tahsis ve toplama hızlı ve sıktır. Eski nesildeki tahsis ve toplama işlemi daha yavaş ve daha seyrektir.

Generational Collector

V8, iki nesil toplayıcı kullanır. Bir değerin yaşı, atanan bayt sayısı olarak tanımlanır. Uygulamada, bir değerin yaşı genellikle hayatta kaldığı genç nesil koleksiyonların sayısıyla yaklaşık olarak belirlenir. Yeterince eski olan değerler eski nesle aktarılır.

Uygulamada, yeni atanan değerler uzun süre kullanılamaz. Smalltalk programlarıyla ilgili bir çalışma, değerlerin yalnızca% 7'sinin genç nesil koleksiyonundan sonra hayatta kaldığını göstermiştir. Çeşitli çalışma zamanlarında yapılan benzer çalışmalar, yeni atanan değerlerin ortalama% 90 ila% 70'inin hiçbir zaman eski nesle aktarılmadığını ortaya koymuştur.

Young Generation

V8'deki genç nesil yığın, from ve to adlı iki alana bölünmüştür. Bellek, kaynak alandan hedef alana ayrılır. Alan dolana kadar ayırma işlemi çok hızlıdır. Alan dolduğunda genç nesil koleksiyonu tetiklenir. Genç nesil koleksiyonu önce kaynak ve hedef alanı değiştirir, eski hedef alan (artık kaynak alan) taranır ve tüm canlı değerler hedef alana kopyalanır veya eski nesle atanır. Tipik bir genç nesil koleksiyonu 10 milisaniye (ms) sürer.

Uygulamanızın yaptığı her tahsisin, alanı tüketmeye ve GC duraklatmaya bir adım daha yaklaşmanızı sağladığını sezgisel olarak anlamanız gerekir. Oyun geliştiriciler, dikkat: 16 ms. kare süresi (saniyede 60 kareye ulaşmak için gerekli) sağlamak amacıyla uygulamanızın sıfır tahsis yapması gerekir. Bunun nedeni, tek bir genç nesil koleksiyonunun kare süresinin çoğunu tüketmesidir.

Genç nesil yığın

Eski Nesil

V8'deki eski nesil yığın, toplama için işaretle ve sıkıştır algoritması kullanır. Eski nesil tahsisleri, bir değer genç neslden eski nesle aktarıldığında gerçekleşir. Eski nesil koleksiyonu her gerçekleştiğinde genç nesil koleksiyonu da yapılır. Uygulamanız birkaç saniye içinde duraklatılır. Eski nesil koleksiyonlar sık kullanılmadığı için bu durum pratikte kabul edilebilir.

V8 GC Özeti

Atık toplama ile otomatik bellek yönetimi, geliştirici üretkenliği için mükemmeldir ancak her değer atadığınızda atık toplama duraklatılmasına daha da yaklaşırsınız. Çöp toplama duraklamaları, uygulamanızın akıcılığını bozarak takılmalara neden olabilir. JavaScript'in belleği nasıl yönettiğini anladığınıza göre, uygulamanız için doğru seçimleri yapabilirsiniz.

Gmail'i düzeltme

Geçtiğimiz yıl, Chrome Geliştirici Araçları'na eklenen çok sayıda özellik ve hata düzeltmesi, bu aracı her zamankinden daha güçlü hale getirdi. Ayrıca tarayıcı, performance.memory API'de Gmail ve diğer uygulamaların alandan bellek istatistikleri toplayabilmesini sağlayan önemli bir değişiklik yaptı. Bu harika araçlar sayesinde, bir zamanlar imkansız gibi görünen görev kısa süre içinde suçluları bulmaya yönelik heyecan verici bir oyuna dönüştü.

Araçlar ve Teknikler

Alan verileri ve performance.memory API

Chrome 22'den itibaren performance.memory API varsayılan olarak etkindir. Gmail gibi uzun süredir çalışan uygulamalar için gerçek kullanıcılardan gelen veriler çok değerlidir. Bu bilgiler, Gmail'de günde 8-16 saat geçiren ve günde yüzlerce ileti alan ileri düzey kullanıcıları, Gmail'de günde birkaç dakika geçiren ve haftada yaklaşık bir düzine ileti alan daha ortalama kullanıcılardan ayırt etmemizi sağlar.

Bu API üç veri döndürür:

  1. jsHeapSizeLimit: JavaScript yığınının sınırlandığı bellek miktarı (bayt cinsinden).
  2. totalJSHeapSize: JavaScript yığınının, boş alan dahil olmak üzere ayırdığı bellek miktarı (bayt cinsinden).
  3. usedJSHeapSize: Şu anda kullanılan bellek miktarı (bayt cinsinden).

API'nin, Chrome işleminin tamamı için bellek değerleri döndürdüğünü unutmayın. Varsayılan mod olmasa da Chrome, belirli durumlarda aynı oluşturma işleminde birden fazla sekme açabilir. Bu, performance.memory tarafından döndürülen değerlerin, uygulamanızı içeren sekmenin yanı sıra diğer tarayıcı sekmelerinin bellek izini de içerebileceği anlamına gelir.

Geniş Ölçekte Bellek Ölçümü

Gmail, JavaScript'ini yaklaşık 30 dakikada bir bellek bilgilerini toplamak için performance.memory API'yi kullanacak şekilde donattı. Birçok Gmail kullanıcısı uygulamayı günlerce açık bıraktığı için ekip, zaman içinde bellek kullanımındaki artışı ve genel bellek kullanımı istatistiklerini izleyebildi. Gmail'i, rastgele bir kullanıcı örnekleminden bellek bilgileri toplayacak şekilde enstrümante ettikten birkaç gün sonra ekip, bellek sorunlarının ortalama kullanıcılar arasında ne kadar yaygın olduğunu anlamak için yeterli veriye sahip oldu. Bir referans belirlediler ve bellek tüketimini azaltma hedefine yönelik ilerlemeyi izlemek için gelen veri akışını kullandılar. Bu veriler, bellek gerilemelerini tespit etmek için de kullanılabilir.

Alan ölçümleri, izleme amaçlarının ötesinde, bellek ayak izi ile uygulama performansı arasındaki ilişki hakkında da önemli bilgiler sağlar. "Daha fazla bellek daha iyi performans sağlar" şeklindeki yaygın inanışın aksine Gmail ekibi, bellek kullanımı ne kadar büyük olursa yaygın Gmail işlemlerinin gecikmesinin de o kadar uzun olduğunu tespit etti. Bu bilgilerle donanmış olarak, bellek tüketimlerini kontrol altına almak için her zamankinden daha motive oldular.

Geniş Ölçekte Bellek Ölçümü

Geliştirici Araçları Zaman Çizelgesi ile Bellek Sorunlarını Tanımlama

Herhangi bir performans sorununu çözmenin ilk adımı, sorunun varlığını kanıtlamak, yeniden oluşturulabilir bir test oluşturmak ve sorunun temel ölçümünü yapmaktır. Tekrarlanabilir bir program olmadan sorunu güvenilir bir şekilde ölçemezsiniz. Referans ölçüm olmadan performansı ne kadar artırdığınızı bilemezsiniz.

DevTools zaman çizelgesi paneli, sorunun varlığını kanıtlamak için ideal bir adaydır. Web uygulamanız veya sayfanız yüklenirken ve onunla etkileşim kurulurken nerede zaman harcandığını ayrıntılı bir şekilde gösterir. Kaynakları yüklemekten JavaScript'i ayrıştırmaya, stilleri hesaplamaya, çöp toplama duraklamaları ve yeniden boyamaya kadar tüm etkinlikler zaman çizelgesinde gösterilir. Zaman çizelgesi panelinde, bellek sorunlarını incelemek için ayrılan toplam belleği, DOM düğümlerinin sayısını, pencere nesnelerinin sayısını ve ayrılan etkinlik dinleyicilerinin sayısını izleyen bir Bellek modu da bulunur.

Sorunun var olduğunu kanıtlama

Hafıza sızıntısı olduğundan şüphelendiğiniz bir işlem dizisini tanımlayarak başlayın. Zaman çizelgesini kaydetmeye başlayın ve işlem sırasını uygulayın. Tam bir çöp toplama işlemini zorlamak için alttaki çöp kutusu düğmesini kullanın. Birkaç iterasyondan sonra testere dişi şeklinde bir grafik görürseniz çok sayıda kısa ömürlü nesne ayırıyorsunuz demektir. Ancak işlem dizisinin herhangi bir bellek tutmaya yol açması beklenmiyorsa ve DOM düğümü sayısı, başladığınız referans değere geri düşmezse bir sızıntı olduğundan şüphelenmek için iyi bir nedeniniz vardır.

Testere dişi şeklinde grafik

Sorunun varlığını doğruladıktan sonra, DevTools Heap Profiler'dan sorunun kaynağını belirleme konusunda yardım alabilirsiniz.

DevTools Yığın Profili ile Bellek Sızıntılarını Bulma

Profil Oluşturucu panelinde hem CPU profili oluşturucu hem de yığın profili oluşturucu bulunur. Yığın profili oluşturma işlemi, nesne grafiğinin anlık görüntüsünü alarak çalışır. Anlık görüntü alınmadan önce hem genç hem de eski nesiller için atık toplama işlemi yapılır. Diğer bir deyişle, yalnızca anlık görüntü alındığında geçerli olan değerleri görürsünüz.

Heap profilleyicide bu makalede yeterince açıklanamayacak kadar çok işlev vardır ancak Chrome Developers sitesinde ayrıntılı dokümanlar bulunabilir. Burada, Yığın Ayrımı profilleyicisine odaklanacağız.

Yığın Ayrımı Profilleyici'yi kullanma

Yığın Tahsisi profilleyicisi, Yığın Profilleyici'nin ayrıntılı anlık görüntü bilgilerini Zaman Çizelgesi panelinin artımlı güncellemesi ve izlemesiyle birleştirir. Profiller panelini açın, bir Yığın Tahsislerini Kaydet profili başlatın, bir dizi işlem gerçekleştirin ve ardından analizi yapmak için kaydı durdurun. Ayırma profilleyici, kayıt boyunca düzenli aralıklarla (50 ms. kadar sıklıkta) yığın anlık görüntüleri ve kayıt sonunda son bir anlık görüntü alır.

Yığın ayırma profil aracı

Üstteki çubuklar, yığınta yeni nesneler bulunduğunda gösterilir. Her çubuğun yüksekliği, yeni atanan nesnelerin boyutuna karşılık gelir. Çubukların rengi, bu nesnelerin nihai yığın anlık görüntüsünde hâlâ etkin olup olmadığını gösterir: Mavi çubuklar, zaman çizelgesinin sonunda hâlâ etkin olan nesneleri, gri çubuklar ise zaman çizelgesi sırasında atanan ancak o zamandan beri çöp toplanan nesneleri gösterir.

Yukarıdaki örnekte bir işlem 10 kez gerçekleştirilmiştir. Örnek program beş nesneyi önbelleğe aldığından son beş mavi çubuk beklenir. Ancak en soldaki mavi çubuk olası bir sorunu gösterir. Ardından, söz konusu anlık görüntüyü yakınlaştırmak ve bu noktada yakın zamanda ayrılan nesneleri görmek için yukarıdaki zaman çizelgesindeki kaydırma çubuklarını kullanabilirsiniz. Yığıntaki belirli bir nesneyi tıkladığınızda, yığın anlık görüntüsünün alt kısmında bu nesnenin koruma ağacı gösterilir. Nesnenin saklama yolunu inceleyerek nesnenin neden toplanmadığını anlamak için yeterli bilgiye sahip olabilirsiniz. Gereksiz referansı kaldırmak için gerekli kod değişikliklerini yapabilirsiniz.

Gmail'in Bellek Krizini Çözme

Yukarıda bahsedilen araç ve teknikleri kullanarak Gmail ekibi, birkaç hata kategorisi tespit etti: sınırsız önbellekler, aslında hiçbir zaman gerçekleşmeyecek bir şeyin olmasını bekleyen sonsuz şekilde büyüyen geri çağırma dizileri ve etkinlik dinleyicilerinin istemeden hedeflerini tutması. Bu sorunlar düzeltilerek Gmail'in toplam bellek kullanımı önemli ölçüde azaltıldı. %99'luk kullanıcı grubu, öncekinden% 80 daha az bellek kullandı ve ortalama kullanıcıların bellek tüketimi yaklaşık %50 azaldı.

Gmail bellek kullanımı

Gmail daha az bellek kullandığından GC duraklatma gecikmesi azaltıldı ve genel kullanıcı deneyimi iyileştirildi.

Ayrıca, Gmail ekibinin bellek kullanımıyla ilgili istatistikler toplaması sayesinde Chrome'da çöp toplama gerilemelerini tespit edebildik. Daha açık belirtmek gerekirse, Gmail'in bellek verileri, ayrılan toplam bellek ile etkin bellek arasındaki boşlukta önemli bir artış göstermeye başladığında iki parçalanma hatası keşfedildi.

Harekete Geçirici Mesaj

Kendinize şu soruları sorun:

  1. Uygulamam ne kadar bellek kullanıyor? Çok fazla bellek kullanıyor olabilirsiniz. Bu durum, yaygın inanışın aksine genel uygulama performansını olumsuz yönde etkiler. Doğru sayının tam olarak ne olduğunu bilmek zordur ancak sayfanızda kullanılan ekstra önbelleğe alma işlemlerinin ölçülebilir bir performans etkisi olduğunu doğruladığınızdan emin olun.
  2. Sayfamda sızıntı var mı? Sayfanızda bellek sızıntısı varsa bu durum yalnızca sayfanızın performansını değil, diğer sekmeleri de etkileyebilir. Sızıntıları tespit etmek için nesne izleyiciyi kullanın.
  3. Sayfam ne sıklıkta GC yapıyor? Chrome Geliştirici Araçları'ndaki Zaman Çizelgesi panelini kullanarak GC duraklatmalarını görebilirsiniz. Sayfanız sık sık GC yapıyorsa büyük olasılıkla çok sık ayırma işlemi gerçekleştiriyor ve genç nesil belleğinizde çok fazla işlem yapıyorsunuzdur.

Sonuç

Başlangıçta kriz içindeydik. Özellikle JavaScript ve V8'de bellek yönetiminin temellerini ele aldık. Chrome'un en son sürümlerinde bulunan yeni nesne izleyici özelliği de dahil olmak üzere araçları nasıl kullanacağınızı öğrendiniz. Bu bilgilerle donanan Gmail ekibi, bellek kullanımı sorununu çözdü ve performansta artış elde etti. Web uygulamalarınızda da aynı işlemi yapabilirsiniz.