SharedPreferencesへの保存における速度差

# 概要

AndroidのSharedPreferencesにおいて、コードによってどの程度の速度差が出るのかを確認してみました。

# 背景

ここでは、Activity内で以下のオブジェクトを作成していることを前提としています。

```Java
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
SharedPreferences.Editor editor = prefs.edit();
```

AndroidのSharedPreferencesに値を保存する際、一般的に以下のような記述を行います。

```Java
editor.putBoolean("val1", true).apply();
editor.putInt("val2", 1).apply();
editor.putLong("val3", 1L).apply();
editor.putFloat("val4", 1.0f).apply();
editor.putString("val5", "1").apply();
```

復数の値を保存する際は、以下のように最後にapply()メソッドを呼ぶことで、まとめて保存ができます。

```Java
editor.putBoolean("val1", true);
editor.putInt("val2", 1);
editor.putLong("val3", 1L);
editor.putFloat("val4", 1.0f);
editor.putString("val5", "1");
editor.apply();
```

以下のようにメソッドチェーンによる記述を行うこともできます。

```Java
editor
        .putBoolean("val1", true)
        .putInt("val2", 1)
        .putLong("val3", 1L)
        .putFloat("val4", 1.0f)
        .putString("val5", "1")
        .apply();
```


apply()はデータを保存するためのメソッドですが、聞きかじりの知識では、実際には非同期で処理を行うとのことです。同期的な書き込みはcommit()がありますが、こちらを使うとAndroid Studioから "Consider using apply() instead;" と警告されます。ただ、特に非推奨とはなっていません。

非同期で書き込まれるなら、一つ一つapply()メソッドを呼んでも、まとめてapply()メソッドを呼んでも、アプリの実行速度にさほど大きな差は無いんじゃなかろうか？と思ったのがこの記事の発端で、それを調べてみた次第です。

# 確認

以下に示すいくつかのパターンのコードで調べてみました。5種類のデータを10000回保存する処理となります。３回測定して、その結果を確認します。(途中でガベージコレクションは発生していません。)
これが本当に確認方法として適切なのかどうか、コメントいただけると助かります。

## 動作環境

### 端末

* Android仮想デバイス (Android 8.1)

### 仮想デバイス実行環境

* MacBook Pro
* CPU: Intel Core i7 2.5GHz
* メモリ: 15GB
* OS: macOS High Sierra 10.13.6

## パターン1:個別にapply()メソッドを呼ぶ

### コード

```Java
for (int i = 0; i < 10000; i++) {
    editor.putBoolean("val1", true).apply();
    editor.putInt("val2", 1).apply();
    editor.putLong("val3", 1L).apply();
    editor.putFloat("val4", 1.0f).apply();
    editor.putString("val5", "1").apply();
}
```

### 結果

1回目: 5587 ms
2回目: 6777 ms
3回目: 5177 ms

とても遅い。実行回数を100万回とかにしなくて良かった。

## パターン2:最後にapply()メソッドを呼ぶ

### コード

```Java
for (int i = 0; i < 10000; i++) {
    editor.putBoolean("val1", true);
    editor.putInt("val2", 1);
    editor.putLong("val3", 1L);
    editor.putFloat("val4", 1.0f);
    editor.putString("val5", "1");
    editor.apply();
}
```

### 結果

1回目: 362 ms
2回目: 351 ms
3回目: 344 ms

個別にapply実行するより10倍以上早くなった。

## パターン3:メソッドチェーンで最後にapply()を呼ぶ

### コード

```Java
for (int i = 0; i < 10000; i++) {
    editor
            .putBoolean("val1", true)
            .putInt("val2", 1)
            .putLong("val3", 1L)
            .putFloat("val4", 1.0f)
            .putString("val5", "1")
            .apply();
}
```

### 結果

1回目: 362 ms
2回目: 378 ms
3回目: 366 ms

当たり前だとは思うけど、メソッドチェーンを使ったからといって特に速度に影響があるわけではない模様。

## パターン4:commitを使う

### コード

```Java
for (int i = 0; i < 10000; i++) {
    editor.putBoolean("val1", true).commit();
    editor.putInt("val2", 1).commit();
    editor.putLong("val3", 1L).commit();
    editor.putFloat("val4", 1.0f).commit();
    editor.putString("val5", "1").commit();
}
```

### 結果

1回目: 250 ms
2回目: 236 ms
3回目: 247 ms

意外なことに、commit()が速い。こちらの方が遅くなるのではないかと思ってたのに…。

## パターン5: メソッドチェーン + commit

### コード

```Java
for (int i = 0; i < 10000; i++) {
    editor
            .putBoolean("val1", true)
            .putInt("val2", 1)
            .putLong("val3", 1L)
            .putFloat("val4", 1.0f)
            .putString("val5", "1")
            .commit();
}
```

### 結果

1回目: 59 ms
2回目: 74 ms
3回目: 58 ms

個別にapply()するより100倍くらい早くなった。

# まとめ

結果から言うと、予想通りapply()をまとめて実行した方がずっと早いです。というか、複数のapply()を繰り返し実行すると処理が遅くなるようです。よく分かっていないのですが、非同期の書き込み処理がブロックされて遅延するということでしょうか。そんなわけで、書き込み処理が一度に行える場合は積極的に行うべきかと思います。

今回調べたかった内容とは全然関係無いですが、commit()が速かったです。ただ、この記事は別にcommit()の利用を推奨しているわけではないので、参考程度に留めておいてください。

今回は、ただ単にapply()の特性を知りたかっただけなので、このような確認をしましたが、これだけ高速に複数の値を一度に書き込む状況に陥ることはまず無いと思いますので、この実験はあまり実務的な内容ではないと思います。そもそも、こんな状況に陥る前に、ソフトウェア設計の見直しや、他の手段の採用を検討するべきでしょう。

結論：SharedPreferencesへの書き込みには、複数のput処理の最後にapply()を呼んだ方が圧倒的に高速になる。

# 参考

- [core/java/android/app/SharedPreferencesImpl\.java \- platform/frameworks/base \- Git at Google](https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/SharedPreferencesImpl.java)
- [SharedPreferences のファイルアクセスのタイミング \- Qiita](https://qiita.com/usamao/items/d7fbb19b508dc4cb5521)