2016-12-15 2 views
2

내 사용자는 각 사용자의 plist 파일에 사용자 설정을 저장하고 싶습니다. one class called CCUserSettingsNSUserDefaults과 거의 동일한 인터페이스를 가지고 있으며 현재 사용자 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]); 

, NSUserDefaultsCCUserSettings에 비해 거의 20 배 빠르다. 이제 NSUserDefaults가 synchronize 메서드를 호출 할 때마다 plist 파일에 실제로 쓰는지 궁금해하기 시작합니다. 그렇지 않으면 프로세스가 종료되기 전에 데이터를 파일에 다시 기록 할 수있는 방법을 궁금해하기 시작합니다. 언제든지 죽을 수있다)?

요즘은 CCUserSettings을 개선하기위한 아이디어가 나오는데, mmapMemory-mapped I/O입니다. 가상 메모리를 파일에 매핑 할 수 있으며 사용자가 synchronize을 호출 할 때마다 NSPropertyListSerialization dataWithPropertyList:format:options:error: 메서드를 사용하여 NSData을 만들고 해당 메모리에 데이터를 복사하면 운영 체제는 프로세스가 종료 될 때 메모리에 파일을 다시 쓸 것입니다. 그러나 파일 크기가 고정되지 않기 때문에 좋은 성능을 얻지 못할 수도 있습니다. 데이터가 길어질 때마다 다시 가상 메모리가 필요합니다. 작업 시간이 많이 걸립니다.

내 중복 정보로 불편을 끼쳐 드려 죄송합니다. NSUserDefaults이 좋은 성능을 내기 위해 어떻게 작동하는지 알고 싶거나 누구도 내 조언을 개선해 줄 수 있습니까? CCUserSettings?

+1

을 NSUserDefaults''에 대한 좋은 기사입니다 : http://dscoder.com /defaults.html. 그 저자는 Apple의 엔지니어이기 때문에 그가 말하는 것에 대해 잘 알고 있다고 생각할 수 있습니다. – Losiowaty

+0

@Losiowaty 링크를 주셔서 감사합니다.하지만 Mac ox에서의 구현에 대해 이야기합니다. "값 설정하기 (결국 비동기식이고 나중에 다른 프로세스에서 약간의 시간이 소요됨) 변경 사항이 아무리 작아도 전체 plist를 디스크에 기록합니다. " NSUserDefaults를 수정하고'synchronize '하지 않고 app을 죽이면 설정은 파일에 쓰지 않을 것이므로 iOS에 파일을 쓰는 다른 프로세스가 있다고 생각하지 않습니다. – KudoCC

+0

NSUserDefaults를 수정하고 응용 프로그램을 종료하면 데이터를 잃어 버리기 위해 강제 종료 버튼에서 * 매우 * 빠릅니다. 몇 밀리 초 정도. 이것은 iOS 8에서 변경되었습니다. 그 전에는 데이터를 잃는 것이 훨씬 쉬웠습니다. –

답변

2

최신 운영 체제 (iOS 8 이상, macOS 10.10 이상)에서는 NSUserDefaults가 동기화를 호출 할 때 파일을 쓰지 않습니다. -set * 메소드를 호출하면 cfprefsd라는 프로세스에 비동기 메시지를 보내고 새로운 값을 저장하고 응답을 보낸 다음 나중에 파일을 기록합니다. 모두 -synchronize는 cfprefsd가 해결되지 않은 모든 메시지가 응답을받을 때까지 대기합니다.

(편집 : 사용자의 기본 xpc_connection_send_message_with_reply에 상징적 중단 점을 설정 한 후 설정하여, 원하는 경우,이를 확인할 수) 여기

+0

답변 해 주셔서 감사합니다! 그러나 다른 프로세스에서 입출력 작업이 발생하면 '나가기'를 호출하기 직전에 설정 한 설정을 잃은 이유는 무엇입니까? – KudoCC

+0

'exit'를 호출하기 직전에 설정 한 설정을 잃어 버렸다는 사실이 사용자가 틀렸다는 것을 의미하지는 않습니다. cfprefsd 프로세스가 다른 메시지를 보내려면 프로세스가 필요할 수도 있습니다. 나는 단지 궁금해서 :) BTW 어떻게 알았습니까, 당신은 몇 가지 참조를 게시 할 수 있습니다. – KudoCC

+0

두 가지 이유가 있습니다. 첫 번째 메시지는 가장 간단한 메시지입니다. 메시지 전송은 비동기식이므로 메시지가 실제로 프로세스에서 벗어나기 전에 exit() 호출이 발생할 수 있습니다. 두 번째는 더 미묘합니다. cfprefsd는 샌드 박스 사용 권한을 확인하여 해당 환경 설정에 액세스 할 수 있도록해야합니다. 샌드 박스 권한을 확인하려면 프로세스가 계속 실행 중이어야합니다. –

2

마지막으로 mmap을 사용하여 CCUserSettings의 성능을 향상시킬 수있는 해결책을 찾았습니다. 전화 번호는 CCMmapUserSettings입니다.

전제 조건

(가) synchronizeCCUserSettings 또는 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

문제 :

저를 지원하는 두 개의 링크가 있습니다

문제는 데이터의 길이가 증가 할 때마다 가상 메모리를 다시 작성해야하므로 시간이 오래 걸립니다.

솔루션 지금은 해결책을 가지고 : 항상 우리가 필요한 것보다 더 큰 크기를 만들고 파일의 처음 4 바이트의 실제 파일 크기를 유지하고 4 바이트 후 실제 데이터를 작성합니다. 파일이 우리가 필요로하는 것보다 크기 때문에 데이터가 원활하게 증가 할 때 synchronize의 모든 호출에서 메모리를 mmap으로 다시 설정할 필요가 없습니다. 파일 크기에는 또 다른 제한이 있습니다. 파일 크기는 항상 MEM_PAGE_SIZE (내 앱에서 4096으로 정의 됨)의 배수입니다.

동기화 방법 :

- (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 바이트, 나는 = 5004 + 5000 4 I 4 쓰기됩니다 쓸 필요가 총 바이트 가정 부호없는 정수 값이 5004이면 먼저 5000 바이트 데이터를 씁니다. 총 파일 크기는 8192 (2 * MEM_PAGE_SIZE) 여야합니다. 큰 파일을 만드는 이유는 메모리를 다시 mmap하기위한 시간을 줄이기 위해 큰 버퍼가 필요하기 때문입니다.

는 성능

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

그것은 그 CCMmapUserSettings 빠르게 실행 NSUserDefaults보다 보여줍니다! 나는 공식을 확보하지 않았기 때문에이 모든 아이폰 OS 버전에서 작동하는 경우

은 정말 확실하지

CCMmapUserSettings 내 iPhone6 ​​(아이폰 OS 10.1.1)의 단위 설정을 전달 모르겠지만, 문서 파일을 매핑하는 데 사용되는 메모리가 프로세스가 종료 될 때 즉시 디스크에 다시 기록되는지 확인하십시오. 그렇지 않으면 장치가 종료되기 전에 디스크에 기록됩니까?

내가 시스템의 동작을 연구해야한다고 생각하는 사람이 있다면 mmap, 알고 있다면 공유하십시오. 매우 감사합니다.

+0

FWIW, NSUserDefaults에서 호출 - 동기화는 일반적으로 iOS 8 이상에서는 불필요합니다. 그렇게하면 exit()를 호출 할 때 데이터가 안전하게 처리 될 때까지 기다릴 필요가있을 때만 프로그램을 느리게 실행합니다 (때로는 좋지 않음). mmap()을 사용하면 기본 볼륨을 마운트 해제 할 수 있거나 원 자성이 필요한 경우, 커널 패닉이 발생하는 경우 또는 다른 프로세스가 파일을 엉망으로 만들 수있는 경우 위험합니다. 주의 : Use –

+0

오직 하나의 프로세스 만이 iOS 플랫폼에서 실행되고 하나의 스레드 만이'mmap'을 수행하고 메모리를 수정하는 것처럼 파일에 접근 할 수 있다면 여전히 위험할까요? 너무 빨리 달려서 정말 좋아해요 !!! – KudoCC

+0

데이터를 쓰는 도중에 시스템이 충돌하면 여전히 위험 할 수 있습니다. 그렇지만 사용법을 신중하게 제어 할 수 있다면 mmap을 사용할 때의 위험이 훨씬 더 적습니다. 시스템 충돌에 대해 강력하게 쓰기를 수행하는 일반적인 패턴은 mkstemp()를 사용하여 임시 파일을 만들고 그 파일에 fsync를 기록한 다음 원본 파일에 이름을 바꾸는 것입니다. 슬프게도 당신이 본 것처럼 느리다. –

관련 문제