From ef60b2022d50b99e6de78cc301bf71e8b336ae0e Mon Sep 17 00:00:00 2001 From: rowanbeentje Date: Tue, 13 Aug 2013 23:49:31 +0000 Subject: Rework table content and custom query data loading and storage for speed increases and lower memory usage: - Add a new SPMySQLStreamingResultStore class to SPMySQL.framework. This class acts as both a result set and a data store for the accompanying data, storing the row information in a custom format in a custom malloc zone. - Amend SPDataStorage to wrap the new class, so original result information is stored in the one location in the custom format. Any edited information is handled by SPDataStorage for clean separation - Rework table content and custom query data data stores to use the new class. This significantly speeds up data loading, resulting in faster data loads if they weren't previously network constrained, or lower CPU usage otherwise. The memory usage is also lowered, with the memory overhead for many small cells being enormously reduced. --- Source/SPTableContent.m | 139 +++++++++++++++++++----------------------------- 1 file changed, 55 insertions(+), 84 deletions(-) (limited to 'Source/SPTableContent.m') diff --git a/Source/SPTableContent.m b/Source/SPTableContent.m index f093f875..cc0102f2 100644 --- a/Source/SPTableContent.m +++ b/Source/SPTableContent.m @@ -72,6 +72,12 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOperator"; #endif +@interface SPTableContent (SPTableContentDataSource_Private_API) + +- (id)_contentValueForTableColumn:(NSUInteger)columnIndex row:(NSUInteger)rowIndex asPreview:(BOOL)asPreview; + +@end + @interface SPTableContent () - (BOOL)cancelRowEditing; @@ -165,6 +171,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper usedQuery = [[NSString alloc] initWithString:@""]; tableLoadTimer = nil; + tableLoadingCondition = [NSCondition new]; blackColor = [NSColor blackColor]; lightGrayColor = [NSColor lightGrayColor]; @@ -609,10 +616,9 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper [dataCell setEditable:YES]; - // Set the line break mode and an NSFormatter subclass which truncates long strings for display + // Set the line break mode and an NSFormatter subclass which displays line breaks nicely [dataCell setLineBreakMode:NSLineBreakByTruncatingTail]; [dataCell setFormatter:[[SPDataCellFormatter new] autorelease]]; - [[dataCell formatter] setDisplayLimit:150]; // Set field length limit if field is a varchar to match varchar length if ([[columnDefinition objectForKey:@"typegrouping"] isEqualToString:@"string"] @@ -765,7 +771,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper NSMutableString *queryString; NSString *queryStringBeforeLimit = nil; NSString *filterString; - SPMySQLFastStreamingResult *streamingResult; + SPMySQLStreamingResultStore *resultStore; NSInteger rowsToLoad = [[tableDataInstance statusValueForKey:@"Rows"] integerValue]; #ifndef SP_CODA @@ -828,23 +834,23 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Perform and process the query [tableContentView performSelectorOnMainThread:@selector(noteNumberOfRowsChanged) withObject:nil waitUntilDone:YES]; [self setUsedQuery:queryString]; - streamingResult = [[mySQLConnection streamingQueryString:queryString] retain]; + resultStore = [[mySQLConnection resultStoreFromQueryString:queryString] retain]; // Ensure the number of columns are unchanged; if the column count has changed, abort the load // and queue a full table reload. BOOL fullTableReloadRequired = NO; - if (streamingResult && [dataColumns count] != [streamingResult numberOfFields]) { + if (resultStore && [dataColumns count] != [resultStore numberOfFields]) { [tableDocumentInstance disableTaskCancellation]; [mySQLConnection cancelCurrentQuery]; - [streamingResult cancelResultLoad]; + [resultStore cancelResultLoad]; fullTableReloadRequired = YES; } // Process the result into the data store - if (!fullTableReloadRequired && streamingResult) { - [self processResultIntoDataStorage:streamingResult approximateRowCount:rowsToLoad]; + if (!fullTableReloadRequired && resultStore) { + [self updateResultStore:resultStore approximateRowCount:rowsToLoad]; } - if (streamingResult) [streamingResult release]; + if (resultStore) [resultStore release]; // If the result is empty, and a late page is selected, reset the page if (!fullTableReloadRequired && [prefs boolForKey:SPLimitResults] && queryStringBeforeLimit && !tableRowsCount && ![mySQLConnection lastQueryWasCancelled]) { @@ -852,10 +858,10 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper previousTableRowsCount = tableRowsCount; queryString = [NSMutableString stringWithFormat:@"%@ LIMIT 0,%ld", queryStringBeforeLimit, (long)[prefs integerForKey:SPLimitResultsValue]]; [self setUsedQuery:queryString]; - streamingResult = [[mySQLConnection streamingQueryString:queryString] retain]; - if (streamingResult) { - [self processResultIntoDataStorage:streamingResult approximateRowCount:[prefs integerForKey:SPLimitResultsValue]]; - [streamingResult release]; + resultStore = [[mySQLConnection resultStoreFromQueryString:queryString] retain]; + if (resultStore) { + [self updateResultStore:resultStore approximateRowCount:[prefs integerForKey:SPLimitResultsValue]]; + [resultStore release]; } } @@ -950,7 +956,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Retrieve and cache the column definitions for editing views if (cqColumnDefinition) [cqColumnDefinition release]; - cqColumnDefinition = [[streamingResult fieldDefinitions] retain]; + cqColumnDefinition = [[resultStore fieldDefinitions] retain]; // Notify listenters that the query has finished @@ -985,100 +991,57 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper } /** - * Processes a supplied streaming result set, loading it into the data array. + * Processes a supplied streaming result store, monitoring the load and updating the data + * displayed during download. */ -- (void)processResultIntoDataStorage:(SPMySQLFastStreamingResult *)theResult approximateRowCount:(NSUInteger)targetRowCount +- (void)updateResultStore:(SPMySQLStreamingResultStore *)theResultStore approximateRowCount:(NSUInteger)targetRowCount; { NSUInteger i; NSUInteger dataColumnsCount = [dataColumns count]; - BOOL *columnBlobStatuses = malloc(dataColumnsCount * sizeof(BOOL)); tableLoadTargetRowCount = targetRowCount; - // Set the column count on the data store before setting up anything else - - // ensures that SPDataStorage is set up for timer-driven data loads - [tableValues setColumnCount:dataColumnsCount]; + // Update the data storage, updating the current store if appropriate + pthread_mutex_lock(&tableValuesLock); + [tableValues setDataStorage:theResultStore updatingExisting:!![tableValues count]]; + pthread_mutex_unlock(&tableValuesLock); - // Set up the table updates timer - [[self onMainThread] initTableLoadTimer]; + // Start the data downloading + [theResultStore startDownload]; - NSAutoreleasePool *dataLoadingPool; #ifndef SP_CODA NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance valueForKey:@"queryProgressBar"]; #else NSProgressIndicator *dataLoadingIndicator = [tableDocumentInstance queryProgressBar]; #endif - BOOL prefsLoadBlobsAsNeeded = -#ifndef SP_CODA - [prefs boolForKey:SPLoadBlobsAsNeeded] -#else - NO -#endif - ; - - // Build up an array of which columns are blobs for faster iteration - for ( i = 0; i < dataColumnsCount ; i++ ) { - columnBlobStatuses[i] = [tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]]; - } - - // Set up an autorelease pool for row processing - dataLoadingPool = [[NSAutoreleasePool alloc] init]; - // Loop through the result rows as they become available - tableRowsCount = 0; - for (NSArray *eachRow in theResult) { - pthread_mutex_lock(&tableValuesLock); - - if (tableRowsCount < previousTableRowsCount) { - SPDataStorageReplaceRow(tableValues, tableRowsCount, eachRow); - } else { - SPDataStorageAddRow(tableValues, eachRow); - } - - // Alter the values for hidden blob and text fields if appropriate - if ( prefsLoadBlobsAsNeeded ) { - for ( i = 0 ; i < dataColumnsCount ; i++ ) { - if (columnBlobStatuses[i]) { - SPDataStorageReplaceObjectAtRowAndColumn(tableValues, tableRowsCount, i, [SPNotLoaded notLoaded]); - } +#ifndef SP_CODA + // Set the column load states on the table values store + if ([prefs boolForKey:SPLoadBlobsAsNeeded]) { + for ( i = 0; i < dataColumnsCount ; i++ ) { + if ([tableDataInstance columnIsBlobOrText:[NSArrayObjectAtIndex(dataColumns, i) objectForKey:@"name"]]) { + [tableValues setColumnAsUnloaded:i]; } } - tableRowsCount++; + } +#endif - pthread_mutex_unlock(&tableValuesLock); + // Set up the table updates timer and wait for it to notify this thread about completion + [[self onMainThread] initTableLoadTimer]; - // Drain and reset the autorelease pool every ~1024 rows - if (!(tableRowsCount % 1024)) { - [dataLoadingPool drain]; - dataLoadingPool = [[NSAutoreleasePool alloc] init]; - } + [tableLoadingCondition lock]; + while (![tableValues dataDownloaded]) { + [tableLoadingCondition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.05]]; } - - // Clean up the interface update timer - [[self onMainThread] clearTableLoadTimer]; + [tableLoadingCondition unlock]; // If the final column autoresize wasn't performed, perform it if (tableLoadLastRowCount < 200) [[self onMainThread] autosizeColumns]; - // If the reloaded table is shorter than the previous table, remove the extra values from the storage - if (tableRowsCount < [tableValues count]) { - pthread_mutex_lock(&tableValuesLock); - [tableValues removeRowsInRange:NSMakeRange(tableRowsCount, [tableValues count] - tableRowsCount)]; - pthread_mutex_unlock(&tableValuesLock); - } - // Ensure the table is aware of changes - if ([NSThread isMainThread]) { - [tableContentView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:YES]; - } else { - [tableContentView performSelectorOnMainThread:@selector(noteNumberOfRowsChanged) withObject:nil waitUntilDone:YES]; - [tableContentView performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO]; - } + [[tableContentView onMainThread] noteNumberOfRowsChanged]; - // Clean up the autorelease pool and reset the progress indicator - [dataLoadingPool drain]; + // Reset the progress indicator [dataLoadingIndicator setIndeterminate:YES]; - - free(columnBlobStatuses); } /** @@ -1397,6 +1360,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper */ - (void) tableLoadUpdate:(NSTimer *)theTimer { + tableRowsCount = [tableValues count]; // Update the task interface as necessary if (!isFiltered && tableLoadTargetRowCount != NSUIntegerMax) { @@ -1414,6 +1378,13 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper return; } + if ([tableValues dataDownloaded]) { + [tableLoadingCondition lock]; + [tableLoadingCondition signal]; + [self clearTableLoadTimer]; + [tableLoadingCondition unlock]; + } + // Check whether a table update is required, based on whether new rows are // available to display. if (tableRowsCount == tableLoadLastRowCount) { @@ -1422,7 +1393,6 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper // Update the table display [tableContentView noteNumberOfRowsChanged]; - if (!tableLoadLastRowCount) [tableContentView setNeedsDisplay:YES]; // Update column widths in two cases: on very first rows displayed, and once // more than 200 rows are present. @@ -2452,7 +2422,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper for (NSTableColumn *tableColumn in tableColumns) { - [tempRow addObject:[self tableView:tableContentView objectValueForTableColumn:tableColumn row:i]]; + [tempRow addObject:[self _contentValueForTableColumn:[[tableColumn identifier] integerValue] row:i asPreview:NO]]; } [currentResult addObject:[NSArray arrayWithArray:tempRow]]; @@ -4274,6 +4244,7 @@ static NSString *SPTableFilterSetDefaultOperator = @"SPTableFilterSetDefaultOper if(fieldEditor) [fieldEditor release], fieldEditor = nil; [self clearTableLoadTimer]; + [tableLoadingCondition release]; [tableValues release]; pthread_mutex_destroy(&tableValuesLock); [dataColumns release]; -- cgit v1.2.3