2012年7月16日月曜日

Android 4.0で発生するパフォーマンス劣化の問題について

下記の記事で記載している性能劣化の問題ですが、Razrに対して2012年9月に提供されたシステムアップデートを適用することで、だいぶ改善されることを確認しました。ただし、まだまだ不安定です。概ね60fps程度で動くようになったのですが、バラツキが非常に激しいため、Android2.3の頃のような安定度はまだありません。
2012年9月7日・記

前の記事に書いている内容と重なる部分があるかと思いますが、Android4.0(3.0を含む?)で発生する、アプリケーションのパフォーマンス劣化に関する問題について、今、私が把握している情報を纏めておきます。

(1)どんな問題?

Android4.0(4.0.4)では、SurfaceViewCanvasを使って画面描画を行っているアプリケーションの動作性能(パフォーマンス)が極端に劣化する問題が発生します。
私はこの現象を、Motorola社のRazrという機種で確認しました。

SurfaceViewCanvasとは、2Dゲームなどでよく使われる「高速な描画」を行うための機能です。
私(SuzukiPlan)が開発しているアプリの場合、次のようなロジックでこれを利用しています。
/*
 *============================================================================
 * サーフェース・ビュー
 *============================================================================
 */
class VgeSurfaceView extends SurfaceView implements SurfaceHolder.Callback,Runnable {
    public Thread thread;
    public boolean isAttached;
    private final int XSIZE=240;
    private final int YSIZE=320;
    private Bitmap vram;
    private Rect vramRect;
    private Rect screenRect;
    private Paint screenPaint;
    /*
     *------------------------------------------------------------------------
     * コンストラクタ
     *------------------------------------------------------------------------
     */
    public VgeSurfaceView(Context context) {
        super(context);
        vram=Bitmap.createBitmap(XSIZE,YSIZE,Bitmap.Config.RGB_565);
        vramRect=new Rect(0,0,XSIZE,YSIZE);
        getHolder().addCallback(this);
    }
    /*
     *------------------------------------------------------------------------
     * サーフェース変更の検出
     *------------------------------------------------------------------------
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        int xPos=0;
        int yPos=0;
        float wX=(float)width;
        float wY=(float)height;
        if((float)YSIZE*(wX/XSIZE)<wY) {
            wY=YSIZE*(wX/XSIZE);
            yPos=0;
            height=(int)wY;
        } else {
            wX=XSIZE*(wY/YSIZE);
            xPos=(width-(int)wX)/2;
            width=(int)wX;
        }
        Log.i("SHOT04-J","Width="+width);
        Log.i("SHOT04-J","Height="+height);
        screenRect=new Rect(xPos,yPos,width+xPos,height+yPos);
        screenPaint=new Paint();
    }
    /*
     *------------------------------------------------------------------------
     * サーフェース作成の検出 (スレッド起動)
     *------------------------------------------------------------------------
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isAttached=true;
        thread = new Thread(this);
        thread.start();
    }
    /*
     *------------------------------------------------------------------------
     * サーフェース破棄の検出 (スレッド停止)
     *------------------------------------------------------------------------
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        isAttached = false;
        while (thread.isAlive());
    }
    /*
     *------------------------------------------------------------------------
     * メインループ (スレッド)
     *------------------------------------------------------------------------
     */
    @Override
    public void run() {
        SurfaceHolder holder=getHolder();
        Canvas canvas;
        int rc=0;
        while (isAttached) {
            rc=SHOT04.setVram2(vram);
            canvas=holder.lockCanvas();
            if(null!=canvas) {
                canvas.drawBitmap(vram,vramRect,screenRect,screenPaint);
                holder.unlockCanvasAndPost(canvas);
            }
            if(rc!=0) break;
        }
        if(0!=rc) {
            System.exit(0);
        }
    }
}

上記の青色の部分が問題が発生する箇所です。
問題の処理は、
 1. 240x320(QVGA)サイズのBitmap(AndroidBitmap)を更新(SHOT04.setVram2
 2. そのBitmapを表示(lockCanvasdrawBitmapunlockCanvasAndPost
という単純な処理を繰り返し実行しています。
Android2.3では、この処理では60fpsの性能を(かなりの余裕をもって)キープできていました。
しかし、Android4.0では、40fps~50fps程度の性能しか出なくなります。(※Razrの場合)

(2)原因は何?

推測ですが、Android2.3の場合、上記の描画処理でハードウェアアクセラレーション(GPU)が作用してました。
※なお、GPUを搭載していない端末の場合を除きます。(Android2.3を搭載しているスマホのほぼ全てに搭載していますが、ただし、一部の端末では搭載していない場合があるようです。GPUを搭載しているかどうかについては、あなたが所有する端末の公式サイトなどに掲載されているスペック表で確認できます)

しかし、Android4.0の場合、上記の描画処理でGPUが作用せず、ソフトウェア描画になったものと思われます。
その結果、遅延が発生したものと考えています。
その原因について調査した結果、下記のフォーラムを発見しました。
https://groups.google.com/forum/?fromgroups#!topic/android-developers/CK_jkHEQoOM

なお、上記フォーラムはAndroid3.0に関するものなので、本件とは同件では無いかもしれません。
ただし、質問者のコンディションと上記の実装が一致するので、私は同じ問題だと考えています。
上記の回答にあるGoogleのAndroid開発者、Romain Guy氏曰く、
In this case you won't get hardware acceleration.
この場合(SurfaceViewとlockCanvasを使用する場合)、ハードウェアアクセラレーションは作用しません。
We couldn't add this feature on time for Android 3.0. Sorry about this.
我々(Google)は、この機能をAndroid3.0で実装する時間がありませんでした。申し訳ありません。
とのことです。
私の和訳は若干怪しい部分があります。具体的には、on time forのところ。
Google翻訳の場合、「Android3.0の時」という風になりました。
ただ、「could not X on time Y」って「XがYに間に合わなかった」って意味では?(※カンです)
という訳で、上記のような翻訳にしておきました。

ただ、上記の記事は1年半ぐらい前のものです。
そして、Android4.0ではなくAndroid3.0に関するものです。
なので、不良ではなく仕様の可能性が高いと思っています。

追記:あと、そもそもAndroid2.3の時もこのケースではGPUは働いていなかった可能性もあります。
つまり、ソフトウェア描画だったけど、極端に性能が劣化してしまったということ。
劣化の度合いが大きいので、その可能性は低いと想定していますが。
まぁ、結局のところ、本当の原因はAndroidのソースコードを追いかけなければ分からない・・・ということ。

(3)アプリ利用者が問題を回避する手段は?

ありません。
Android4.0の場合、「設定」の「開発」で「全ての2D描画処理をGPUで行う」というモードがあります。
しかし、そのモードを設定しても、上記問題を回避することはできません。

(4)アプリ開発者が問題を回避する手段は?

■マニフェストによる回避は不可能
しばしば、android:hardwareAccelerated="trueをマニフェストに定義すれば良いんでない?」という意見を見ますが、その方法での回避は不可能です。
まず、この設定項目のデフォルト値は、"true"です。
そもそも、この設定項目は、GPUの使用を抑制するために定義するものです。
(そして、上記(3)の設定(強制GPUモード)は、falseを強制的にtrueにするものです)
なので、マニフェストの定義で、問題を解決することはできません。

[訂正]
上記については、若干間違いがありました。(マニフェストで回避不可能な点は正しいです)
デフォルト値はfalseでした。
ただし、実機確認した限り、SurfaceView+lockCanvasではtrueにしてもハードウェアアクセラレーションは作用しません。性能劣化があまりにも極端だったので、GPUが作用していたものがしなくなったものと実動作から判断しましたが、Android3.0以降(4.0以降を含む)は、有り得ないレベルで性能がデグっているだけという可能性もあります。(これについては、Androidのソースコードを追いかけて調べるしかないですが、未だ調べてません)

■GLSurfaceView(OpenGL/ES)へ移行による回避
この回避方法を用いれば、大半のアプリケーションは問題を回避できます。

しかし、(私が開発しているアプリのように)ピクセル単位で描画内容を更新するアプリの場合、この方式での問題回避は不可能です。ピクセル単位で描画内容を更新するアプリとは、例えば、エミュレータ(SNES,NES,MAME)などが該当します。

ピクセル単位で描画内容を更新するアプリがOpenGL/ESで目的とする処理を実現しようとする場合、テクスチャのフレーム単位での更新が必要になります。しかし、画像情報をテクスチャに変換する処理は(GPUがやっていますが)メチャクチャ遅い欠点があります。Android(やiPhone)のテクスチャは、GPUの内部では「PVRTC」という圧縮形式で保持する必要があるのですが、この圧縮方式への変換に極めて大きなオーバヘッドを要するため、240x320もの大きなサイズのテクスチャを60fpsで変換することはRazrで検証した限り不可能でした。(これについての詳しい検証内容は、前の記事を参照してください)

■RSSurfaceViewへの移行による回避
この方式については検証していません。
しかし、私の場合、そもそもこの方式を採用することが不可能です。
というのも、RSSurfaceViewはAPI level 11以降でないと使えません。
API level 11以降の場合、Android2.3以前での動作が不可能になります。
API level 11以降は、Android3.0以降で動作するアプリケーション専用

現在、世の中で最も普及しているAndroid2.3を切るという選択肢は、当面ありえません。
今のところ、Android3以降の機能を使いたいアプリ限定で利用可能な選択肢といえます。
少なくとも、ゲーム開発者でそれを考えている人はほぼ居ないと思います。

■結論
大半のアプリは、GLSurfaceViewへの移行で問題を解決できます。
私が検証してみた限り、けっこう大変な作業になると思いますが。。。
ガッツがある方は、がんばって移行してください。
しかし、ピクセル単位で描画内容を更新するアプリの場合、アプリ開発者でも問題の回避は不可能です。

つまり、Android4.0(or3.0)でデグってます。
Googleが修正しない限り、何ともなりません。
「Googleが修正しない限り」というのは、必ずしも正しくはないかもしれません。今では、Motorola側の作りこみ部分の不具合の可能性の方が高いかも・・・と、思っていたりします。
2012年9月7日・記
なお、この結論は、私なりにちゃんと実機で検証した上で得たものです。
しかし、私が技術的に至らないだけで、別に良い回避手段があるかもしれません。
私は、その可能性について、探り続けるしかありません。
(ですが、全然見つからないので、開発自体を投げ出しそうです...)

もちろん、Googleが本件を不良と認めて、修正するのがベストですが。
ただ、私の英語力では、Googleと交渉する自信が全くありません。
他力本願でアレですが、そこは英語が得意な人or米国のデベロッパーにお任せで。
たぶん、4.0がもっと普及すれば、米国でもすぐに叩かれる筈。

なお、この記事を英訳して、blogへ記事を転載してくれる方が居るとすれば、有り難いです...
この記事の内容の転載はご自由にどうぞ。
ただし、転載先で発生したトラブル等についての責任ついて、私が取ることはできません。
その点はご了承ください。

0 件のコメント:

コメントを投稿

注: コメントを投稿できるのは、このブログのメンバーだけです。

合理的ではないものを作りたい

ここ最近、実機版の東方VGSの開発が忙しくて、東方VGSの曲追加が滞っています。 東方VGS(実機版)のデザインを作りながら検討中。基本レトロUIベースですがシークバーはモダンに倣おうかな…とか pic.twitter.com/YOYprlDsYD — SUZUKI PLAN (...