Skip to content

Commit 0e475dd

Browse files
committed
Significant performance improvement when running lots of queries via the query editor
1 parent 97c1b85 commit 0e475dd

File tree

1 file changed

+104
-3
lines changed

1 file changed

+104
-3
lines changed

Source/SPCustomQuery.m

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,44 @@
6767
#import <pthread.h>
6868
#import <SPMySQL/SPMySQL.h>
6969

70+
#include <libkern/OSAtomic.h>
71+
72+
typedef struct {
73+
NSUInteger query;
74+
NSUInteger total;
75+
} QueryProgress;
76+
77+
typedef void (^QueryProgressHandler)(QueryProgress *);
78+
79+
/**
80+
* This class is used to decouple the background thread running the SQL queries
81+
* from the main thread displaying the progress.
82+
*
83+
* It ensures that
84+
* - the background thread will not get blocked because it has to wait for the UI update to complete,
85+
* - the main thread does not have to process more progress updates than it can handle and
86+
* - the UI is updated at most once for each query (important with slow queries)
87+
*
88+
* This has quite a significant performance impact:
89+
* - Both threads synchronized on progress update =>
90+
* running 2500 queries takes about 157 seconds wall time (SP reported time: 10.5s) and the UI is updated 2500 times
91+
* - With this class decoupling them =>
92+
* running 2500 queries takes about 1 second wall time (SP reported time: 605ms) and the UI is updated 80 times
93+
*/
94+
@interface SPQueryProgressUpdateDecoupling : NSObject
95+
{
96+
QueryProgressHandler updateHandler;
97+
98+
volatile BOOL dirtyMarker;
99+
volatile OSSpinLock qpLock;
100+
volatile QueryProgress _queryProgress;
101+
}
102+
@property(atomic,assign) QueryProgress queryProgress;
103+
- (instancetype)initWithBlock:(QueryProgressHandler)anUpdateHandler;
104+
- (void)_updateProgress;
105+
@end
106+
107+
70108
@interface SPCustomQuery ()
71109

72110
- (id)_resultDataItemAtRow:(NSInteger)row columnIndex:(NSUInteger)column preserveNULLs:(BOOL)preserveNULLs asPreview:(BOOL)asPreview;
@@ -656,13 +694,18 @@ - (void)performQueriesTask:(NSDictionary *)taskArguments
656694
taskButtonString = (queryCount > 1)? NSLocalizedString(@"Stop queries", @"Stop queries string") : NSLocalizedString(@"Stop query", @"Stop query string");
657695
[tableDocumentInstance enableTaskCancellationWithTitle:taskButtonString callbackObject:nil callbackFunction:NULL];
658696

697+
SPQueryProgressUpdateDecoupling *progressUpdater = [[SPQueryProgressUpdateDecoupling alloc] initWithBlock:^(QueryProgress *qp) {
698+
NSString *taskString = [NSString stringWithFormat:NSLocalizedString(@"Running query %ld of %lu...", @"Running multiple queries string"), (long)(qp->query+1), (unsigned long)(qp->total)];
699+
[tableDocumentInstance setTaskDescription:taskString];
700+
[errorText setString:taskString];
701+
}];
702+
659703
// Perform the supplied queries in series
660704
for ( i = 0 ; i < queryCount ; i++ ) {
661705

662706
if (i > 0) {
663-
NSString *taskString = [NSString stringWithFormat:NSLocalizedString(@"Running query %ld of %lu...", @"Running multiple queries string"), (long)(i+1), (unsigned long)queryCount];
664-
[[tableDocumentInstance onMainThread] setTaskDescription:taskString];
665-
[[errorText onMainThread] setString:taskString];
707+
QueryProgress qp = { .query = i, .total = queryCount};
708+
[progressUpdater setQueryProgress:qp];
666709
}
667710

668711
NSString *query = [NSArrayObjectAtIndex(queries, i) stringByTrimmingCharactersInSet:whitespaceAndNewlineSet];
@@ -811,6 +854,8 @@ - (void)performQueriesTask:(NSDictionary *)taskArguments
811854
if ([mySQLConnection lastQueryWasCancelled]) break;
812855
}
813856

857+
[progressUpdater release];
858+
814859
// Reload table list if at least one query began with drop, alter, rename, or create
815860
if(tableListNeedsReload || databaseWasChanged) {
816861
// Build database pulldown menu
@@ -3606,3 +3651,59 @@ - (void)dealloc
36063651
}
36073652

36083653
@end
3654+
3655+
#pragma mark -
3656+
3657+
@implementation SPQueryProgressUpdateDecoupling
3658+
3659+
- (instancetype)initWithBlock:(QueryProgressHandler)anUpdateHandler
3660+
{
3661+
self = [super init];
3662+
if (self) {
3663+
updateHandler = [anUpdateHandler copy];
3664+
qpLock = OS_SPINLOCK_INIT;
3665+
}
3666+
3667+
return self;
3668+
}
3669+
3670+
- (void)setQueryProgress:(QueryProgress)newProgress
3671+
{
3672+
BOOL notify = NO;
3673+
OSSpinLockLock(&qpLock);
3674+
_queryProgress = newProgress;
3675+
if(!dirtyMarker) {
3676+
dirtyMarker = 1;
3677+
notify = YES;
3678+
}
3679+
OSSpinLockUnlock(&qpLock);
3680+
3681+
if(notify) [self performSelectorOnMainThread:@selector(_updateProgress) withObject:nil waitUntilDone:NO];
3682+
}
3683+
3684+
- (QueryProgress)queryProgress
3685+
{
3686+
OSSpinLockLock(&qpLock);
3687+
QueryProgress cpy = _queryProgress;
3688+
OSSpinLockUnlock(&qpLock);
3689+
return cpy;
3690+
}
3691+
3692+
- (void)_updateProgress
3693+
{
3694+
OSSpinLockLock(&qpLock);
3695+
bool doRun = dirtyMarker;
3696+
dirtyMarker = NO;
3697+
QueryProgress p = _queryProgress;
3698+
OSSpinLockUnlock(&qpLock);
3699+
3700+
if(doRun) updateHandler(&p);
3701+
}
3702+
3703+
- (void)dealloc
3704+
{
3705+
SPClear(updateHandler);
3706+
[super dealloc];
3707+
}
3708+
3709+
@end

0 commit comments

Comments
 (0)