Help us understand the problem. What is going on with this article?

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

主題

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

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

    await AsyncStorage.setItem(key, value);

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

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

複数の値を管理したい

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

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

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

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

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

    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を使います。

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)
}


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));
};


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

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({});
};

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

// 値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({});
};

実行!

結果

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

iOS

    // 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

    [
      {
        "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

nitaking
ReactNativeエンジニアになりつつある
https://www.wantedly.com/users/18008954
aircloset
「新しい当たり前を作る」を作ることをミッションに、airClosetを開発・運営しています。
http://corp.air-closet.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした