2016-12-15 6 views
2

私のアプリでは、各ユーザのplistファイルにユーザ設定を保存したいのですが、one class called CCUserSettingsはほとんど同じインタフェースをNSUserDefaultsと書いてあり、現在のユーザIDに関連するplistファイルを読み書きします。それは動作しますが、パフォーマンスが悪いです。ユーザーが[[CCUserSettings sharedUserSettings] synchronize]と呼ぶたびに、NSMutableDictionary(ユーザー設定を保持する)をplistファイルに書き込むと、下のコードはCCUserSettingsの簡略な説明を省略してsynchronizeを示しています。「NSUserDefaults synchronize」はどのように高速で動作しますか?

- (BOOL)synchronize { 
    BOOL r = [_settings writeToFile:_filePath atomically:YES]; 
    return r; 
} 

私はdemoをテストするために書き、私たちは[[NSUserDefaults standardUserDefaults] synchronize]を呼び出すときNSUserDefaultsは、ファイルに書き込む必要があると仮定し、それは本当に速く走るには、重要な部分は、以下の私のiPhone6の実行を1000回[[NSUserDefaults standardUserDefaults] synchronize][[CCUserSettings sharedUserSettings] synchronize]、その結果であります0.45秒対9.16秒である。結果が示すように

NSDate *begin = [NSDate date]; 
for (NSInteger i = 0; i < 1000; ++i) { 
    [[NSUserDefaults standardUserDefaults] setBool:(i%2==1) forKey:@"key"]; 
    [[NSUserDefaults standardUserDefaults] synchronize]; 
} 
NSDate *end = [NSDate date]; 
NSLog(@"synchronize seconds:%f", [end timeIntervalSinceDate:begin]); 


[[CCUserSettings sharedUserSettings] loadUserSettingsWithUserId:@"1000"]; 
NSDate *begin = [NSDate date]; 
for (NSInteger i = 0; i < 1000; ++i) { 
    [[CCUserSettings sharedUserSettings] setBool:(i%2==1) forKey:@"_boolKey"]; 
    [[CCUserSettings sharedUserSettings] synchronize]; 
} 
NSDate *end = [NSDate date]; 
NSLog(@"CCUserSettings modified synchronize seconds:%f", [end timeIntervalSinceDate:begin]); 

NSUserDefaultsは私のCCUserSettingsよりも約20倍高速です。今私は "NSUserDefaultsが本当にsynchronizeメソッドを呼び出すたびにplistファイルに書き込むのですか?"と思っていますが、そうでなければ、プロセスが終了する前にファイルへのデータの書き戻しをどうすれば保証できますか?いつでも殺されるだろう)?

最近、私はCCUserSettingsを改善するアイデアを思いついた、mmapMemory-mapped I/Oです。私は仮想メモリをファイルにマップすることができ、ユーザがsynchronizeを呼び出すたびにNSPropertyListSerialization dataWithPropertyList:format:options:error:メソッドを使ってNSDataを作成し、そのメモリにデータをコピーすると、オペレーティングシステムはプロセスが終了するとメモリにファイルを書き戻します。しかし、私はファイルのサイズが固定されていないため、データの長さが増えるたびに、私は仮想メモリをmmapに再登録しなければならないため、パフォーマンスが良くないかもしれません。

私の冗長な詳細については申し訳ありませんが、私はちょうどNSUserDefaultsがどのように優れた性能を達成するために働いているか知りたいですか、誰かが私のCCUserSettingsを改善する良いアドバイスを持っていますか?

+1

ここではNSUserDefaults' 'についての素晴らしい記事です:http://dscoder.com /defaults.html。その作者はAppleのエンジニアですから、彼が話していることを知っていると想定するのはかなり安全です:) – Losiowaty

+0

@Losiowatyあなたのリンクをありがとうございますが、私はMac oxの実装について話しています。変更がどれほど小さいかにかかわらず、plist全体をディスクに書き出します(つまり、非同期で、後で別のプロセスで時間がかかります)。 "あなたがNSUserDefaultsを修正し、 'synchronize'なしであなたのアプリをkillすると、設定はファイルに書き込まれないので、iOSにファイルを書き込む別のプロセスはないと思います。 – KudoCC

+0

NSUserDefaultsを修正してアプリを終了した場合、データを失うためにはkillボタンで*非常に*高速にする必要があります。数ミリ秒ほどです。これはiOS 8で変更されました。それ以前は、データを失うことがずっと簡単でした。 –

答えて

2

最近のオペレーティングシステム(iOS 8+、macOS 10.10+)では、NSUserDefaultsは、同期を呼び出すときにファイルを書き出しません。 -set *メソッドを呼び出すと、cfprefsdという名前のプロセスに非同期メッセージが送信され、新しい値が格納され、応答が送信され、後でそのファイルが書き出されます。すべて-synchronizeは、cfprefsdへの未解決のメッセージがすべて返信されるのを待ちます。

(編集:あなたが好きな場合は、xpc_connection_send_message_with_replyにシンボリックブレークポイントを設定し、ユーザーのデフォルトを設定することで、これを確認することができます)

+0

あなたの答えをありがとう!しかし、IO操作が他のプロセスで発生した場合、なぜ私は 'exit'を呼び出す直前に設定された設定を失うのでしょうか? – KudoCC

+0

私が 'exit'を呼び出す直前に設定した設定が失われても、あなたが間違っているということではありません。cfprefsdプロセスがプロセスに他のメッセージを送る必要があるかもしれません。私はちょうど好奇心を知っています:)BTWあなたはそれを知っていました、あなたはいくつかの参考資料を投稿できますか? – KudoCC

+0

発生する可能性があるのは2つの理由があります。最初のメッセージは最もシンプルなものです。メッセージの送信は非同期なので、メッセージが実際にプロセスから抜け出す前にexit()が呼び出される可能性があります。 2番目は微妙です。cfprefsdは、サンドボックスのアクセス許可を確認して、それらの設定にアクセスできるようにする必要があります。サンドボックス権限を確認するには、プロセスがまだ実行されている必要があります。 –

2

最後に、mmapを使用してCCUserSettingsのパフォーマンスを向上させるためのソリューションを用意します。CCMmapUserSettingsと呼んでいます。

前提条件

CCUserSettingssynchronizeまたはNSUserDefaults方法が戻ってディスクにplistファイルを書き込み、それが顕著な時間がかかりますが、私たちは、アプリがバックグラウンドになったときのようないくつかの状況でそれを呼び出す必要があります。それでも、私たちは設定を失うリスクを冒す:私たちのアプリはメモリが足りなくなったり、許可を得ていないアドレスにアクセスしたりするため、システムによって殺される可能性があります。その時、最新のsynchronize以降の設定は失われます。

プロセスが終了したときにファイルをディスクに書き込む方法がある場合は、常にメモリ内の設定を変更することができます。かなり高速です。しかし、それを達成する方法はありますか?

まあ、それはmmapですが、mmapはメモリ領域にファイルをマップします。これが完了すると、ファイルはプログラム内の配列のようにアクセスできます。したがって、ファイルを書き込むかのようにメモリを変更できます。プロセスが終了すると、メモリはファイルに書き戻します。

These days I come up with an idea to improve my CCUserSettings, it is mmap Memory-mapped I/O. I can map a virtual memory to a file and every time user calls synchronize, I create a NSData with NSPropertyListSerialization dataWithPropertyList:format:options:error: method and copy the data into that memory, operating system will write memory back to file when process exits. But I may not get a good performance because the file size is not fixed, every time the length of data increases, I have to remmap a virtual memory, I believe the operation is time consuming.

:私は私の質問で述べたようにmmap

を使用しての

Does the OS (POSIX) flush a memory-mapped file if the process is SIGKILLed?

mmap, msync and linux process termination

問題:

私をサポートする2つのリンクがあります。

問題は:データの長さが増えるたびに、私はmmap仮想メモリを再作成する必要があります、それは時間がかかる操作です。

ソリューションは、今、私は解決策を持っている:常に我々が必要以上に大きいサイズを作成し、ファイルの先頭4バイトで、実際のファイルサイズを維持し、4バイトの後に実際のデータを書き込みます。ファイルが必要以上に大きいので、データがスムーズに増加しているときは、毎回synchronizeの呼び出しでメモリをmmapに再設定する必要はありません。ファイルサイズには別の制限があります:ファイルサイズは常にMEM_PAGE_SIZEの倍数です(私のアプリケーションでは4096と定義されています)。

synchronizeメソッド:

- (BOOL)synchronize { 
    if (!_changed) { 
     return YES; 
    } 
    NSData *data = [NSPropertyListSerialization dataWithPropertyList:_settings format:NSPropertyListXMLFormat_v1_0 options:0 error:nil]; 
    // even if data.length + sizeof(_memoryLength) is a multiple of MEM_PAGE_SIZE, we need one more page. 
    unsigned int pageCount = (unsigned int)(data.length + sizeof(_memoryLength))/MEM_PAGE_SIZE + 1; 
    unsigned int fileSize = pageCount * MEM_PAGE_SIZE; 
    if (fileSize != _memoryLength) { 
     if (_memory) { 
      munmap(_memory, _memoryLength); 
      _memory = NULL; 
      _memoryLength = 0; 
     } 

     int res = ftruncate(fileno(_file), fileSize); 
     if (res == -1) { 
      // truncate file error 
      fclose(_file); 
      _file = NULL; 
      return NO; 
     } 
     // re-map the file 
     _memory = (unsigned char *)mmap(NULL, fileSize, PROT_READ|PROT_WRITE, MAP_SHARED, fileno(_file), 0); 
     _memoryLength = (unsigned int)fileSize; 
     if (_memory == MAP_FAILED) { 
      _memory = NULL; 
      fclose(_file); 
      _file = NULL; 
      return NO; 
     } 
#ifdef DEBUG 
     NSLog(@"memory map file success, size is %@", @(_memoryLength)); 
#endif 
    } 

    if (_memory) { 
     unsigned int length = (unsigned int)data.length; 
     length += sizeof(length); 
     memcpy(_memory, &length, sizeof(length)); 
     memcpy(_memory+sizeof(length), data.bytes, data.length); 
    } 
    return YES; 
} 

の例では、私の考えを説明するのに役立ちます:plistのデータサイズが5000バイトであると仮定し、私は書く必要があるの総バイト数は、私は4を書き込ん4 + 5000 = 5004でありますバイトの符号なし整数で、その値は5004です。その後、5000バイトのデータを書き込みます。合計ファイルサイズは8192(2 * MEM_PAGE_SIZE)にする必要があります。大きなファイルを作成するのは、メモリを再マップする時間を短縮するために大きなバッファが必要なためです。

パフォーマンス

{ 
    [[CCMmapUserSettings sharedUserSettings] loadUserSettingsWithUserId:@"1000"]; 
    NSDate *begin = [NSDate date]; 
    for (NSInteger i = 0; i < 1000; ++i) { 
     [[CCMmapUserSettings sharedUserSettings] setBool:(i%2==1) forKey:@"_boolKey"]; 
     [[CCMmapUserSettings sharedUserSettings] synchronize]; 
    } 
    NSDate *end = [NSDate date]; 
    NSLog(@"CCMmapUserSettings modified synchronize seconds:%f", [end timeIntervalSinceDate:begin]); 
} 

{ 
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"key"]; 
    NSDate *begin = [NSDate date]; 
    for (NSInteger i = 0; i < 1000; ++i) { 
     [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"key"]; 
     [[NSUserDefaults standardUserDefaults] synchronize]; 
    } 
    NSDate *end = [NSDate date]; 
    NSLog(@"NSUserDefaults not modified synchronize seconds:%f", [end timeIntervalSinceDate:begin]); 
} 

{ 
    NSDate *begin = [NSDate date]; 
    for (NSInteger i = 0; i < 1000; ++i) { 
     [[NSUserDefaults standardUserDefaults] setBool:(i%2==1) forKey:@"key"]; 
     [[NSUserDefaults standardUserDefaults] synchronize]; 
    } 
    NSDate *end = [NSDate date]; 
    NSLog(@"NSUserDefaults modified synchronize (memory not change) seconds:%f", [end timeIntervalSinceDate:begin]); 
} 

出力は次のようになります。

CCMmapUserSettings modified synchronize seconds:0.037747 
NSUserDefaults not modified synchronize seconds:0.479931 
NSUserDefaults modified synchronize (memory not change) seconds:0.402940 

それはNSUserDefaultsよりもそのCCMmapUserSettings実行が速く示し!!!

私は

CCMmapUserSettingsは私iPhone6(iOS版10.1.1)の単位の設定を渡しますが、私は公式を得ていないので、それはすべてのiOSバージョンで動作する場合、私は本当にわからないわからないんだけどファイルをマップするために使用されたメモリが、プロセスが終了した直後にディスクに書き戻されることを確認します。そうでない場合、デバイスがシャットダウンする前にディスクに書き込まれますか?

あなたが知っている人なら誰でも知っていれば、システムのふるまいを調べなければならないと思います。どうもありがとう。

+0

FWIW、NSUserDefaultsでの-synchronizeの呼び出しは、iOS 8以降では一般的に不要です。そうすることで、プログラムを遅くするだけです(exit()を呼び出して、データが安全に処理されるまで待つ必要がある場合は、悪いことではありません)。 mmap()の使用は、基底のボリュームをマウント解除できる場合、原子性が必要な場合、カーネルパニックが発生した場合、または他のプロセスがファイルを混乱させる可能性がある場合は危険です。気をつけてください:) –

+0

ファイルが1つのプロセスだけがiOSプラットフォームで実行され、1つのスレッドだけが 'mmap'を実行してメモリを変更する場合、それはまだ危険ですか?私はそれがとても速く走るので、本当に好きです! – KudoCC

+0

データを書き込む途中でシステムがクラッシュしても、いくらかのリスクはありますが、そういう使い方を慎重にコントロールできれば、mmapを使用するリスクははるかに小さくなります。システムクラッシュに対して堅牢な書き込みを行うための典型的なパターンは、mkstemp()を使用して一時ファイルを作成し、それにfsyncを書き込んだ後に元のファイルに名前を変更することです。残念ながらあなたが見たように、それは遅いです。 –

関連する問題