AndroidでEnumって結局Performanceに影響するの?

  • 11
    いいね
  • 0
    コメント

これは第2のドワンゴ Advent Calendar 2016 の 12/6日版です。

今年の7月末で卒業したドワンゴですが、とても良い会社でした。
ドワンゴというと皆さん「niconico」のイメージが強いと思いますが…
モバイル事業というのもあるんですよ。

ことのはじめ

立候補したものの、何書くか何にも決めていなかったのですが…
またTLで「Enum」に関するTweetが回ってきたので、改めてEnumについて調べることにしました。
(年に1回位どこからともなく回って来る気がする…w)

なお、私はAndroidアプリ内でもEnum普通に使う派です。
こちらの回答と同じような考え方です。(これ2011年のコメントなんだ…)

また、この投稿を読む前にAndroidにおけるenum利用に関するGoogleの公式見解と考察を読むのが良いと思います。

この投稿で「Proguardを設定する」の下りで「詳細は追っていない。」とありましたので…
そう言えば追ったこと無いし、程々追ってみたら面白いかも!?

ということで書きました。

ProguardのoptimizationsのOption関して

http://proguard.sourceforge.net/manual/optimizations.html

そもそも「class/unboxing/enum」という設定があって…
単純なEnumならIntegerに変えてくれるらしいです。

あれ、結論出てね…?

ということで、build.gradleでOptimizeの有効無効別にそれぞれbuild typeを分けておきます。

なお、「proguard-android-optimize.txt」のTopには
* -optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*

と設定されているので、単純なEnumはIntegerに変えてくれそうな感じがします。

ちなみに
* (The "arithmetic" optimization can be used if you are only targeting Android 2.0 or later.)

という記述も(実際試したことはない)

結果、よくわからん

smaliファイルを見てもEnumそのものへの参照は残っている状態でした。
Enum自体もRenameされている程度で特に差分なし。

ちなみにこちらがintを引数に取ったものと、Enumを引数にとったものそれぞれのメソッドのsmaliです。

コード

.method static a(Lcom/github/daneko/adventcalendar2016/a;)V
    .locals 3

    sget-object v0, Lcom/github/daneko/adventcalendar2016/MainActivity;->m:Ljava/lang/String;

    new-instance v1, Ljava/lang/StringBuilder;

    const-string v2, "Enum value is "

    invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

    invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    return-void
.end method

コード

.method static b(I)V
    .locals 3

    sget-object v0, Lcom/github/daneko/adventcalendar2016/MainActivity;->m:Ljava/lang/String;

    new-instance v1, Ljava/lang/StringBuilder;

    const-string v2, "int value is "

    invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

    invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    return-void
.end method

appendの部分がI(int?)を直接入れているか、Objectを突っ込んでいるか…の差分があります。
で、StringBuilderはintとObjectを受けた時、当然処理が異なるので…
* 差分がある

と言えるでしょう…

ちなみにOptimize処理なしの場合

# virtual methods
.method logWithEnum(Lcom/github/daneko/adventcalendar2016/SampleEnum;)V
    .locals 3
    .param p1, "i"    # Lcom/github/daneko/adventcalendar2016/SampleEnum;

    .prologue
    .line 66
    sget-object v0, Lcom/github/daneko/adventcalendar2016/MainActivity;->TAG:Ljava/lang/String;

    new-instance v1, Ljava/lang/StringBuilder;

    invoke-direct {v1}, Ljava/lang/StringBuilder;-><init>()V

    const-string v2, "Enum value is "

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

    move-result-object v1

    invoke-virtual {v1, p1}, Ljava/lang/StringBuilder;->append(Ljava/lang/Object;)Ljava/lang/StringBuilder;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    .line 67
    return-void
.end method

比較するとstatic methodに置き換えられていたり、appendの回数が減ってたりして面白いですね。

よくわからん、じゃ困る

よく考えると、Log出力時にEnumを文字列(Object)として使っているのが悪い。
ordinal使ってむりやりintとして書き出そう。

結果

.method static b(I)V
    .locals 3

    sget-object v0, Lcom/github/daneko/adventcalendar2016/MainActivity;->m:Ljava/lang/String;

    new-instance v1, Ljava/lang/StringBuilder;

    const-string v2, "int value is "

    invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

    invoke-virtual {v1, p0}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    return-void
.end method

.method static c(I)V
    .locals 3

    sget-object v0, Lcom/github/daneko/adventcalendar2016/MainActivity;->m:Ljava/lang/String;

    new-instance v1, Ljava/lang/StringBuilder;

    const-string v2, "Enum value is "

    invoke-direct {v1, v2}, Ljava/lang/StringBuilder;-><init>(Ljava/lang/String;)V

    add-int/lit8 v2, p0, -0x1

    invoke-virtual {v1, v2}, Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;

    move-result-object v1

    invoke-virtual {v1}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

    move-result-object v1

    invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

    return-void
.end method

intでの呼び出しとほぼ一致しました。
というか定義がまず I になっている…すごい…

唯一の差分がadd-int/lit8 v2, p0, -0x1なんですけどなんですかこれ…?

Enumオブジェクトはどうなったか

ここで改めてSampleEnumのsmaliを見たところ…
サイズが半減したうえに変化が見られました。

まずStaticFieldです

before

# static fields
.field public static final enum a:Lcom/github/daneko/adventcalendar2016/a;

.field public static final enum b:Lcom/github/daneko/adventcalendar2016/a;

.field public static final enum c:Lcom/github/daneko/adventcalendar2016/a;

.field private static final synthetic d:[Lcom/github/daneko/adventcalendar2016/a;

after

# static fields
.field public static final enum a:I

.field public static final enum b:I

.field public static final enum c:I

.field private static final synthetic d:[I

全部 I になっている…

また values() valueOf() も削られていました。

恐らく文字列としての参照が一切ない状態と判断できたので、ここまで変わったのだと思います。

Simplifies enum types というのはまさにそんな状況なら、なのでしょう。

恐らく今回動作確認のためだけにordinal()を呼び出しましたが、
Enum同士の比較とかでも同じような結果になるのではという気がします(未確認)

個人的な結論

さて、Performanceへの影響有無、ですが、
上記では触れませんでしたが、呼び出し元である無名クラス上では

  • constかEnumでの定義への参照か

の差分もあったり、当然余分なBinaryコードがあったりなどで

  • 影響はある

という結論になるかと思います。

ただ、「定義値をEnumにしたから、アプリの体験を損ねる結果になるか?」にはなんとも言えないでしょう。

いずれにしても今回Optimize後の状態を見てみたことで、
「確かにSimpleなEnumのままなら、ProguardのOptimize処理に任せるだけで随分変わる」
ことを確認できたので、上にも張ったこちらのコメントと同じようなスタンスで良いかなと改めて思いました。

ということで ProguardのOptimizeは今後も積極的に活用しようと思います。