An amazing project that generates micro reports from tournament results
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

732 lines
24 KiB

////////////////////////////////////////////////////////////////////////////
//
// Copyright 2014 Realm Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////
#import "RLMTestCase.h"
#import "RLMRealm_Dynamic.h"
#import "RLMRealm_Private.h"
#if !DEBUG && TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR
@interface PerformanceTests : RLMTestCase
@property (nonatomic) dispatch_queue_t queue;
@property (nonatomic) dispatch_semaphore_t sema;
@end
static RLMRealm *s_smallRealm, *s_mediumRealm, *s_largeRealm;
@implementation PerformanceTests
+ (void)setUp {
[super setUp];
s_smallRealm = [self createStringObjects:1];
s_mediumRealm = [self createStringObjects:5];
s_largeRealm = [self createStringObjects:50];
}
+ (void)tearDown {
s_smallRealm = s_mediumRealm = s_largeRealm = nil;
[RLMRealm resetRealmState];
[super tearDown];
}
- (void)resetRealmState {
// Do nothing, as we need to keep our in-memory realms around between tests
}
- (void)measureBlock:(void (^)(void))block {
[super measureBlock:^{
@autoreleasepool {
block();
}
}];
}
- (void)measureMetrics:(NSArray *)metrics automaticallyStartMeasuring:(BOOL)automaticallyStartMeasuring forBlock:(void (^)(void))block {
[super measureMetrics:metrics automaticallyStartMeasuring:automaticallyStartMeasuring forBlock:^{
@autoreleasepool {
block();
}
}];
}
+ (RLMRealm *)createStringObjects:(int)factor {
RLMRealmConfiguration *config = [RLMRealmConfiguration new];
config.inMemoryIdentifier = @(factor).stringValue;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
[realm beginWriteTransaction];
for (int i = 0; i < 1000 * factor; ++i) {
[StringObject createInRealm:realm withValue:@[@"a"]];
[StringObject createInRealm:realm withValue:@[@"b"]];
}
[realm commitWriteTransaction];
return realm;
}
- (RLMRealm *)testRealm {
RLMRealmConfiguration *config = [RLMRealmConfiguration new];
config.inMemoryIdentifier = @"test";
return [RLMRealm realmWithConfiguration:config error:nil];
}
- (void)testInsertMultiple {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = self.realmWithTestPath;
[self startMeasuring];
[realm beginWriteTransaction];
for (int i = 0; i < 5000; ++i) {
StringObject *obj = [[StringObject alloc] init];
obj.stringCol = @"a";
[realm addObject:obj];
}
[realm commitWriteTransaction];
[self stopMeasuring];
[self tearDown];
}];
}
- (void)testInsertSingleLiteral {
[self measureBlock:^{
RLMRealm *realm = self.realmWithTestPath;
for (int i = 0; i < 50; ++i) {
[realm beginWriteTransaction];
[StringObject createInRealm:realm withValue:@[@"a"]];
[realm commitWriteTransaction];
}
[self tearDown];
}];
}
- (void)testInsertMultipleLiteral {
[self measureBlock:^{
RLMRealm *realm = self.realmWithTestPath;
[realm beginWriteTransaction];
for (int i = 0; i < 5000; ++i) {
[StringObject createInRealm:realm withValue:@[@"a"]];
}
[realm commitWriteTransaction];
[self tearDown];
}];
}
- (RLMRealm *)getStringObjects:(int)factor {
RLMRealmConfiguration *config = [RLMRealmConfiguration new];
config.inMemoryIdentifier = @(factor).stringValue;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
[NSFileManager.defaultManager removeItemAtURL:RLMTestRealmURL() error:nil];
[realm writeCopyToURL:RLMTestRealmURL() encryptionKey:nil error:nil];
return [self realmWithTestPath];
}
- (void)testCountWhereQuery {
RLMRealm *realm = [self getStringObjects:50];
[self measureBlock:^{
for (int i = 0; i < 50; ++i) {
RLMResults *array = [StringObject objectsInRealm:realm where:@"stringCol = 'a'"];
[array count];
}
}];
}
- (void)testCountWhereTableView {
RLMRealm *realm = [self getStringObjects:50];
[self measureBlock:^{
for (int i = 0; i < 10; ++i) {
RLMResults *array = [StringObject objectsInRealm:realm where:@"stringCol = 'a'"];
[array firstObject]; // Force materialization of backing table view
[array count];
}
}];
}
- (void)testEnumerateAndAccessQuery {
RLMRealm *realm = [self getStringObjects:5];
[self measureBlock:^{
for (StringObject *so in [StringObject objectsInRealm:realm where:@"stringCol = 'a'"]) {
(void)[so stringCol];
}
}];
}
- (void)testEnumerateAndAccessAll {
RLMRealm *realm = [self getStringObjects:5];
[self measureBlock:^{
for (StringObject *so in [StringObject allObjectsInRealm:realm]) {
(void)[so stringCol];
}
}];
}
- (void)testEnumerateAndAccessAllSlow {
RLMRealm *realm = [self getStringObjects:5];
[self measureBlock:^{
RLMResults *all = [StringObject allObjectsInRealm:realm];
for (NSUInteger i = 0; i < all.count; ++i) {
(void)[all[i] stringCol];
}
}];
}
- (void)testEnumerateAndAccessArrayProperty {
RLMRealm *realm = [self getStringObjects:5];
[realm beginWriteTransaction];
ArrayPropertyObject *apo = [ArrayPropertyObject createInRealm:realm
withValue:@[@"name", [StringObject allObjectsInRealm:realm], @[]]];
[realm commitWriteTransaction];
[self measureBlock:^{
for (StringObject *so in apo.array) {
(void)[so stringCol];
}
}];
}
- (void)testEnumerateAndAccessArrayPropertySlow {
RLMRealm *realm = [self getStringObjects:5];
[realm beginWriteTransaction];
ArrayPropertyObject *apo = [ArrayPropertyObject createInRealm:realm
withValue:@[@"name", [StringObject allObjectsInRealm:realm], @[]]];
[realm commitWriteTransaction];
[self measureBlock:^{
RLMArray *array = apo.array;
for (NSUInteger i = 0; i < array.count; ++i) {
(void)[array[i] stringCol];
}
}];
}
- (void)testEnumerateAndMutateAll {
RLMRealm *realm = [self getStringObjects:5];
[self measureBlock:^{
[realm beginWriteTransaction];
for (StringObject *so in [StringObject allObjectsInRealm:realm]) {
so.stringCol = @"c";
}
[realm commitWriteTransaction];
}];
}
- (void)testEnumerateAndMutateQuery {
RLMRealm *realm = [self getStringObjects:1];
[self measureBlock:^{
[realm beginWriteTransaction];
for (StringObject *so in [StringObject objectsInRealm:realm where:@"stringCol != 'b'"]) {
so.stringCol = @"c";
}
[realm commitWriteTransaction];
}];
}
- (void)testQueryConstruction {
RLMRealm *realm = self.realmWithTestPath;
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"boolCol = false and (intCol = 5 or floatCol = 1.0) and objectCol = nil and longCol != 7 and stringCol IN {'a', 'b', 'c'}"];
[self measureBlock:^{
for (int i = 0; i < 500; ++i) {
[AllTypesObject objectsInRealm:realm withPredicate:predicate];
}
}];
}
- (void)testDeleteAll {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = [self getStringObjects:50];
[self startMeasuring];
[realm beginWriteTransaction];
[realm deleteObjects:[StringObject allObjectsInRealm:realm]];
[realm commitWriteTransaction];
[self stopMeasuring];
}];
}
- (void)testQueryDeletion {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = [self getStringObjects:5];
[self startMeasuring];
[realm beginWriteTransaction];
[realm deleteObjects:[StringObject objectsInRealm:realm where:@"stringCol = 'a' OR stringCol = 'b'"]];
[realm commitWriteTransaction];
[self stopMeasuring];
}];
}
- (void)testManualDeletion {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = [self getStringObjects:5];
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:10000];
for (StringObject *obj in [StringObject allObjectsInRealm:realm]) {
[objects addObject:obj];
}
[self startMeasuring];
[realm beginWriteTransaction];
[realm deleteObjects:objects];
[realm commitWriteTransaction];
[self stopMeasuring];
}];
}
- (void)testUnIndexedStringLookup {
RLMRealm *realm = self.realmWithTestPath;
[realm beginWriteTransaction];
for (int i = 0; i < 1000; ++i) {
[StringObject createInRealm:realm withValue:@[@(i).stringValue]];
}
[realm commitWriteTransaction];
[self measureBlock:^{
for (int i = 0; i < 1000; ++i) {
[[StringObject objectsInRealm:realm where:@"stringCol = %@", @(i).stringValue] firstObject];
}
}];
}
- (void)testIndexedStringLookup {
RLMRealm *realm = self.realmWithTestPath;
[realm beginWriteTransaction];
for (int i = 0; i < 1000; ++i) {
[IndexedStringObject createInRealm:realm withValue:@[@(i).stringValue]];
}
[realm commitWriteTransaction];
[self measureBlock:^{
for (int i = 0; i < 1000; ++i) {
[[IndexedStringObject objectsInRealm:realm where:@"stringCol = %@", @(i).stringValue] firstObject];
}
}];
}
- (void)testLargeINQuery {
RLMRealm *realm = self.realmWithTestPath;
[realm beginWriteTransaction];
NSMutableArray *ids = [NSMutableArray arrayWithCapacity:3000];
for (int i = 0; i < 3000; ++i) {
[IntObject createInRealm:realm withValue:@[@(i)]];
if (i % 2) {
[ids addObject:@(i)];
}
}
[realm commitWriteTransaction];
[self measureBlock:^{
(void)[[IntObject objectsInRealm:realm where:@"intCol IN %@", ids] firstObject];
}];
}
- (void)testSortingAllObjects {
RLMRealm *realm = self.realmWithTestPath;
[realm beginWriteTransaction];
for (int i = 0; i < 3000; ++i) {
[IntObject createInRealm:realm withValue:@[@(arc4random())]];
}
[realm commitWriteTransaction];
[self measureBlock:^{
(void)[[IntObject allObjectsInRealm:realm] sortedResultsUsingProperty:@"intCol" ascending:YES].lastObject;
}];
}
- (void)testRealmCreationCached {
__block RLMRealm *realm;
[self dispatchAsyncAndWait:^{
realm = [self realmWithTestPath]; // ensure a cached realm for the path
}];
[self measureBlock:^{
for (int i = 0; i < 250; ++i) {
@autoreleasepool {
[self realmWithTestPath];
}
}
}];
[realm configuration];
}
- (void)testRealmCreationUncached {
[self measureBlock:^{
for (int i = 0; i < 50; ++i) {
@autoreleasepool {
[self realmWithTestPath];
}
}
}];
}
- (void)testRealmFileCreation {
RLMRealmConfiguration *config = [RLMRealmConfiguration new];
__block int measurement = 0;
const int iterations = 10;
[self measureBlock:^{
for (int i = 0; i < iterations; ++i) {
@autoreleasepool {
config.inMemoryIdentifier = @(measurement * iterations + i).stringValue;
[RLMRealm realmWithConfiguration:config error:nil];
}
}
++measurement;
}];
}
- (void)testInvalidateRefresh {
RLMRealm *realm = [self testRealm];
[self measureBlock:^{
for (int i = 0; i < 50000; ++i) {
@autoreleasepool {
[realm invalidate];
[realm refresh];
}
}
}];
}
- (void)testCommitWriteTransaction {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = self.testRealm;
[realm beginWriteTransaction];
IntObject *obj = [IntObject createInRealm:realm withValue:@[@0]];
[realm commitWriteTransaction];
[self startMeasuring];
while (obj.intCol < 100) {
[realm transactionWithBlock:^{
obj.intCol++;
}];
}
[self stopMeasuring];
}];
}
- (void)testCommitWriteTransactionWithLocalNotification {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = self.testRealm;
[realm beginWriteTransaction];
IntObject *obj = [IntObject createInRealm:realm withValue:@[@0]];
[realm commitWriteTransaction];
RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) { }];
[self startMeasuring];
while (obj.intCol < 500) {
[realm transactionWithBlock:^{
obj.intCol++;
}];
}
[self stopMeasuring];
[token invalidate];
}];
}
- (void)testCommitWriteTransactionWithCrossThreadNotification {
const int stopValue = 500;
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = self.testRealm;
[realm beginWriteTransaction];
IntObject *obj = [IntObject createInRealm:realm withValue:@[@0]];
[realm commitWriteTransaction];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self dispatchAsync:^{
RLMRealm *realm = self.testRealm;
IntObject *obj = [[IntObject allObjectsInRealm:realm] firstObject];
__block RLMNotificationToken *token;
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) {
if (obj.intCol == stopValue) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
}];
dispatch_semaphore_signal(sema);
});
CFRunLoopRun();
[token invalidate];
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[self startMeasuring];
while (obj.intCol < stopValue) {
[realm transactionWithBlock:^{
obj.intCol++;
}];
}
[self dispatchAsyncAndWait:^{}];
[self stopMeasuring];
}];
}
- (void)testCommitWriteTransactionWithResultsNotification {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = [self getStringObjects:5];
RLMResults *results = [StringObject allObjectsInRealm:realm];
id token = [results addNotificationBlock:^(__unused RLMResults *results, __unused RLMCollectionChange *change, __unused NSError *error) {
CFRunLoopStop(CFRunLoopGetCurrent());
}];
CFRunLoopRun();
[realm beginWriteTransaction];
[realm deleteObjects:[StringObject objectsInRealm:realm where:@"stringCol = 'a'"]];
[realm commitWriteTransaction];
[self startMeasuring];
CFRunLoopRun();
[token invalidate];
}];
}
- (void)testCommitWriteTransactionWithListNotification {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = [self getStringObjects:5];
[realm beginWriteTransaction];
ArrayPropertyObject *arrayObj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", [StringObject allObjectsInRealm:realm], @[]]];
[realm commitWriteTransaction];
id token = [arrayObj.array addNotificationBlock:^(__unused RLMArray *results, __unused RLMCollectionChange *change, __unused NSError *error) {
CFRunLoopStop(CFRunLoopGetCurrent());
}];
CFRunLoopRun();
[realm beginWriteTransaction];
[realm deleteObjects:[StringObject objectsInRealm:realm where:@"stringCol = 'a'"]];
[realm commitWriteTransaction];
[self startMeasuring];
CFRunLoopRun();
[token invalidate];
}];
}
- (void)testCrossThreadSyncLatency {
const int stopValue = 500;
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = self.testRealm;
[realm beginWriteTransaction];
[realm deleteAllObjects];
IntObject *obj = [IntObject createInRealm:realm withValue:@[@0]];
[realm commitWriteTransaction];
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[self dispatchAsync:^{
RLMRealm *realm = self.testRealm;
IntObject *obj = [[IntObject allObjectsInRealm:realm] firstObject];
__block RLMNotificationToken *token;
CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^{
token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) {
if (obj.intCol == stopValue) {
CFRunLoopStop(CFRunLoopGetCurrent());
}
else if (obj.intCol % 2 == 0) {
[realm transactionWithBlock:^{
obj.intCol++;
}];
}
}];
dispatch_semaphore_signal(sema);
});
CFRunLoopRun();
[token invalidate];
}];
RLMNotificationToken *token = [realm addNotificationBlock:^(__unused NSString *note, __unused RLMRealm *realm) {
if (obj.intCol % 2 == 1 && obj.intCol < stopValue) {
[realm transactionWithBlock:^{
obj.intCol++;
}];
}
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
[self startMeasuring];
[realm transactionWithBlock:^{
obj.intCol++;
}];
while (obj.intCol < stopValue) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[self dispatchAsyncAndWait:^{}];
[self stopMeasuring];
[token invalidate];
}];
}
- (void)testArrayKVOIndexHandlingRemoveForward {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = [self getStringObjects:50];
[realm beginWriteTransaction];
ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", [StringObject allObjectsInRealm:realm], @[]]];
[realm commitWriteTransaction];
const NSUInteger initial = obj.array.count;
[self observeObject:obj keyPath:@"array"
until:^(id obj) { return [obj array].count < initial; }];
[self startMeasuring];
[realm beginWriteTransaction];
for (NSUInteger i = 0; i < obj.array.count; i += 10) {
[obj.array removeObjectAtIndex:i];
}
[realm commitWriteTransaction];
dispatch_sync(_queue, ^{});
}];
}
- (void)testArrayKVOIndexHandlingRemoveBackwards {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = [self getStringObjects:50];
[realm beginWriteTransaction];
ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", [StringObject allObjectsInRealm:realm], @[]]];
[realm commitWriteTransaction];
const NSUInteger initial = obj.array.count;
[self observeObject:obj keyPath:@"array"
until:^(id obj) { return [obj array].count < initial; }];
[self startMeasuring];
[realm beginWriteTransaction];
for (NSUInteger i = obj.array.count; i > 0; i -= i > 10 ? 10 : i) {
[obj.array removeObjectAtIndex:i - 1];
}
[realm commitWriteTransaction];
dispatch_sync(_queue, ^{});
}];
}
- (void)testArrayKVOIndexHandlingInsertCompact {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = [self getStringObjects:50];
[realm beginWriteTransaction];
ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]];
[realm commitWriteTransaction];
const NSUInteger count = [StringObject allObjectsInRealm:realm].count / 8;
const NSUInteger factor = count / 10;
[self observeObject:obj keyPath:@"array"
until:^(id obj) { return [obj array].count >= count; }];
RLMArray *array = obj.array;
[self startMeasuring];
[realm beginWriteTransaction];
for (StringObject *so in [StringObject allObjectsInRealm:realm]) {
[array addObject:so];
if (array.count % factor == 0) {
[realm commitWriteTransaction];
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
[realm beginWriteTransaction];
}
if (array.count > count) {
break;
}
}
[realm commitWriteTransaction];
dispatch_sync(_queue, ^{});
}];
}
- (void)testArrayKVOIndexHandlingInsertSparse {
[self measureMetrics:self.class.defaultPerformanceMetrics automaticallyStartMeasuring:NO forBlock:^{
RLMRealm *realm = [self getStringObjects:50];
[realm beginWriteTransaction];
ArrayPropertyObject *obj = [ArrayPropertyObject createInRealm:realm withValue:@[@"", @[], @[]]];
[realm commitWriteTransaction];
const NSUInteger count = [StringObject allObjectsInRealm:realm].count / 8;
const NSUInteger factor = count / 10;
[self observeObject:obj keyPath:@"array"
until:^(id obj) { return [obj array].count >= count; }];
RLMArray *array = obj.array;
[self startMeasuring];
[realm beginWriteTransaction];
for (StringObject *so in [StringObject allObjectsInRealm:realm]) {
NSUInteger index = array.count;
if (array.count > factor) {
index = index * 3 % factor;
}
[array insertObject:so atIndex:index];
if (array.count % factor == 0) {
[realm commitWriteTransaction];
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
[realm beginWriteTransaction];
}
if (array.count > count) {
break;
}
}
[realm commitWriteTransaction];
dispatch_sync(_queue, ^{});
}];
}
- (void)observeObject:(RLMObject *)object keyPath:(NSString *)keyPath until:(int (^)(id))block {
self.sema = dispatch_semaphore_create(0);
self.queue = dispatch_queue_create("bg", 0);
RLMRealmConfiguration *config = object.realm.configuration;
NSString *className = [object.class className];
dispatch_async(_queue, ^{
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
id obj = [[realm allObjects:className] firstObject];
[obj addObserver:self forKeyPath:keyPath options:(NSKeyValueObservingOptions)0 context:(__bridge void *)_sema];
dispatch_semaphore_signal(_sema);
while (!block(obj)) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[obj removeObserver:self forKeyPath:keyPath context:(__bridge void *)_sema];
});
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
}
- (void)observeValueForKeyPath:(__unused NSString *)keyPath
ofObject:(__unused id)object
change:(__unused NSDictionary *)change
context:(void *)context {
dispatch_semaphore_signal((__bridge dispatch_semaphore_t)context);
}
@end
#endif