0

私は自分のアプリでこの問題を数か月にわたって輪になって回っています。私は多くの自作のソリューションを試しましたが、ここで私が取り組んでいることを説明しますが、誰かが私が見逃したより良い解決策を提案してくれることを願っています.

基本的な問題は次のとおりです。アプリからいつでもアクセスする必要があるアイテムが (潜在的に) 数千あります。NSMutableDictionary は通常、各項目を表すための最初のアプローチです。各項目には数個から数百個のプロパティがある可能性があるためです。しかし、残りの要件は物事を毛むくじゃらにします。

  1. 各アイテムは、任意のスレッドから読み取られたり、書き込まれたりする可能性があります
  2. 各アイテムは、セッション間で取得できるようにディスクに保存する必要があります
  3. 非常に多くの項目 (および非常に多くのデータ) が (潜在的に) 存在するため、一度にすべてをメモリに格納すると、メモリの問題が発生する可能性があります

Apple がとても気に入っている CoreData を使用したかったのですが、多くの問題に遭遇しました。各アイテムには決定的な構造がないため、データ モデルを構造化する良い方法はありません。さらに、データのクエリにより、単一の .sqlite ファイルがボトルネックとして機能するようになりました。つまり、多くのスレッドがアイテムを一度に取得しようとすると、待機時間 (ラグ) が非常に速くなりました。

実用的な解決策がありますが、問題があります。これがコードの一部です。以下でその機能を説明します。

- (NSObject*) getValue:(NSString*)key {
    @synchronized(self) {
        if(!_cached_obj) { // private variable in this object
            _cached_obj = [self loadFromDisk]; // simply loads the NSDictionary from a file
        }
        _last_access = time(nil);//don't release for a while
        return [_cached_obj valueForKey:key];
    }
}
- (void) setValue:(NSObject*)value forKey:(NSString*)key {
    @synchronized(self) {
        [self getValue:key];//ensures the cache is active
        [_cached_obj setValue:value forKey:key];
        _needs_save = true;
    }
}
- (void) clean {
    if(!_cached_obj)
        return;
    @synchronized(self) {
        if(_needs_save)
        {
            [self writeToFile];//writes the cache obj to a file
            _needs_save = NO;
        }

        NSTimeInterval elapsed = time(nil) - _last_access;
        if(elapsed > 20)
        {
            [_cached_obj release];
            _cached_obj = nil;
        }
    }
}
  • Item からのデータが必要な場合は、getValue 関数が呼び出されます。キャッシュされたオブジェクト (NSMutableDictionary) を使用しようとします。キャッシュされたオブジェクトが NULL の場合、オブジェクトをディスクからロードしてから返します。
  • setValue 関数は期待どおりに機能しますが、保存フラグも設定します
  • 「クリーン」機能は、バックグラウンド スレッドによって 10 秒のタイマーで実行されます。これにより、アイテムがディスクに保存され、メモリを節約するためにデータがキャッシュから削除されます。

私のアプローチで気に入らない点は、@synchronized の使用に基づいて、セマフォを大量に待機していることです。場合によっては、これはメイン スレッドがディスクの読み取り/書き込みを待機している間にブロックされていることも意味します。これは苦痛です。

私が見逃しているより良いデータ構造またはストレージメカニズムはありますか?

ありがとう!


編集: 詳細: 「getValue」関数が返す速度は、メイン スレッドをブロックしていなくても非常に重要です。たとえば、バックグラウンド スレッドで 10,000 個のアイテムを検索しているシナリオを考えてみましょう。10k オブジェクトのそれぞれから 1 つの値を 1 回取得する必要があります。私の現在のメカニズムでは機能しますが、キャッシュされていない各オブジェクトをディスクからロードするのは時間がかかり、iPhone 4 では最大 20 秒かかります。 " しかし、おそらくデータを小さなチャンクに保存すると役立つでしょうか? たとえば、アイテム全体を辞書として保存するのではなく、個別のオブジェクトのコレクションとして保存します。

4

1 に答える 1

1

私が理解しているように、あなたはアプリをプロファイリングし、プロファイルは @synchronize ブロックが最大のパフォーマンスのボトルネックであることを示しています。右?

あなたが指摘したように、ミューテックスを保持しながらファイルを読み書きします。さらに、同時に許可するスレッドは 1 つだけですが、キャッシュへのアクセスを多数のリーダーまたは 1 つのライターに簡単に許可することもできます。

特定されたロック操作:

  • 値を取得 -> キャッシュで値を取得、キャッシュにない場合はディスクで値を取得、値をキャッシュに入れる
  • 値を設定 -> キャッシュ内の値を取得、キャッシュ内にない場合はディスク上の値を取得、キャッシュ内に値を配置、キャッシュ内に新しい値を配置
  • クリーン -> キャッシュを保存、キャッシュを空にする

したがって、基本的な操作は次のとおりです。

  • キャッシュで値を取得する
  • ディスク上の値を取得する
  • キャッシュに値を入れる
  • キャッシュを保存
  • 空のキャッシュ

これらの単純な操作の同時実行性を判断し、ロックを作り直して、すべてが互いにうまく機能するようにするのは非常に簡単です。

多くのリーダーまたは 1 つのライターがキャッシュにアクセスできるようにすることができます。キャッシュをロックしなくても、1 つのスレッドでディスクを読み取る (または書き込む) ことができます。ディスクから読み取った値は、後でライターとしてキャッシュに設定されます。つまり、キャッシュ用の 1 つの読み取り/書き込みロックと、ファイル用のミューテックスです。設定値のシーケンスも少し不可解です。ファイルから古い値を読み取ってすぐに置き換えるポイントがわかりません。キャッシュ データ構造を準備する必要がある場合は、ファイル操作をトリガーしないようにしてください。

これらはすべて GCD を使用して実装することもでき、すべてではないにしてもほとんどのロックを回避できます。

多くの複雑さを導入したり、アプリのスレッド モデルを変更したりせずに、衝突を減らす余地は十分にあります。GCD はさらに多くの機会を提供すると思いますが、スレッドの代わりにキューと操作の観点から考える必要があり、これは一見すると必ずしも容易ではありません。

ロックを作り直すだけで十分だとは言いません。データを読み取ってディスクに保存する方法も改善する必要があるかもしれませんが、ロックから始めてください。あなたは驚くかもしれません。

于 2012-07-25T01:01:49.953 に答える