나를 위해 그것은 일반적으로 성능입니다. 객체의 ivar에 액세스하는 것은 그러한 구조체를 포함하는 메모리에 대한 포인터를 사용하여 C의 구조체 멤버에 액세스하는 것만 큼 빠릅니다. 실제로 Objective-C 객체는 기본적으로 동적으로 할당 된 메모리에있는 C 구조체입니다. 이는 일반적으로 코드가 얻을 수있는 속도만큼 빠르며 수동으로 최적화 된 어셈블리 코드도 그보다 빠를 수는 없습니다.
게터 / 설정을 통해 ivar에 액세스하려면 Objective-C 메서드 호출이 필요합니다. Objective-C 메서드 호출은 "일반"C 함수 호출보다 훨씬 느리며 (최소 3-4 배), 심지어 일반 C 함수 호출도 이미 여러 배 느립니다. 구조체 멤버에 액세스 재산의 특성에 따라, 컴파일러에 의해 생성 된 세터 / 게터 구현이 기능에 또 다른 C 함수 호출을 포함 할 수있다 objc_getProperty
/ objc_setProperty
이는 것으로 retain
/ copy
/ autorelease
와 같은 객체 필요하고 필요한 경우 추가 원자 속성에 대한 spinlocking 수행합니다. 이것은 쉽게 비싸 질 수 있고 나는 50 % 느리다는 것에 대해 이야기하고 있지 않다.
이것을 시도하자 :
CFAbsoluteTime cft;
unsigned const kRuns = 1000 * 1000 * 1000;
cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
testIVar = i;
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"1: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);
cft = CFAbsoluteTimeGetCurrent();
for (unsigned i = 0; i < kRuns; i++) {
[self setTestIVar:i];
}
cft = CFAbsoluteTimeGetCurrent() - cft;
NSLog(@"2: %.1f picoseconds/run", (cft * 10000000000.0) / kRuns);
산출:
1: 23.0 picoseconds/run
2: 98.4 picoseconds/run
이것은 4.28 배 더 느리며 이것은 원자가 아닌 원시적 int이며, 가장 좋은 경우입니다 . 대부분의 다른 경우는 더 나쁩니다 (원자 NSString *
속성을 사용해보십시오 !). 따라서 각 ivar 액세스가 예상보다 4-5 배 느리다는 사실을 알 수 있다면 속성을 사용하는 것이 좋지만 (적어도 성능에 관해서는) 성능이 저하되는 상황이 많이 있습니다. 완전히 받아 들일 수 없습니다.
2015-10-20 업데이트
어떤 사람들은 이것이 실제 문제가 아니라고 주장합니다. 위의 코드는 순전히 합성이며 실제 응용 프로그램에서는 결코 눈치 채지 못할 것입니다. 이제 실제 샘플을 사용해 봅시다.
아래 코드는 Account
객체를 정의 합니다. 계정에는 소유자의 이름 ( NSString *
), 성별 ( enum
) 및 연령 ( unsigned
)과 잔액 ( int64_t
) 을 설명하는 속성이 있습니다 . 계정 개체에는 init
메서드와 compare:
메서드가 있습니다. 이 compare:
방법은 다음과 같이 정의됩니다. 남성 이전의 여성 주문, 알파벳순으로 된 주문, 이전 이전의 젊은 주문, 잔액 주문이 낮음에서 높음으로 정의됩니다.
사실이 두 계정 클래스가 존재 AccountA
하고 AccountB
. 구현을 살펴보면 compare:
메소드 가 하나만 제외하고 거의 완전히 동일하다는 것을 알 수 있습니다. AccountA
개체에 액세스 자신의 특성을 하면서, 방법 (게터)에 의해 AccountB
개체에 액세스 자신의 특성을 바르에 의해. 그것은 정말로 유일한 차이점입니다! 그들은 둘 다 getter로 비교하기 위해 다른 객체의 속성에 액세스합니다 (ivar로 액세스하는 것은 안전하지 않습니다! 다른 객체가 하위 클래스이고 getter를 재정의하면 어떻게됩니까?). 또한 ivar로 자신의 속성에 액세스해도 캡슐화가 중단되지 않습니다 (ivar은 여전히 공개되지 않음).
테스트 설정은 정말 간단합니다. 1 개의 Mio 임의 계정을 생성하고 배열에 추가하고 해당 배열을 정렬하십시오. 그게 다야. 물론 두 개의 배열이 있습니다. 하나는 AccountA
객체 용이고 다른 하나는 AccountB
객체 용이며 두 어레이 모두 동일한 계정 (동일한 데이터 소스)으로 채워져 있습니다. 배열을 정렬하는 데 시간이 얼마나 걸리나요?
어제 내가 한 몇 가지 실행 결과는 다음과 같습니다.
runTime 1: 4.827070, 5.002070, 5.014527, 5.019014, 5.123039
runTime 2: 3.835088, 3.804666, 3.792654, 3.796857, 3.871076
보다시피, AccountB
객체 배열을 정렬하는 것이 객체 배열을 정렬하는 것 보다 항상 훨씬 빠릅니다AccountA
.
최대 1.32 초의 런타임 차이로 인해 차이가 없다고 주장하는 사람은 UI 프로그래밍을 더 잘 수행해서는 안됩니다. 예를 들어 큰 테이블의 정렬 순서를 변경하려는 경우 이러한 시간 차이로 인해 사용자에게 큰 차이가 생길 수 있습니다 (허용되는 UI와 느린 UI의 차이).
또한이 경우 샘플 코드는 여기서 실제로 수행되는 유일한 작업이지만 코드는 복잡한 시계의 작은 장비입니까? 모든 기어가 이와 같이 전체 프로세스를 느리게하면 결국 전체 시계 속도에 어떤 의미가 있습니까? 특히 하나의 작업 단계가 다른 작업 단계의 결과에 의존하는 경우 모든 비 효율성이 요약됩니다. 대부분의 비 효율성 자체는 문제가 아니며 전체 프로세스에 문제가되는 것은 그 자체입니다. 그리고 그러한 문제는 프로파일 러가 중요한 핫스팟을 찾는 것이기 때문에 프로파일 러가 쉽게 보여줄 수있는 것은 아니지만, 이러한 비 효율성 자체는 핫스팟이 아닙니다. CPU 시간은 평균적으로 그 사이에 분산되어 있지만 각 CPU에는 그 비율이 매우 적으므로 최적화하는 데 총 시간 낭비가 있습니다. 그리고 사실입니다
CPU 시간에 대해 생각하지 않더라도 CPU 시간 낭비가 완전히 허용된다고 생각하기 때문에 "무료"라고 가정하면 전력 소비로 인한 서버 호스팅 비용은 어떻습니까? 모바일 장치의 배터리 런타임은 어떻습니까? 동일한 모바일 앱을 두 번 작성하는 경우 (예 : 자체 모바일 웹 브라우저), 한 번만 모든 클래스가 getter 만 자신의 속성에 액세스하는 버전이고 모든 클래스가 ivar 만 해당 속성에 액세스하는 버전 인 경우 첫 번째 앱을 계속 사용하면 배터리는 기능적으로 동일하지만 사용자에게는 두 번째 배터리를 사용하는 것보다 훨씬 빠릅니다.
다음은 main.m
파일 코드입니다 (코드는 ARC를 사용하도록 설정하고 컴파일 할 때 최적화를 사용하여 전체 효과를 확인하십시오).
#import <Foundation/Foundation.h>
typedef NS_ENUM(int, Gender) {
GenderMale,
GenderFemale
};
@interface AccountA : NSObject
@property (nonatomic) unsigned age;
@property (nonatomic) Gender gender;
@property (nonatomic) int64_t balance;
@property (nonatomic,nonnull,copy) NSString * name;
- (NSComparisonResult)compare:(nonnull AccountA *const)account;
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance;
@end
@interface AccountB : NSObject
@property (nonatomic) unsigned age;
@property (nonatomic) Gender gender;
@property (nonatomic) int64_t balance;
@property (nonatomic,nonnull,copy) NSString * name;
- (NSComparisonResult)compare:(nonnull AccountB *const)account;
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance;
@end
static
NSMutableArray * allAcocuntsA;
static
NSMutableArray * allAccountsB;
static
int64_t getRandom ( const uint64_t min, const uint64_t max ) {
assert(min <= max);
uint64_t rnd = arc4random(); // arc4random() returns a 32 bit value only
rnd = (rnd << 32) | arc4random();
rnd = rnd % ((max + 1) - min); // Trim it to range
return (rnd + min); // Lift it up to min value
}
static
void createAccounts ( const NSUInteger ammount ) {
NSArray *const maleNames = @[
@"Noah", @"Liam", @"Mason", @"Jacob", @"William",
@"Ethan", @"Michael", @"Alexander", @"James", @"Daniel"
];
NSArray *const femaleNames = @[
@"Emma", @"Olivia", @"Sophia", @"Isabella", @"Ava",
@"Mia", @"Emily", @"Abigail", @"Madison", @"Charlotte"
];
const NSUInteger nameCount = maleNames.count;
assert(maleNames.count == femaleNames.count); // Better be safe than sorry
allAcocuntsA = [NSMutableArray arrayWithCapacity:ammount];
allAccountsB = [NSMutableArray arrayWithCapacity:ammount];
for (uint64_t i = 0; i < ammount; i++) {
const Gender g = (getRandom(0, 1) == 0 ? GenderMale : GenderFemale);
const unsigned age = (unsigned)getRandom(18, 120);
const int64_t balance = (int64_t)getRandom(0, 200000000) - 100000000;
NSArray *const nameArray = (g == GenderMale ? maleNames : femaleNames);
const NSUInteger nameIndex = (NSUInteger)getRandom(0, nameCount - 1);
NSString *const name = nameArray[nameIndex];
AccountA *const accountA = [[AccountA alloc]
initWithName:name age:age gender:g balance:balance
];
AccountB *const accountB = [[AccountB alloc]
initWithName:name age:age gender:g balance:balance
];
[allAcocuntsA addObject:accountA];
[allAccountsB addObject:accountB];
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
@autoreleasepool {
NSUInteger ammount = 1000000; // 1 Million;
if (argc > 1) {
unsigned long long temp = 0;
if (1 == sscanf(argv[1], "%llu", &temp)) {
// NSUIntegerMax may just be UINT32_MAX!
ammount = (NSUInteger)MIN(temp, NSUIntegerMax);
}
}
createAccounts(ammount);
}
// Sort A and take time
const CFAbsoluteTime startTime1 = CFAbsoluteTimeGetCurrent();
@autoreleasepool {
[allAcocuntsA sortedArrayUsingSelector:@selector(compare:)];
}
const CFAbsoluteTime runTime1 = CFAbsoluteTimeGetCurrent() - startTime1;
// Sort B and take time
const CFAbsoluteTime startTime2 = CFAbsoluteTimeGetCurrent();
@autoreleasepool {
[allAccountsB sortedArrayUsingSelector:@selector(compare:)];
}
const CFAbsoluteTime runTime2 = CFAbsoluteTimeGetCurrent() - startTime2;
NSLog(@"runTime 1: %f", runTime1);
NSLog(@"runTime 2: %f", runTime2);
}
return 0;
}
@implementation AccountA
- (NSComparisonResult)compare:(nonnull AccountA *const)account {
// Sort by gender first! Females prior to males.
if (self.gender != account.gender) {
if (self.gender == GenderFemale) return NSOrderedAscending;
return NSOrderedDescending;
}
// Otherwise sort by name
if (![self.name isEqualToString:account.name]) {
return [self.name compare:account.name];
}
// Otherwise sort by age, young to old
if (self.age != account.age) {
if (self.age < account.age) return NSOrderedAscending;
return NSOrderedDescending;
}
// Last ressort, sort by balance, low to high
if (self.balance != account.balance) {
if (self.balance < account.balance) return NSOrderedAscending;
return NSOrderedDescending;
}
// If we get here, the are really equal!
return NSOrderedSame;
}
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance
{
self = [super init];
assert(self); // We promissed to never return nil!
_age = age;
_gender = gender;
_balance = balance;
_name = [name copy];
return self;
}
@end
@implementation AccountB
- (NSComparisonResult)compare:(nonnull AccountA *const)account {
// Sort by gender first! Females prior to males.
if (_gender != account.gender) {
if (_gender == GenderFemale) return NSOrderedAscending;
return NSOrderedDescending;
}
// Otherwise sort by name
if (![_name isEqualToString:account.name]) {
return [_name compare:account.name];
}
// Otherwise sort by age, young to old
if (_age != account.age) {
if (_age < account.age) return NSOrderedAscending;
return NSOrderedDescending;
}
// Last ressort, sort by balance, low to high
if (_balance != account.balance) {
if (_balance < account.balance) return NSOrderedAscending;
return NSOrderedDescending;
}
// If we get here, the are really equal!
return NSOrderedSame;
}
- (nonnull instancetype)initWithName:(nonnull NSString *const)name
age:(const unsigned)age gender:(const Gender)gender
balance:(const int64_t)balance
{
self = [super init];
assert(self); // We promissed to never return nil!
_age = age;
_gender = gender;
_balance = balance;
_name = [name copy];
return self;
}
@end