YapDatabase/Testing/Benchmarking/BenchmarkYapDatabase.m
2016-03-28 10:29:44 -07:00

337 lines
9.5 KiB
Objective-C

#import "BenchmarkYapDatabase.h"
#import "YapDatabase.h"
#import <stdlib.h>
@implementation BenchmarkYapDatabase
static YapDatabase *database;
static YapDatabaseConnection *connection;
static NSMutableArray *keys;
+ (NSString *)databaseName
{
return @"BenchmarkYapDatabase.sqlite";
}
+ (NSString *)databasePath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *baseDir = ([paths count] > 0) ? [paths objectAtIndex:0] : NSTemporaryDirectory();
return [baseDir stringByAppendingPathComponent:[self databaseName]];
}
+ (NSString *)randomLetters:(NSUInteger)length
{
NSString *alphabet = @"abcdefghijklmnopqrstuvwxyz";
NSUInteger alphabetLength = [alphabet length];
NSMutableString *result = [NSMutableString stringWithCapacity:length];
NSUInteger i;
for (i = 0; i < length; i++)
{
uint32_t randomIndex = arc4random_uniform((uint32_t)alphabetLength);
unichar c = [alphabet characterAtIndex:(NSUInteger)randomIndex];
[result appendFormat:@"%C", c];
}
return result;
}
+ (void)generateKeys:(NSUInteger)count
{
if (keys == nil)
keys = [[NSMutableArray alloc] initWithCapacity:count];
else
[keys removeAllObjects];
for (NSUInteger i = 0; i < count; i++)
{
[keys addObject:[self randomLetters:24]];
}
}
+ (void)populateDatabase
{
NSDate *start = [NSDate date];
[connection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
for (NSString *key in keys)
{
// For now, use key for object.
// Later we need to test with bigger objects with more serialization overhead.
[transaction setObject:key forKey:key inCollection:nil];
}
}];
NSTimeInterval elapsed = [start timeIntervalSinceNow] * -1.0;
NSLog(@"Populate database: total time: %.6f, added items: %lu", elapsed, (unsigned long)[keys count]);
}
+ (void)enumerateDatabase
{
NSDate *start = [NSDate date];
[connection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[transaction enumerateKeysInAllCollectionsUsingBlock:^(NSString __unused *collection, NSString __unused *key, BOOL __unused *stop) {
// Nothing to do, just testing overhead
}];
}];
NSTimeInterval elapsed = [start timeIntervalSinceNow] * -1.0;
NSLog(@"Enumerate keys: total time: %.6f, database count: %lu", elapsed, (unsigned long)[keys count]);
start = [NSDate date];
[connection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
[transaction enumerateKeysAndObjectsInAllCollectionsUsingBlock:
^(NSString __unused *collection, NSString __unused *key, id __unused object, BOOL __unused *stop) {
// Nothing to do, just testing overhead
}];
}];
elapsed = [start timeIntervalSinceNow] * -1.0;
NSLog(@"Enumerate keys & objects: total time: %.6f, database count: %lu", elapsed, (unsigned long)[keys count]);
}
+ (void)fetchValuesInLoop:(NSUInteger)loopCount withCacheHitPercentage:(float)hitPercentage
{
// Generate a random list of keys to fetch which satisfies the requested hit percentage
NSUInteger cacheSize = connection.objectCacheLimit;
NSAssert(cacheSize < loopCount, @"Can't satisfy hitPercentage because cacheSize is too big");
NSMutableOrderedSet *keysInCache = [NSMutableOrderedSet orderedSetWithCapacity:cacheSize];
NSMutableArray *keysToFetch = [NSMutableArray arrayWithCapacity:loopCount];
for (NSUInteger i = 0; i < loopCount; i++)
{
float rand = arc4random_uniform(1000) / 1000.0F;
if ((rand < hitPercentage) && ([keysInCache count] > 0))
{
// Pick a random key in the cache
uint32_t randomIndex = arc4random_uniform((uint32_t)[keysInCache count]);
NSString *key = [keysInCache objectAtIndex:(NSUInteger)randomIndex];
[keysToFetch addObject:key];
[keysInCache removeObject:key];
[keysInCache insertObject:key atIndex:0];
}
else
{
// Pick a random key NOT in the cache
BOOL found = NO;
do {
uint32_t randomIndex = arc4random_uniform((uint32_t)[keys count]);
NSString *key = [keys objectAtIndex:(NSUInteger)randomIndex];
if (![keysInCache containsObject:key])
{
found = YES;
[keysToFetch addObject:key];
[keysInCache insertObject:key atIndex:0];
if ([keysInCache count] > cacheSize) {
[keysInCache removeObjectAtIndex:cacheSize];
}
}
} while (!found);
}
}
// Execute fetches
NSDate *start = [NSDate date];
[connection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (NSString *key in keysToFetch)
{
(void)[transaction objectForKey:key inCollection:nil];
}
}];
NSTimeInterval elapsed = [start timeIntervalSinceNow] * -1.0;
double avg = (elapsed / loopCount);
double perSec = 1.0 / avg;
NSLog(@"Fetch %lu random objs (cache hit %%: %.2f): total time: %.6f, avg time per obj: %.6f, obj per sec: %.0f",
(unsigned long)loopCount, hitPercentage, elapsed, avg, perSec);
}
+ (void)readTransactionOverhead:(NSUInteger)loopCount withLongLivedReadTransaction:(BOOL)useLongLivedReadTransaction
{
if (useLongLivedReadTransaction) {
[connection beginLongLivedReadTransaction];
}
NSDate *start = [NSDate date];
for (NSUInteger i = 0; i < loopCount; i++)
{
[connection readWithBlock:^(YapDatabaseReadTransaction __unused *transaction) {
// Nothing to do, just testing overhead
}];
}
NSTimeInterval elapsed = [start timeIntervalSinceNow] * -1.0;
if (useLongLivedReadTransaction)
NSLog(@"ReadOnly transaction overhead : %.8f (using longLivedReadTransaction)", (elapsed / loopCount));
else
NSLog(@"ReadOnly transaction overhead : %.8f", (elapsed / loopCount));
if (useLongLivedReadTransaction) {
[connection endLongLivedReadTransaction];
}
}
+ (void)readWriteTransactionOverhead:(NSUInteger)loopCount
{
NSDate *start = [NSDate date];
for (NSUInteger i = 0; i < loopCount; i++)
{
[connection readWriteWithBlock:^(YapDatabaseReadTransaction __unused *transaction) {
// Nothing to do, just testing overhead
}];
}
NSTimeInterval elapsed = [start timeIntervalSinceNow] * -1.0;
NSLog(@"ReadWrite transaction overhead: %.8f", (elapsed / loopCount));
}
+ (void)removeAllValues
{
NSDate *start = [NSDate date];
[connection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction removeAllObjectsInAllCollections];
}];
NSTimeInterval elapsed = [start timeIntervalSinceNow] * -1.0;
NSLog(@"Remove all transaction: total time: %.6f", elapsed);
}
+ (void)runTestsWithCompletion:(dispatch_block_t)completionBlock
{
NSString *databasePath = [self databasePath];
// Delete old database file (if exists)
[[NSFileManager defaultManager] removeItemAtPath:databasePath error:NULL];
// Create database
YapDatabaseOptions *options = [[YapDatabaseOptions alloc] init];
// options.pragmaSynchronous = YapDatabasePragmaSynchronous_Normal; // Use for faster speed
// options.pragmaSynchronous = YapDatabasePragmaSynchronous_Off; // Use for fastest speed
options.pragmaMMapSize = (1024 * 1024 * 25); // full file size, with max of 25 MB
database = [[YapDatabase alloc] initWithPath:databasePath
serializer:NULL
deserializer:NULL
options:options];
// Create database connection (can have multiple for concurrency)
connection = [database newConnection];
connection.objectCacheLimit = 250; // default size
// Setup
[self generateKeys:1000];
// Run tests
dispatch_async(dispatch_get_main_queue(), ^{
NSString *pragmaSynchronousStr;
switch(options.pragmaSynchronous)
{
case YapDatabasePragmaSynchronous_Off : pragmaSynchronousStr = @"Off"; break;
case YapDatabasePragmaSynchronous_Normal : pragmaSynchronousStr = @"Normal"; break;
case YapDatabasePragmaSynchronous_Full : pragmaSynchronousStr = @"Full"; break;
default : pragmaSynchronousStr = @"Unknown"; break;
}
NSLog(@" \n\n\n ");
NSLog(@"YapDatabase Benchmarks:");
NSLog(@" - sqlite version = %@", database.sqliteVersion);
NSLog(@" - pragma synchronous = %@", pragmaSynchronousStr);
NSLog(@" - pragma mmap_size = %ld", (long)[connection pragmaMMapSize]);
NSLog(@"====================================================");
NSLog(@"POPULATE DATABASE");
[self populateDatabase];
NSLog(@"====================================================");
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"ENUMERATE DATABASE");
[self enumerateDatabase];
NSLog(@"====================================================");
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"FETCH DATABASE");
[self fetchValuesInLoop:500 withCacheHitPercentage:0.05f];
[self fetchValuesInLoop:500 withCacheHitPercentage:0.25f];
[self fetchValuesInLoop:500 withCacheHitPercentage:0.50f];
[self fetchValuesInLoop:500 withCacheHitPercentage:0.75f];
[self fetchValuesInLoop:500 withCacheHitPercentage:0.95f];
NSLog(@"====================================================");
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"TRANSACTION OVERHEAD");
[self readTransactionOverhead:1000 withLongLivedReadTransaction:YES];
[self readTransactionOverhead:1000 withLongLivedReadTransaction:NO];
[self readWriteTransactionOverhead:1000];
NSLog(@"====================================================");
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"REMOVE ALL");
[self removeAllValues];
NSLog(@"====================================================");
});
dispatch_async(dispatch_get_main_queue(), ^{
database = nil;
connection = nil;
keys = nil;
completionBlock();
});
}
@end