【AsyncStorageで実験】Object格納とKeyValue格納どっちが速いの？

## 主題

react-nativeでの永続ストレージとして `AsyncStorage`をreact-nativeが提供しています。

知っている方はご存知の通り、KeyとValueの組み合わせで保存する形式となります。

```js
    await AsyncStorage.setItem(key, value);
```

そして第二引数のvalueはstringのみとなっています。

```ts
    static setItem(key: string, value: string, [callback]: ?(error: ?Error) => void)
```

## 複数の値を管理したい

ここで、永続ストレージに複数の値を管理、かつ、同時にアクセスしたいニーズがあります。

例えば、ユーザー情報が複数プロパティあって、利用するときはユーザー情報を全て表示する、などです。

そんなときはkey-valueなので、そのまま二回setするとこうなります。

```ts
    await AsyncStorage.setItem('name', 'nita');
    await AsyncStorage.setItem('age', '56');
```

しかし、他にもobjectを`JSON.stringify()`してから値にセットする方法もあります。

```js
    const user = {
    	name: 'nita',
    	age: '56',
    }
    await AsyncStorage.setItem('user', JSON.stringify(user));
```

ストレージって書き込み速度と読み込み速度のパフォーマンスが気になるところですが、
これ、どっちのほうが速いんだろう・・・？

## 検証！

以下の組み合わせを試します

- 値の組み合わせ
    - key-value 1個
    - key-value 2個
    - key-value 3個
- 処理
    - 書き込み（`setItem`）
    - 読み込み（`getItem`）
- OS差
    - iOS
    - Android

適当に検証用にkey-valueのsetItem/getItemと、objectのsetItem/getItemを作ります
ObjectのsetItemはJSON.stringifyを使う、getItemはJSON.parseを使います。

```ts:local-store.ts
import { AsyncStorage } from 'react-native'

export async function save(key: string, value: string) {
    await AsyncStorage.setItem(key, value)
}
export async function get(key: string) {
    return AsyncStorage.getItem(key)
}

export async function retrieve(key) {
    const serialized = await AsyncStorage.getItem(key)
    if (!serialized) {
        return null
    }
    return JSON.parse(serialized)
}


```

```tsx:App.tsx
import faker from "faker";

// 値1個
const setMultiKeyValue1 = async () => {
  await LocalStore.save("name1", faker.name.findName());
};
const setObjectValue1 = async () => {
  const value = {
    name: faker.name.findName()
  };
  await LocalStore.save("@user1", JSON.stringify(value));
};

// 値2個
const setMultiKeyValue2 = async () => {
  await LocalStore.save("name2", faker.name.findName());
  await LocalStore.save("age2", String(faker.random.number()));
};
const setObjectValue2 = async () => {
  const value = {
    name: faker.name.findName(),
    age: faker.random.number()
  };
  await LocalStore.save("@user2", JSON.stringify(value));
};

// 値3個
const setMultiKeyValue3 = async () => {
  await LocalStore.save("name3", faker.name.findName());
  await LocalStore.save("age3", String(faker.random.number()));
  await LocalStore.save("token3", faker.random.uuid());
};
const setObjectValue3 = async () => {
  const value = {
    name: faker.name.findName(),
    age: faker.random.number(),
    token: faker.random.uuid()
  };
  await LocalStore.save("@user3", JSON.stringify(value));
};


```

計測用にタイマーを起きます。

```tsx
import * as marky from "marky";

// markyのオブジェクトを整形する
const format = object => {
    return {
        name: object.name,
        duration: object.duration
    };
};

const startSet = async () => {
    let entry = null;

    marky.mark("<write>object");
    await setObjectValue1();
    entry = marky.stop("<write>object");
    resultSet.push(format(entry));

    marky.mark("<write>key-value");
    await setMultiKeyValue1();
    entry = marky.stop("<write>key-value");
    resultSet.push(format(entry));

    marky.mark("<write>object");
    await setObjectValue1();
    entry = marky.stop("<write>object");
    resultSet.push(format(entry));

    marky.mark("<write>key-value");
    await setMultiKeyValue1();
    entry = marky.stop("<write>key-value");
    resultSet.push(format(entry));

    marky.mark("<write>key-value2");
    await setMultiKeyValue2();
    entry = marky.stop("<write>key-value2");
    resultSet.push(format(entry));

    marky.mark("<write>object2");
    await setObjectValue2();
    entry = marky.stop("<write>object2");
    resultSet.push(format(entry));

    marky.mark("<write>key-value3");
    await setMultiKeyValue3();
    entry = marky.stop("<write>key-value3");
    resultSet.push(format(entry));

    marky.mark("<write>object3");
    await setObjectValue3();
    entry = marky.stop("<write>object3");
    resultSet.push(format(entry));

    alert(JSON.stringify(resultSet));
    setShow(false);
    setShow(true);
    resultSet.push({});
};
```

読み込みの処理とタイマーもセット

```tsx

// 値1個
const getMultiKeyValue1 = async () => {
  await LocalStore.get("name1");
};
const getObjectValue1 = async () => {
  const value = {
    name: faker.name.findName()
  };
  await LocalStore.retrieve("@user1");
};

// 値2個
const getMultiKeyValue2 = async () => {
  await LocalStore.get("name2");
  await LocalStore.get("age2");
};
const getObjectValue2 = async () => {
  const value = {
    name: faker.name.findName(),
    age: faker.random.number()
  };
  await LocalStore.retrieve("@user2");
};

// 値3個
const getMultiKeyValue3 = async () => {
  await LocalStore.get("name3");
  await LocalStore.get("age3");
  await LocalStore.get("token3");
};
const getObjectValue3 = async () => {
  const value = {
    name: faker.name.findName(),
    age: faker.random.number(),
    token: faker.random.uuid()
  };
  await LocalStore.retrieve("@user3");
};

const startGet = async () => {
    let entry = null;

    marky.mark("<read>object");
    await getObjectValue1();
    entry = marky.stop("<read>object");
    resultGet.push(format(entry));

    marky.mark("<read>key-value");
    await getMultiKeyValue1();
    entry = marky.stop("<read>key-value");
    resultGet.push(format(entry));

    marky.mark("<read>object");
    await getObjectValue1();
    entry = marky.stop("<read>object");
    resultGet.push(format(entry));

    marky.mark("<read>key-value");
    await getMultiKeyValue1();
    entry = marky.stop("<read>key-value");
    resultGet.push(format(entry));

    marky.mark("<read>key-value2");
    await getMultiKeyValue2();
    entry = marky.stop("<read>key-value2");
    resultGet.push(format(entry));

    marky.mark("<read>object2");
    await getObjectValue2();
    entry = marky.stop("<read>object2");
    resultGet.push(format(entry));

    marky.mark("<read>key-value3");
    await getMultiKeyValue3();
    entry = marky.stop("<read>key-value3");
    resultGet.push(format(entry));

    marky.mark("<read>object3");
    await getObjectValue3();
    entry = marky.stop("<read>object3");
    resultGet.push(format(entry));

    alert(JSON.stringify(resultGet));
    setShow(false);
    setShow(true);
    console.log(resultSet, resultGet);
    resultGet.push({});
};
```

## 実行！
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103475/1e9a6088-c569-62ef-185c-3debc3f2b7d9.gif" width="300"> 


## 結果

（※長いです、ファーストアクセスのみdurationの遅延が発生したので、それぞれ一回は"<XX>object"の処理を先頭に入れています）

### iOS

```json
    // ios
[
  {
    "duration": 8,
    "name": "<write>object"
  },
  {
    "duration": 4,
    "name": "<write>key-value"
  },
  {
    "duration": 3,
    "name": "<write>object"
  },
  {
    "duration": 4,
    "name": "<write>key-value"
  },
  {
    "duration": 5,
    "name": "<write>key-value2"
  },
  {
    "duration": 2,
    "name": "<write>object2"
  },
  {
    "duration": 9,
    "name": "<write>key-value3"
  },
  {
    "duration": 3,
    "name": "<write>object3"
  },
  {},
  {
    "duration": 4,
    "name": "<write>object"
  },
  {
    "duration": 3,
    "name": "<write>key-value"
  },
  {
    "duration": 3,
    "name": "<write>object"
  },
  {
    "duration": 3,
    "name": "<write>key-value"
  },
  {
    "duration": 4,
    "name": "<write>key-value2"
  },
  {
    "duration": 3,
    "name": "<write>object2"
  },
  {
    "duration": 6,
    "name": "<write>key-value3"
  },
  {
    "duration": 3,
    "name": "<write>object3"
  },
  {}
] [
  {
    "duration": 3,
    "name": "<read>object"
  },
  {
    "duration": 3,
    "name": "<read>key-value"
  },
  {
    "duration": 2,
    "name": "<read>object"
  },
  {
    "duration": 1,
    "name": "<read>key-value"
  },
  {
    "duration": 3,
    "name": "<read>key-value2"
  },
  {
    "duration": 2,
    "name": "<read>object2"
  },
  {
    "duration": 3,
    "name": "<read>key-value3"
  },
  {
    "duration": 2,
    "name": "<read>object3"
  },
  {},
  {
    "duration": 3,
    "name": "<read>object"
  },
  {
    "duration": 2,
    "name": "<read>key-value"
  },
  {
    "duration": 2,
    "name": "<read>object"
  },
  {
    "duration": 2,
    "name": "<read>key-value"
  },
  {
    "duration": 3,
    "name": "<read>key-value2"
  },
  {
    "duration": 2,
    "name": "<read>object2"
  },
  {
    "duration": 4,
    "name": "<read>key-value3"
  },
  {
    "duration": 4,
    "name": "<read>object3"
  }
]
```

### Android

```json
    [
      {
        "duration": 26,
        "name": "<write>object"
      },
      {
        "duration": 9,
        "name": "<write>key-value"
      },
      {
        "duration": 13,
        "name": "<write>object"
      },
      {
        "duration": 8,
        "name": "<write>key-value"
      },
      {
        "duration": 13,
        "name": "<write>key-value2"
      },
      {
        "duration": 15,
        "name": "<write>object2"
      },
      {
        "duration": 20,
        "name": "<write>key-value3"
      },
      {
        "duration": 8,
        "name": "<write>object3"
      },
      {},
      {
        "duration": 20,
        "name": "<write>object"
      },
      {
        "duration": 10,
        "name": "<write>key-value"
      },
      {
        "duration": 10,
        "name": "<write>object"
      },
      {
        "duration": 8,
        "name": "<write>key-value"
      },
      {
        "duration": 11,
        "name": "<write>key-value2"
      },
      {
        "duration": 5,
        "name": "<write>object2"
      },
      {
        "duration": 12,
        "name": "<write>key-value3"
      },
      {
        "duration": 5,
        "name": "<write>object3"
      },
      {}
    ] [
      {
        "duration": 19,
        "name": "<read>object"
      },
      {
        "duration": 5,
        "name": "<read>key-value"
      },
      {
        "duration": 5,
        "name": "<read>object"
      },
      {
        "duration": 6,
        "name": "<read>key-value"
      },
      {
        "duration": 10,
        "name": "<read>key-value2"
      },
      {
        "duration": 6,
        "name": "<read>object2"
      },
      {
        "duration": 13,
        "name": "<read>key-value3"
      },
      {
        "duration": 5,
        "name": "<read>object3"
      },
      {},
      {
        "duration": 16,
        "name": "<read>object"
      },
      {
        "duration": 5,
        "name": "<read>key-value"
      },
      {
        "duration": 6,
        "name": "<read>object"
      },
      {
        "duration": 5,
        "name": "<read>key-value"
      },
      {
        "duration": 8,
        "name": "<read>key-value2"
      },
      {
        "duration": 5,
        "name": "<read>object2"
      },
      {
        "duration": 11,
        "name": "<read>key-value3"
      },
      {
        "duration": 4,
        "name": "<read>object3"
      }
    ]
```


### 結果

- なぜか、関数の始めのAsyncStorageアクセスはスピードが遅い。（なので比較ではノーカンしてください。）
- 書き込み、読み込みともに、**Object格納（アクセス回数1回）が速い。**
- key-valueの個数が増えるほど（アクセス回数が増えるほど）**比例して遅くなる**。
- データが増えた場合でも、Object格納時の書き込み・読み込み速度の変動はアクセス回数と比べて限りなく**小さい**。
- iOSより、Androidのほうがやや速度差が大きい。

## つまり

- 同時取り扱いデータ個数が**1個の場合はほぼ同じ**
- 同時取り扱いデータ個数が**2個以上の場合**は、**Object格納**（Objectを`JSON.stringify()`して書き込み、読み込み）**したほう**がデータが大きくてもアクセス回数減らしたほうが**速い。**
    - （1valueの最大保存可能桁数的なのを検証してないので限度は不明）

よくよく考えるとデータ保存領域への入出力のほうが速度的に遅くて当然な気がしてきたけど、JSON.stringify()が速度的に全然影響が無いことがわかって安心した・・・。

## Link
- https://github.com/nitaking/ExperimentAsyncStoragePerformance