Android uygulama performansı ile ilgili seriye Compute‘le devam ediyorum. Bir önceki konu olan renderingle ilgili anlatımıma ilgili linkten erişebilirsiniz.

Precompiler’dan compiler’a, optimizer’a, kodun kendisine kadar birçok şey cihaz üzerinde çalışmaktadır. Bu yüzden de compute performansı çok önemlidir. Ki bu da aslında algoritmaların, computing process’lerinin nasıl işletildiğiyle ilgilidir. (Compiler’ın kodu generate etmesi ve virtual machine’in hardware üzerinde onu işletmesi)

Slow function performance‘la başlayalım. Siz aslında spesifik bir sorunu çözmek için bir kod parçası yazarsınız ancak sonra fark edersiniz ki kodu execute ettiğinizde tahmin ettiğinizden çok daha uzun sürer.

Kodunuzu nasıl yazdığınız, hardware’in hangi programlama dilini kullandığından silikon çiplerin nasıl yapılandırıldığına kadar bağlı olarak, performansı etkiler. Kodunuzu optimize etmek için sistemin nasıl çalıştığını anlamak zorundasınız. Screen Shot 2015-11-11 at 00.46.26

Slow function performans genelllikle iki şekilde gelir. İlk olarak, tek bir slow function form’unuz vardır. Bunun anlaşılması kolaydır. Normalde çalışmasını beklediğiniz süreye göre 2x ya da 5x süren bir fonksiyonunuz vardır. Bu durumda aslında slow function olan kodunuzu bulup, inceleyip, sorunu bulup, düzeltebilirsiniz.

Screen Shot 2015-11-11 at 00.50.55İkinci tipi çözmek daha zordur. Yüzlerce fonksiyonunuzun her biri normalde sürmesi gereken süreden 1 ms daha uzun sürerse, tüm programı işlettiğinizde extra 100 milyonlarca ms ile sonuçlanır. Burada çözümü bulmak daha zordur çünkü aslında küçük kazanımlar için programınızın her bir parçasını incelenmeniz gerekir. Bu tip küçük performans problemlerini çözmenin yolu ise profiling‘den geçmektedir. Ki bu da aslında hangi kısımlarının daha yavaş çalıştığını görebilmek için kodunuzu ölçümlemenizdir. Böylece kodunuzun hangi kısımlarının daha yavaş ya da uzun sürdüğünü görebilirsiniz. Sonrasında ise aynı işlemi satırlar üzerinde yaparak hatalı kısmı bulabilirsiniz. Ancak eğer uzman değilseniz bu şekilde kodunuzdaki sorunu bulmanız gerçekten zor olabilir.

Android SDK bu tip problematik durumlar için faydalı tool’lar içermektedir.

Profiling with Traceview

Traceview, kodunuzdaki logları trace etmek için Debug sınıfını kullanarak execution logları için kullanılan bir grafik görüntüleyicidir. Traceview, uygunamanızı debug etmenize ve performansınız profile etmenize yardımcı olur.

Tool->Android->Android Device Monitor

DDMS’de cihanızda çalışan işlemlerden hangisini profile etmek istiyorsanız onun üzerinde tıklayarak ardından aşağıdaki resimde işaretlediğim butona tıklarsanız, record etmeye başlamış olacaksınız. Trace işlemini kendi uygulamanız üzerinde deneyebilir ya da google’ın sample project olarak yüklediği project sunshine’ı kullanabilirsiniz. Uygulamanın kurulumu için gerekli instructionlar githubda bulunmakta.

ddm

Tıkladığınız anda aşağıdaki ekranla karşılacaksınız. Burda sample ve 1000 microsecond seçeneğiyle ilerleyebilirsiniz. Bunun anlamı her 1 milisecond’da bir profiling’in uygulamanızdan hangi methodun o an işletildiğiyle ilgili veri alacağıdır. İkinci seçenek olan Trace based profiling tüm methodları trace edecektir.

 

 

Screen Shot 2015-11-15 at 11.41.35

Ok, dedikten sonra trace işlemi başlamış olacak. Bu arada seçtiğiniz uygulamada gezinebilirsiniz. Tekrar durdurmak için ise aşağıdaki resimdeki gibi aynı ikona tıklayabilirsiniz.

gettingstarted_image007

Aşağıdaki gibi bir çıktıyla karşılaşacaksınız. Resimde de gördüğünüz gibi TraceView’in iki paneli bulunmakta. Biri Timeline diğeri de Profiling Panelleri. Profiling sonuçlarını filtrelemek için en altta yer alan Find‘ı kullanabilirsiniz. Mesela, spesifik olarak bir methodun ne kadar süreyle çalıştığını öğrenmek için, search ederek bulabilirsiniz.

gettingstarted_image008Timeline paneli kodunuzun zaman akış çizelgesidir. gettingstarted_image009

  • Her bir satır bir thread’i gösterir.
  • Timeline üzerindeki her bir bar farklı bir methodu simgeler.
  • Her renk farklı bir method içindir ve bir method her execute edildiğinde aslında siz aynı görüyor olursunuz.
  • Son olarak barın genişliği de methodun çalışmasının ne kadar süre aldığını göstermektedir.

Barlardan birinde çift tıklarsanız. Aşağıda yer alan profiling panelinde karşılığını görmüş olursunuz. Böylece aslında uzun sürdüğünü gördüğünüz bir bara tıkladığınızda aşağıda karşılığını görecek ve müdahale etmeniz gereken methodu da kolayca bulmuş olacaksınız.

Profiling paneli ise methodlarınızın listesini göstermektedir.gettingstarted_image011

  • Bir method seçenebilir ve böylece onu çağıran kim ya da o hangi methodları çağırıyor görebilirsiniz.
  • Seçili method aynı zamanda Timeline panelinde de highlight edilmiş olacaktır.
  • Sütunlar, exclusive ve inclusive CPU ve real times, yüzdeleri, oranları, ve bir methodun ne kadar sıklıkla çağrıldığı gibi bilgileri içermektedir.
  • Exclusive time, methodunuzun kendisinin ne kadar vakit harcağını göstermektedir. Spesifik olarak bir methodla ilgili sorunları bulmak istediğinizde bunu kullanabilirsiniz.
  • Inclusive time, methodunuz ve o methodun çağırdığı tüm methodların toplam süresiyle ilgilidir. Call tree’nizle ilgili bir problemi bulmak isterseniz de inclusive time’a bakmak faydalı olacaktır.
  • Calls+Rec sütunu ise bir methodun recursive olarak kaç defa çağrıldığını gösterir ki performans sorunlarını bulmak için çok faydalı olacaktır.

Batching And Caching

Batching ve Caching için performans tekniklerinden en önemli iki tanesidir diyebiliriz. Aslında bunlar en iyi bildiklerimiz ve zaten uyguladığımız işlemler ancak yine de kısaca değinmekte fayda var.

Bazı fonksiyonlar ya da işlemler her ne kadar yazılması gerektiği gibi yazılmış da olsa yapılması gereken işlemlerden dolayı diğerlerine göre daha uzun sürüyor ve de sürmesi gerekiyor olabilir. Buna örnek olarak, execute etmeden önce bellekte yeni bir alana data yüklememiz gerekiyor olabilir ya da search işleminden önce dataları sort etmemiz gerekebilir. Bu işlemleri, defalarca yaptığımızda ise ciddi performans problemleri yaratabiliriz.

Bu performans problemlerini çözmek için yapılması gereken işlem ise batching‘dir ki bu da execution başına yapılan işlemleri elimine etmekten geçmektedir. Bu aslında en çok kullanmadan önce datayı hazırlama aşamasında kullanılmaktadır.

Screen Shot 2015-11-15 at 12.52.23Mesela search işlemi yapmamız gerekiyor ve search yapmadan önce ise kullanacağımız
search algoritmasına bağlı olarak da bir sort işlemi yapmamız gerekli. En basit şekliyle bir target array ve target value’yu vererek bir fonksiyon yazarsınız. Sort edip ardından aranan değeri search edersiniz. Ama eğer 10.000 value’yu search edecekseniz ve target array’iniz de milyonlarca değerden oluşuyorsa, bu durumda birden bire her bir test için yükünüz tonlarca artmış olacak. Bu durumda aslında cevap basit, her seferinde sort işlemini yapmaktansa bi kere tüm listeyi sort edip, bu bilgiyi tutup ardından search işleminizi bu sort edilmiş liste üzerinde yapmalısınız. Bu tam olarak batching işlemidir.

Screen Shot 2015-11-15 at 12.55.14Screen Shot 2015-11-15 at 13.16.51

Batching işlemine benzer ve yine çok önemli diğer bir teknik ise caching’dir.  Modern bilgisayar mimarisinde, RAM’in tüm amacı yakın zamanda erişilen bilgileri cache’de tutup daha kısa sürede erişmenizi sağlamaktadır. Ya da örneğin data center’ların tüm amacı, sıklıkla erişilen datayı saklamaktır ki böylece her seferinde binlerce mil ötedeki server’lara gitmemiş oluruz. Kodumuza geri dönersek, caching için aslında optimizasyonların olması gereken en genel yer data’nın işlenmesi sürecidir. Örneğin, for döngünüzün içerisinde her seferinde aynı sonucu verecek bir işlem yapıyorsanız, bunu aslında dışında yapmanız gerekir. Özetle, batching ve caching kullanarak uygulamanızı kesinlikle daha performanslı hale getirebilirsiniz.

Blocking the UI Thread

Performans için her bir fonksiyonun olabildiğinde etkin bir şekilde çalışması çok önemlidir tabi bununla beraber kodunuzun nerede ve ne zaman çalıştırıldığı da eşit değerde önemlidir.

Screen Shot 2015-11-15 at 13.58.51

Android uygulamasını başlattığınızda ilk olarak main(ui) thread yaratılır. Main thread kodunuzun çalıştırılması, eventlerin ilgili view’lerle ilişkilendirilmesi ve drawing fonksiyonlarının çalıştırılması gibi işlerden sorumludur. Temel olarak main thread, uygulamanızın olduğu yerdedir.

Screen Shot 2015-11-15 at 14.03.45

Main thread’e UI thread de denmesinin sebebi ise kullanıların etkileşimde olduğu thread olmasıdır. Örnek olarak, ekrandaki bir butonu dokunduğunuzda, UI thread view’in touch event’ine dispatch eder, view button’ın state’ini pressed olarak set eder ve event queue’suna bir istek gösterir. Ardından ui thread, bu isteği işler ve butonu kendisini pressed olarak çizmesi için notify eder. Her bir touch event handler’ınızda bu işlem bütün akışın ortasında tekrarlanacaktır ve eğer onTouch fonksiyonlarınızın işlenmesi uzun sürerse, draw ve kullanıcıların görebilmesi için view’inizin update işlemi öncesi bekleyecek olduğunuz süre de aynı şekilde artacaktır. (Aşağıdaki process’i görebilirsiniz.)

Screen Shot 2015-11-15 at 14.14.36

Yukarıdaki resmi inceleyerek gidersek, inputunuzun handle edileceği kod’la, rendering ve update kodları aynı döngüyü paylaşırlar. Bu da UI’ın, hesaplamanın ortasındayken draw edememesi anlamına gelmektedir, ki bu sırada touch event’lerle, network erişimiyle ya da database query’lerini de handle etmeye çalışıyor olabilir.

Screen Shot 2015-11-15 at 14.27.11

En basit olarak bu 16 ms’ı kaçırmanıza sebep olabilir. (dropping rendering frame)

Peki bunu nasıl çözeriz?

Main thread’de yapılması zorunlu olmayan tüm işleri tanımlamanız gerekir ki bu da draw işlemi için buna ihityacım var mıyla sorgulanabilir. Sonrasında bu işlemleri tamamen farklı bir thread’e taşırsınız, böylece ui thread’i kitlememiş olur.

Örnek olarak, eğer bir submit butonunuz var ve bu butona bastığınızda bir siparişi iletiyorsanız, onayı alıp, mail gönderme işlemi tamamen farklı bir thread’de yapılabilir.

Screen Shot 2015-11-15 at 14.34.06

Ek olarak, Android UI tookit trade-safe değildir. Yani, eğer isterseniz worker thread’lerden UI’ı tetikleyebilirsiniz ama tetiklememelisiniz. UI’a yaptığınız tüm işlemleri UI thread’de yapmalısınız. Android’in iki çok basit, single thread modeli kuralı vardır:

  1. UI thread’i blocklama
  2. Android UI tookit’ine UI thread dışından erişme

Mesela örnek olarak, aşağıdaki kod url’den imaj çekiyor ve bunu sonrasında görüntülüyor ve bunu ayrı bir thread’de yapıyor. Ancak burda en temel kural olan UI toolkitine dışardan erişilmemesi gerektiği kuralı çiğnenmiş durumda. Bu örnek UI thread yerine imajgeView’i worker thread’den update diyor ki bu çok farklı sorunlara yol açabilir.

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

Bu sorunu çözmek için, Android worker thread’lerden UI thread’e ulaşmak için çeşitli methodlar sunmaktadır:

Mesela buradaki sorunu aşağıdaki gibi. View.post(Runnable) kullanarak çözebilirdiniz.

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap =
                    loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

Bu durumda network işlemi ayrı bir thread’de yapılmış ve imageView UI thread’den update edilmiş oldu.

Daha kompleks durumda Handler kullanmayı deneyebilirsiniz ama en doğru çözüm AsyncTask’ı extend etmek olacaktır.

Async Tasks: AsyncTask kullanıcı arayüzünüzdeki async yapabileceğiniz işlerini yapabilmenize olanak sağlar. Blocklayan işlemleri worker thread’de yapar ve ardından sonuçları UI thread’e iletir. Async task kullandığınızda, thread’leri kendiniz handle etmeniz gerekmez.

Yapısı basitçe aşağıdaki gibidir. doInBackground methodunda blocklayan işlemleri çalıştırır ve bittiğinde onPostExecute() methoduna haber verir.

public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }

    /** The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

Container Performance:

Doğru data structure’ı kullanmak da çok önemlidir. Fonksiyoneliteyi sağlarken aslında hangi veri yapısının daha performanslı çalıştığı da önemlidir. Mesela aşağıda hashmap’lerin karşılaştırmasını görüyorsunuz.

Screen Shot 2015-11-15 at 17.29.51

Android MPI’ları profile ederek sorunlu olan veri yapısını tespit edebilirsiniz.

Analyzing UI Performance with Systrace

Systrace Android cihazınızdaki zamanla ilgili bilgileri toplar ve kontrol eder. (trace) Zamanın ve CPU döngülerinin nerede harcandığı, her bir thread’in ve işlemin verilen bir zamanda ne yaptığını gösterir. Ayrıca, recycling’den content’in rendering’ine kadar, tracing bilgisi  ve oluşan problemlerle ilgili nasıl çözebileceğimize dair öneriler sunar.

Basitçe trace etmek istediğiniz kod bloğunu aşağıdaki iki kod satırının arasına koymalısınız.

Trace.beginSection("Data Structures");
// TODO:
Trace.endSection();

overview

Yukarıdaki örnek, 5 snlik scroll yapan bi uygulamanın performansı kötü olduğunda oluşacak çıktının nasıl olduğunu göstermektedir.

Her bir app, frame circle’larından oluşan bir satır içerir. Bu da çoğunlukla yeşil olur. Eğer sarı ya da kırmızı olursa, bu 16.6 milisecond’lık sürenin aşılmış olduğu anlamına gelmektedir. Zoom-in yaparak, uzun süren frame’ler hakkında bilgi alabilirsiniz.

 

frame-unselected

Sorunlu olduğunu gördüğünüz frame’e tıkladığınızda, size bununla ilgili çözüm önerisi de sunacaktır. Mesela aşağıdaki ekranda, ListView’in getView methodunu incelememizi önermektedir.

frame-selected

Ayrıca, sağ tarafta bulunan alert tabına tıklayarak, her bir alerti de görebilirsiniz.

frame-selected-alert-tab

Referanslar: