2012年4月15日 星期日

[AndroidDev] Designing for Performance

  • Battery life is one reason you might want to optimize your app even if it already seems to run “fast enough”.
  • Choosing the right algorithms and data structures should always be your priority.
    • Using the right data structures and algorithms will make more difference than any of the advice here
  • Considering the performance consequences of your API decisions will make it easier to switch to better implementations later (this is more important for library code than for application code).
  • Different versions of the VM running on different processors running at different speeds.
  • There are also huge differences between devices with and without a JIT: the “best” code for a device with a JIT is not always the best code for a device without.

There are two basic rules for writing efficient code:
  • Don’t do work that you don’t need to do.
  • Don’t allocate memory if you can avoid it.


Avoid Creating Unnecessary Objects *
過多物件會占據太多空間,觸使系統 GC 的頻率變高,而在 GC 時會使得使用者在操作中感覺頓頓的,因為系統會暫停(lock)所有東西。
  • Fewer objects created mean less-frequent garbage collection, which has a direct impact on user experience.
  • If you allocate objects in a user interface loop, you will force a periodic garbage collection, creating little “hiccups” in the user experience.
  • 改善範例: 
    • 如果回傳的字串會再 append 到 StringBuffer,那麼可以直接傳入 StringBuffer 去 append 字串。
    • When extracting strings from a set of input data, try to return a substring of the original data, instead of creating a copy. You will create a new String object, but it will share the char[] with the data. (The trade-off being that if you’re only using a small part of the original input, you’ll be keeping it all around in memory anyway if you go this route.)
    • 使用一維矩陣取代多維矩陣;使用 primitive types array instead of Object type array. 
      • But this also generalizes to the fact that two parallel arrays of ints are also a lot more efficient than an array of (int,int) objects.
      • But tuples of two-dimension objects is usually better to trade good API design for a small hit in speed. 

Performance Myths
  • On devices without a JIT
    • 型別有指定特定的 type 會比使用 interface 有效率。For example, it was cheaper to invoke methods on a HashMap map than a Map map, even though in both cases the map was a HashMap
    • Caching field accesses is about 20% faster than repeatedly accesssing the field. 
  • With a JIT, field access costs about the same as local access, so this isn’t a worthwhile optimization unless you feel it makes your code easier to read. (This is true of final, static, and static final fields too.)

Prefer Static Over Virtual
If you don’t need to access an object’s fields, make your method static.

Avoid Internal Getters/Setters
  • Virtual method calls are expensive, much more so than instance field lookups.
  • It’s reasonable to follow common object-oriented programming practices and have getters and setters in the public interface, but within a class you should always access fields directly.

Use Static Final For Constants
This optimization only applies to primitive types and String constants, not arbitrary reference types. Still, it’s good practice to declare constants static final whenever possible.
// Compiler 會產生  method(a class initializer method),在第一次使用這個 class 時會被執行。
// The method stores the value 42 into intVal, and extracts a reference from the classfile string constant table for strVal.
// When these values are referenced later on, they are accessed with field lookups.
static int intVal = 42;
static String strVal = "Hello, world!";
We can improve matters with the “final” keyword:
// 不會產生  method,因為常數會存在於 dex file 的 static field initializers。
// Code that refers to intVal will use the integer value 42 directly, and accesses to strVal will use a relatively inexpensive “string constant” instruction instead of a field lookup. 
static final int intVal = 42;
static final String strVal = "Hello, world!";

Use Enhanced For Loop Syntax (i.e for-each loop)
  • The enhanced for loop can be used for collections that implement the Iterable interface and for arrays.
  • 只要是 Collection 介面的實作物件,都可以用 foreach 語法,新增於 J2SE 5.0 之後。
// 如果走訪的是陣列,編譯器會自動展開為以下的程式碼:
public void go(int ai[]) {
    int ai1[] = ai;
    int i = ai1.length;
    for (int j = 0; j < i; j++) {
        int k = ai1[j];
        System.out.println(k);
    }
}

// 若是 Collection 的實作物件,其實編譯器會展開為:
public void go(Collection collection) {
    String s;
    for (Iterator iterator = collection.iterator(); 
        iterator.hasNext(); System.out.println(s))
        s = (String)iterator.next();
}

// 無論是 Collection、List 或 Set,展開後皆利用 iterator() 方法傳回 Iterator 物件,
// 並利用 Iterator 來移動、傳回下一個物件,這是 Iterator 模式 的實現。
// 所以其實在 for-each 中會多產生個 Iterator 物件,因此可寫成以下程式。
public void go(Collection collection) {
    if (collection.size() > 0)
        for(String element : collection) {
            System.out.println(element);
        }
}
To summarize:
  • Collections 使用 for-each loop; ArrayList 使用 for loop(?)。
    • Use the enhanced for loop by default, but consider a hand-written counted loop for performance-critical ArrayList iteration.
    • With an ArrayList, a hand-written counted loop is about 3x faster (with or without JIT), but for other collections the enhanced for loop syntax will be exactly equivalent to explicit iterator usage.

Consider Package Instead of Private Access with Private Inner Classes
Inner class 使用 Package(default) modifier,而不要使用 Private。
public class Foo {
    private class Inner {
        void stuff() {
            Foo.this.doStuff(Foo.this.mValue);
        }
    }

    private int mValue;

    public void run() {
        Inner in = new Inner();
        mValue = 27;
        in.stuff();
    }

    private void doStuff(int value) {
        System.out.println("Value is " + value);
    }
}

因為 VM 會認為從 Foo$Inner 直接去存取 Foo 的 private member 是不合法的(Foo and Foo$Inner are different classes),因此 compiler 會產生下列 methods(Accessors),也就是 Foo$Inner 事實上是透過這些 methods 來存取 Foo 中的 private members。
// package
static int Foo.access$100(Foo foo) {
    return foo.mValue;
}
// package
static void Foo.access$200(Foo foo, int value) {
    foo.doStuff(value);
}
Accessors are slower than direct field accesses! 所以將 inner class 宣告為 Package,但這樣也表示此 inner class 可以直被同個 package 下的 classes 存取,因此不可使用在 public API.

Use Floating-Point Judiciously
避免使用 float type。

Know And Use The Libraries

Use Native Methods Judiciously
  • Native code isn’t necessarily more efficient than Java. For one thing, there’s a cost associated with the Java-native transition, and the JIT can’t optimize across these boundaries.
  • Native code is primarily useful when you have an existing native code base that you want to port to Android, not for “speeding up” parts of a Java app.

Closing Notes
  • One last thing: always measure
  • Before you start optimizing, make sure you have a problem. Make sure you can accurately measure your existing performance, or you won’t be able to measure the benefit of the alternatives you try.


* Reference
- Designing for Performance
- 繼承
- Android App開發效能你知多少?
- Dalvik 記憶體管理
- Just-in-time compilation
- 初探JIT compiler 與 Android Dalvik VM
- Profiling with Traceview and dmtracedump
- Java Gossip: autoboxing、 unboxing
- Java Essence: 神奇的 foreach

沒有留言:

張貼留言