마지막으로 mmap을 사용하여 CCUserSettings
의 성능을 향상시킬 수있는 해결책을 찾았습니다. 전화 번호는 CCMmapUserSettings
입니다.
전제 조건
(가) synchronize
CCUserSettings
또는 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
, 알고 있다면 공유하십시오. 매우 감사합니다.
을 NSUserDefaults''에 대한 좋은 기사입니다 : http://dscoder.com /defaults.html. 그 저자는 Apple의 엔지니어이기 때문에 그가 말하는 것에 대해 잘 알고 있다고 생각할 수 있습니다. – Losiowaty
@Losiowaty 링크를 주셔서 감사합니다.하지만 Mac ox에서의 구현에 대해 이야기합니다. "값 설정하기 (결국 비동기식이고 나중에 다른 프로세스에서 약간의 시간이 소요됨) 변경 사항이 아무리 작아도 전체 plist를 디스크에 기록합니다. " NSUserDefaults를 수정하고'synchronize '하지 않고 app을 죽이면 설정은 파일에 쓰지 않을 것이므로 iOS에 파일을 쓰는 다른 프로세스가 있다고 생각하지 않습니다. – KudoCC
NSUserDefaults를 수정하고 응용 프로그램을 종료하면 데이터를 잃어 버리기 위해 강제 종료 버튼에서 * 매우 * 빠릅니다. 몇 밀리 초 정도. 이것은 iOS 8에서 변경되었습니다. 그 전에는 데이터를 잃는 것이 훨씬 쉬웠습니다. –