Archives

December 2013 の記事

CPU ベンチに Snapdragon 800 MSM8974 Krait 400 の結果を追加しました。

ARM CPU core 毎の浮動小数点演算速度の比較 (VFP/NEON)
CPU benchmark

浮動小数点演算命令毎の実行速度

                (1)      (2)      (3)      (4)       (5)      (6)      (7)
               Nexus7   iPad4    HTL21   Nexus10  iPhone5s iPhone5s KindleHDX7
              Cortex-A9 Swift    Krait  Cortex-A15 Cyclone  Cyclone   Krait4
               Tegra3    A6X    APQ8064  Exynos5D   A7 32    A7 64   MSM8974
               1.2GHz   1.4GHz   1.5GHz   1.7GHz    1.3GHz   1.3GHz   2.2GHz
------------------------------------------------------------------------------
a:m44 vmla_AQ  3.959    1.204    1.337    0.619     0.700    -----    0.661
b:m44 vmla_BQ  2.002    1.266    0.931    0.569     0.670    -----    0.542
c:m44 vmla_AD  3.980    1.554    1.889    0.557     0.649    -----    0.888
d:m44 vmla_BD  2.003    1.238    1.532    0.568     0.745    -----    0.768
A:m44 vfma_AQ  -----    1.519    1.882    0.746     0.707    0.692    1.178
B:m44 vfma_BQ  -----    1.484    0.695    0.840     0.699    0.696    0.463
e:fadds     A  3.343    2.878    2.774    2.383     3.551    1.043    1.864
f:fmuls     A  3.337    2.953    2.747    2.369     3.475    1.548    1.867
g:fmacs     A  3.337    5.757    5.574    2.956     3.480    -----    2.052
h:vfma.f32  A  -----    5.756    2.747    2.957     3.480    3.185    1.864
i:vadd.f32 DA  3.426    2.877    2.762    1.183     1.031    1.031    1.866
j:vmul.f32 DA  3.421    2.950    2.746    1.478     1.545    1.545    1.864
k:vmla.f32 DA  3.792    2.951    5.604    1.480     1.567    -----    2.051
o:vfma.f32 DA  -----    2.494    2.833    1.479     1.574    1.753    1.871
l:vadd.f32 QA  6.688    2.878    2.801    2.365     1.031    1.039    1.872
m:vmul.f32 QA  6.681    2.952    2.761    2.364     1.548    1.548    1.879
n:vmla.f32 QA  6.681    2.950    5.606    2.367     1.574    -----    2.059
N:vfma.f32 QA  -----    -----    -----    -----     -----    1.696    -----
p:fadds     B  3.347    5.756    3.467    2.956     6.953    3.663    -----
q:fmuls     B  4.195    5.756    3.556    3.558     6.652    3.296    -----
r:fmacs     B  6.688   11.514    6.298    5.912     9.867    -----    -----
s:vfma.f32  B  -----   11.513    3.430    5.910     9.859    3.292    -----
t:vadd.f32 DB  3.421    2.881    3.529    2.958     3.663    3.643    1.865
u:vmul.f32 DB  3.422    2.949    3.447    2.364     3.114    3.289    2.339
v:vmla.f32 DB  7.561    5.755    6.293    4.728     6.185    -----    3.773
z:vfma.f32 DB  -----    5.755    3.437    4.730     6.188    6.237    2.340
w:vadd.f32 QB  6.705    2.879    3.457    2.961     3.659    3.641    1.875
x:vmul.f32 QB  6.683    2.950    3.428    2.363     3.101    3.276    2.340
y:vmla.f32 QB  7.532    5.759    6.372    4.729     6.199    -----    3.746
Y:vfma.f32 QB  -----    -----    -----    -----     -----    6.226    -----

・↑数値は実行時間(秒) 数値が小さい方が高速。single thread
・すべて単精度 32bit float の演算です。

Krait 400 は動作 clock が高いこともあり非常に高速です。
上の結果では同じ ARMv7A VFPv4 世代の Cortex-A15 に匹敵し、
実行効率の差を動作クロックが十分補っていることがわかります。

またここでの Quad core は Cortex-A9, Krait, Krait 400 だけなので、
総合的なパフォーマンスでは高クロックかつ Quad core の Krait 400 が
最も高いスコアになることが予想できます。

NEON 命令は 64bit と 128bit の差がなく、Cortex-A15 と違い 128bit
単位となっています。

vfma (FMA) よりも vmla が 2倍遅かった Krait 無印 (3) と比べて、
Krait 400 (7) では vmla も vfma に近い速度が出ています。
同じ Krait でも傾向が異なっており、様々な改良が施されているようです。

同時にあらためて A7 Cyclone の単 core 性能が非常に高いこともわかります。
A7 Cyclone の結果は 2個ありますが、
(5) は ARMv8 AArch32 (armv7) 32bit モードの結果で
(6) は ARMv8 AArch64 (arm64) 64bit モードでの結果です。

以下テスト端末の詳細

device                     OS   SoC      CPU core     clock  Arch        VFP
----------------------------------------------------------------------------
(1)ASUS Nexus 7 (2012)     A4.2 Tegra 3  Cortex-A9 x4 1.2GHz ARMv7A 32bit v3
(2)Apple iPad 4            i6.1 A6X      Swift     x2 1.4GHz ARMv7A 32bit v4
(3)HTC J butterfly HTL21   A4.1 APQ8064  Krait     x4 1.5GHz ARMv7A 32bit v4
(4)Samsung Nexus 10        A4.2 Exynos5D Cortex-A15x2 1.7GHz ARMv7A 32bit v4
(5)Apple iPhone 5s         i7.0 A7       Cyclone   x2 1.3GHz ARMv8A 32bit v4
(6)Apple iPhone 5s         i7.0 A7       Cyclone   x2 1.3GHz ARMv8A 64bit Ad
(7)Amazon Kindle Fire HDX7 A4.2 MSM8974  Krait 400 x4 2.2GHz ARMv7A 32bit v4

下記はもうひとつのCPUベンチの結果です。

  SoC CPU              clock  compiler  arch   time  MB/s   MBS/GHz
-------------------------------------------------------------------
1.A7 Cyclone + AES     1.3GHz clang 5.0 arm64  0.129 837.54  644.26
2.A7 Cyclone           1.3GHz clang 5.0 arm64  1.04  104.27   80.21
3.A7 Cyclone           1.3GHz clang 5.0 armv7  1.16   93.04   71.57
4.MSM8974 Krait 400    2.2GHz gcc 4.8   armv7  1.41   76.67   34.85
5.Exynos 5D Cortex-A15 1.7GHz gcc 4.6   armv7  1.49   72.61   42.71
6.A6X Swift            1.4GHz clang 4.2 armv7  1.75   61.82   44.16
7.APQ8064 Krait        1.5GHz gcc 4.6   armv7  2.28   47.64   31.82
8.Tegra3 Cortex-A9     1.3GHz gcc 4.4.3 armv7  3.00   36.15   25.82

・time の単位は秒
・MB/s が大きいほうが高速
・MBS/GHz = 1GHz あたりの処理速度

専用命令を使っている 1. が一桁高速なのは当然ですが、64bit アーキテクチャ
の A7 も十分高速です。
新 core + クロック数が最も高い Krait 400 はそれらに次ぐ速度となりました。
テスト内容の詳細はこちらから。

テストに使った Kindle Fire HDX 7 のデータは下記にも追加しました。

CPU/GPU OpenGL ES Extension (Mobile GPU)


関連エントリ
iPhone 5s A7 CPU の浮動小数点演算速度 (2) (arm64/AArch64/64bit)
iPhone 5s A7 CPU の浮動小数点演算速度 (32bit)
Nexus 10 CPU Cortex-A15 の速度
Qualcomm APQ8064 GPU Adreno 320 の速度
Qualcomm APQ8064 Krait/A6 swift の浮動小数点演算能力


MIPS 版の debian があったので試してみました。

Linux on Android

Debian-MIPS.zip を解凍すると image file が 2GB になり
そのままでは内部ストレージから溢れます。
実際の容量は 200M くらいなので、500M の新しい image file を作って
中身を全部コピーしました。
/sdcard/debian の中に debian.img として入れています。

アプリは使いません。
起動スクリプト debian.sh (debian-script.zip) はそのままでは使えないので、
必要な箇所を書き換えて adb shell から直接実行しています。

adb shell では su で容易に root になれます。
最初から busybox も入っています。

system update 直後は /system が rw で mount されているので、
この状態なら chroot で Linux を起動できます。
Z Watch を再起動すると /system は ro に戻りますが、
下記公式ページのドキュメントにあるように rw で remount できます。

SmqrtQ Z Watch Programming Guide

device 名が異なるので、debian.sh の最初で mount (remount) している
命令を削除します。

#mount -o remount,rw /dev/block/mmcblk0p5 /system

cut, grep, sed, umount などの存在しない命令は、
前に busybox を挿入します。

perm=$(id|cut -b 5)
↓
perm=$(id|busybox cut -b 5)

image file のパスは直接変数 kit に代入します。

export kit=$(dirname $0)
↓
export kit=/sdcard/debian

あくまで動くだけ。
もしアプリが root 権限で走るなら、端末の terminal からも
実行できるでしょう。


関連エントリ
Android SmartWatch スマートウオッチのスペック比較表
Android SmartWatch SmartQ ZWatch (4) アプリの管理
Android SmartWatch SmartQ ZWatch (3) 腕に関数電卓
Android SmartWatch SmartQ ZWatch (2)
Android 4.1 SmartWatch SmartQ Z Watch


Desktop の OpenGL 4.x との互換性を考えるならば、
OpenGL ES 3.0 の方が圧倒的に相性が良いです。
DirectX で例えるなら Direct3D11/10 と Direct3D9 を比べるようなもの。
Direct3D11 (OpenGL4) と Direct3D10 (OpenGL ES 3.0) は同じ API 構造を
保っていますが Direct3D9 (OpenGL ES 2.0) は大きく異なります。

OpenGL は DirectX ほどの違いはなく API 上は上位互換性を保っています。
ですが Shader の構文や描画パイプラインの設計は、やはりどちらかに
合わせる必要があります。

設計が大きく異なるならば、OpenGL ES 2.0 と OpenGL ES 3.0 のバイナリを
完全に分けてしまった方が良いのかもしれません。

以前 NDK の dll を分離する方法を紹介しました。

Android NDK の初期化と dll の分離

もし dll を分割して 2段階で読み込んでいるなら、同じ方法で
バイナリを切り替えることができます。
例えば下記のように。

・libjniproxy.so
・libappmain_es2.so
・libappmain_es3.so


関連エントリ
Android OpenGL ES 3.0 対応 GPU を判定する方法
Android 4.4 Adreno 320 の OpenGL ES 3.0 と Version 判定
Android NDK の初期化と dll の分離


Android 4.3 から OpenGL ES 3.0 が使えるようになりました。
対応 GPU も順調に増えていますが、Android 4.3/4.4 が動く端末自体が
まだあまり多くありません。
GPU によっては OpenGL ES 3.0 の動作で問題が生じるものもあり、
互換性の意味でも OpenGL ES 2.0/3.0 両対応が望ましいでしょう。

iOS の方は比較的簡単で、今のところ 64bit 対応 (ARMv8 arm64) なら
OpenGL ES 3.0 にも対応しています。

Android 端末が OpenGL ES 3.0 に対応してるかどうか調べる方法は
ドキュメントに記載されています。

OpenGL ES : Checking OpenGL ES Version

ただしこの方法は以前も書いたように必ずしもうまく行きません。
1. 及びサンプルコードは OpenGL ES 3.0 の Context を作成し、
エラーかどうかで判断しています。
実際にいくつかの端末を調べてみると、GPU ドライバは未対応でも
エラーを返さない場合がほとんどです。

version != 2.0 で判定している GPU は OpenGL ES 1.1 の context を返しますし、
version < 2.0 で判定している GPU は OpenGL ES 2.0 の context になります。

2. の方法はうまく行きますが、一旦ダミーの Context を作成するため複雑です。
またこの判定方法は Android 限定です。

以下その具体的な方法

package jp.flatlib.gw;

import  android.opengl.GLES20;
import  android.os.Build;
import  android.graphics.SurfaceTexture;
import  javax.microedition.khronos.egl.EGL10;
import  javax.microedition.khronos.egl.EGLConfig;
import  javax.microedition.khronos.egl.EGLContext;
import  javax.microedition.khronos.egl.EGLDisplay;
import  javax.microedition.khronos.egl.EGLSurface;
import  android.util.Log;

public class GPUType {

    public static int       VersionCode= 0; // 200 or 300
    public static String    VersionString;
    public static String    RendererString;
    private static int      AdrenoID= 0;

    public static boolean   isGLES3()
    {
        return  VersionCode == 300;
    }

    public static boolean   isAdreno300s()
    {
        return  AdrenoID >= 300 && AdrenoID < 400;
    }

    private static int decodeAdrenoID( String gpu_name )
    {
        if( gpu_name.equals( "Adreno" ) || gpu_name.indexOf( "AMD Z430" ) >= 0 ){
            return  200;
        }else if( gpu_name.indexOf( "Adreno" ) >= 0 ){
            return  getAdrenoNumber( gpu_name );
        }
        return  0;
    }

    public static void  update()
    {

        EGL10   egl= (EGL10)EGLContext.getEGL();
        int[]   iparam= new int[8];

        if( Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB ){
            VersionCode= 200;
            return;
        }

        // Initialize
        EGLDisplay  display= egl.eglGetDisplay( EGL10.EGL_DEFAULT_DISPLAY );
        egl.eglInitialize( display, iparam );

        // Config
        int[]   config_attr= { EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL10.EGL_NONE };
        final int   CONFIG_SIZE= 1;
        EGLConfig[] config_array= new EGLConfig[CONFIG_SIZE];
        egl.eglChooseConfig( display, config_attr, config_array, CONFIG_SIZE, iparam );
        EGLConfig   config= config_array[0];

        // Surface
        SurfaceTexture  surfaceTexture= new SurfaceTexture( 0 );    // API Level 11
        EGLSurface  surface= egl.eglCreateWindowSurface( display, config, surfaceTexture, null );

        // Context
        int[] context_attr= { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
        EGLContext  context= egl.eglCreateContext( display, config, EGL10.EGL_NO_CONTEXT, context_attr );

        // Make Current
        egl.eglMakeCurrent( display, surface, surface, context );

        // Query
        VersionString=  GLES20.glGetString( GLES20.GL_VERSION );
        RendererString= GLES20.glGetString( GLES20.GL_RENDERER );
        VersionCode= getGLVersionString( VersionString );
        AdrenoID= decodeAdrenoID( RendererString );


        // Special
        if( isAdreno300s() ){
            if( Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR2 ){ // .. Android 4.3
                VersionCode= 200;   // GLES 2.0
            }
        }

        // Release
        egl.eglMakeCurrent( display, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT );
        egl.eglDestroyContext( display, context );
        egl.eglDestroySurface( display, surface );

    }

    private static int getGLVersionString( String version_string )
    {
        int length= version_string.length();
        int ci= 0;
        for(; ci < length ; ci++ ){
            char    ch= version_string.charAt( ci );
            if( ch >= '0' && ch <= '9' ){
                break;
            }
        }
        int version= 0;
        int num_shift= 100;
        for(; num_shift > 0 && ci < length ;){
            char    ch= version_string.charAt( ci++ );
            if( ch >= '0' && ch <= '9' ){
                if( num_shift == 100 ){
                    if( version_string.charAt( ci ) == '.' ){
                        ci++;
                    }
                }
                version+= (ch - '0') * num_shift;
                num_shift/= 10;
            }else{
                break;
            }
        }
        return  version;
    }


    private static int getAdrenoNumber( String text )
    {
        int length= text.length();
        int num= 0;
        boolean is_num= false;
        for( int i= 0 ; i< length ; i++ ){
            char    ch= text.charAt( i );
            if( is_num ){
                if( ch >= '0' && ch <= '9' ){
                    num*= 10;
                    num+= ch - '0';
                }else{
                    break;
                }
            }else{
                if( ch >= '0' && ch <= '9' ){
                    is_num= true;
                    num= ch - '0';
                }
            }
        }
        return  num;
    }
}

GPUType.update() でデータを集めて isGLES3() で結果がわかります。

Android 3.0 API Level 11 以降でないと動きませんが、
Android 4.2 (API Level 17) 以前は OpenGL ES 2.0 と決め打ちできるため
特に問題ないはずです。

同じ理由で、EGL10 の代わりに EGL14 を使うこともできます。
EGL14 は API Level 17 以降の API です。

Android 4.3 + Adreno 320 の OpenGL ES 3.0 には問題があるので、
この組み合わせでは OpenGL ES 2.0 を返すようにしています。


関連エントリ
Android 4.4 Adreno 320 の OpenGL ES 3.0 と Version 判定
iPhone 5s の Apple A7 GPU
Adreno 320 の OpenGL ES 3.0 と Uniform Block
Nexus 7 (2013) の Adreno 320 と OpenGL ES 3.0 (Android 4.3)


Android の描画も V-Sync に依存しており、一部デバイスを除いておよそ
60fps が上限となっています。
Android 4.1 Jelly Bean (API Level 17) 以降は SwapInterval で
0 を設定することができます。

desktop 版 OpenGL 同様、Interval=0 に設定すると非同期になります。
実際の画面に反映されているかどうかを考えなければ、
GPU の描画ループも 60fps を超えることが可能です。

// Java
import  android.opengl.EGL14;
import  android.opengl.EGLDisplay;

~

EGLDisplay  display14= EGL14.eglGetDisplay( EGL14.EGL_DEFAULT_DISPLAY );
EGL14.eglSwapInterval( display14, 0 );

EGLDisplay, EGLContext, EGLConfig, EGLSurface は android.opengl と
javax.microedition.khronos.egl の両方に存在します。
EGL14 を用いる場合は android.opengl の方を使用。

// NDK NativeActivity
eglSwapInterval( display, 0 );


// NDK + GLSurfaceView
EGLDisplay  display= eglGetDisplay( EGL_DEFAULT_DISPLAY );
eglSwapInterval( display, 0 );

ベンチマークで便利。


関連エントリ
iOS CADisplayLink とフレームレート
OpenGL の同期と描画速度の測定