iOS GCD Max concurrent operations - ios

On an iPad how many parallel operations can start to get maximum performance? in each run a query operation , calculations , etc ...
It depends on the iPad model (CPU)?
int count = [objects count];
if (count > 0)
{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < count; i++)
{
dispatch_group_async(group, queue, ^{
for (int j = i + 1; j < count; j++)
{
dispatch_group_async(group, queue, ^{
/** LOTS AND LOTS OF WORK FOR EACH OBJECT **/
});
}
});
}
dispatch_group_notify(group, queue, ^{
/** END OF ALL OPERATIONS */
};
}

This is basically a UX question. Depends on the needs of the end user.
Does he really need all those computations started and finished quickly ?
Can you delay some or most of them ?
It's good practice to inform the user with a progress of each computation (a progress bar) and notify him upon completion.
Let him choose which to start / stop / pause (this is a very important feature).
If all the tasks are local - CPU intensive, and not related to network fetching of resources, then it depends on each CPU device - how many threads can it run in parallel.

Related

What is the overhead of running an empty block on a queue

I realized that I was queuing a lot of blocks calling to empty methods. In the debugger it looks like a lot is happening when really all the blocks are empty.
Is there any real performance impact from having empty blocks?
The overhead should be negligible: You may check this with Instruments and a simple program like:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
void (^b)(void) = ^{ };
double d = 2.0;
for(int i = 0; i < 10000000; ++i) {
dispatch_sync(q, b);
d = d * 1.5 - 1.0;
}
NSLog(#"d = %.3f", d);
}
return 0;
}
As you can see in the Instruments stack trace the calls require 40ms for 10 millions synchronous invocations of an empty block. That's not much overhead.

Retarget blocks submitted to dispatch queue

I have serial dispatch queue Q (with another serial queue T as a target) and few blocks already submitted via dispatch_async(Q, block). Is there a way to retarget pending blocks to another queue A?
My simple test shows that Q forwards blocks to T as soon as possible, thus setting new target has no effect:
#define print(f, ...) printf(f "\n", ##__VA_ARGS__)
dispatch_queue_t Q = dispatch_queue_create("Q", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t T = dispatch_queue_create("T", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t A = dispatch_queue_create("A", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(Q, T);
dispatch_async(T, ^{ print("T sleeping"); sleep(2); print("T ready"); });
dispatch_async(A, ^{ print("A sleeping"); sleep(5); print("A ready"); });
dispatch_async(Q, ^{
print("block 1");
dispatch_set_target_queue(Q, A); // no effect!
});
dispatch_async(Q, ^{ print("block 2"); });
dispatch_async(Q, ^{ print("block 3"); });
dispatch_async(Q, ^{ print("block 4"); });
Output:
A sleeping
T sleeping
(wait 2 seconds)
T ready
block 1
block 2
block 3
block 4
(wait 3 seconds)
A ready
As you can see, blocks 2-4 were pinned to T even though manual states:
The new target queue setting will take effect between block executions on the object, but not in the middle of any existing block executions (non-preemptive).
It is unclear to me if that only applies to dispatch sources, or "existing" means already submitted (even not yet executed) blocks, but anyway, thing doesn't happen for my serial queue.
Is there a way to do that?
Okay, after studying a bit I came up with the following solution. It is based on custom dispatch source, and I think it is the only way to submit blocks just-in-time.
BlockSource.h:
dispatch_source_t dispatch_block_source_create(dispatch_queue_t queue);
void dispatch_block_source_add_block(dispatch_source_t source, dispatch_block_t block);
BlockSource.c:
struct context {
CFMutableArrayRef array;
pthread_mutex_t mutex;
dispatch_source_t source;
};
static void
s_event(struct context *context)
{
dispatch_block_t block;
CFIndex pending;
pthread_mutex_lock(&context->mutex); {
block = CFArrayGetValueAtIndex(context->array, 0);
CFArrayRemoveValueAtIndex(context->array, 0);
pending = CFArrayGetCount(context->array);
}
pthread_mutex_unlock(&context->mutex);
block();
Block_release(block);
if (pending)
dispatch_source_merge_data(context->source, 1);
}
static void
s_cancel(struct context *context)
{
CFIndex count = CFArrayGetCount(context->array);
for (CFIndex i = 0; i < count; i++) {
dispatch_block_t block = CFArrayGetValueAtIndex(context->array, i);
Block_release(block);
}
CFRelease(context->array);
pthread_mutex_destroy(&context->mutex);
print("canceled");
}
dispatch_source_t
dispatch_block_source_create(dispatch_queue_t queue)
{
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_OR, 0, 0, queue);
struct context *context = calloc(1, sizeof(*context));
context->array = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
pthread_mutex_init(&context->mutex, NULL);
context->source = source;
dispatch_set_context(source, context);
dispatch_source_set_event_handler_f(source, (dispatch_function_t)s_event);
dispatch_source_set_cancel_handler_f(source, (dispatch_function_t)s_cancel);
dispatch_set_finalizer_f(source, (dispatch_function_t)free);
return source;
}
void
dispatch_block_source_add_block(dispatch_source_t source, dispatch_block_t block)
{
struct context *context = dispatch_get_context(source);
pthread_mutex_lock(&context->mutex); {
CFArrayAppendValue(context->array, Block_copy(block));
dispatch_source_merge_data(context->source, 1);
}
pthread_mutex_unlock(&context->mutex);
}
And the test case:
dispatch_queue_t T = dispatch_queue_create("T", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t A = dispatch_queue_create("A", DISPATCH_QUEUE_SERIAL);
static int queue_name_key;
dispatch_queue_set_specific(T, &queue_name_key, "T", NULL);
dispatch_queue_set_specific(A, &queue_name_key, "A", NULL);
dispatch_source_t source = dispatch_block_source_create(T);
dispatch_resume(source);
for (int i = 1; i <= 10; i++) {
dispatch_block_source_add_block(source, ^{
print("block %d on queue %s", i, dispatch_get_specific(&queue_name_key));
sleep(1);
if (i == 2) {
dispatch_set_target_queue(source, A);
}
else if (i == 5) {
dispatch_source_cancel(source);
dispatch_release(source);
}
});
}
Output:
block 1 on queue T
block 2 on queue T
block 3 on queue A
block 4 on queue A
block 5 on queue A
canceled
At least now it follows dispatch source's scheme after dispatch_set_target_queue() call. This means that if new target queue is set while inside one of submitted blocks, it is guaranteed that all remaining blocks will go to new queue.
May still contain bugs.

How to break the loop if we are using dispatch_apply?

if we are using GCD approach for iteration , how to break/stop the loop once the condition matched?
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_apply(count, queue, ^(size_t i) {
printf("%u\n", i);
//doing thread safe(also heavy) operation here
if (condition) {
//exit the loop
}
});
It is not possible to cancel dispatch_apply as not all operations are completed sequentially but concurrently. The purpose of dispatch_apply is to parallelize a for-loop where all iterations are independent from other iterations.
However you can use a boolean which indicates that the condition was satisfied. All pending operations are cancelled immediately as they are invoked.
__block BOOL stop = NO;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_apply(count, queue, ^(size_t i) {
if (stop)
return;
//Do stuff
if (condition)
stop = YES;
});

What does the priority of global queue means

I am learning GCD Now. I create two global queues with different priority:
dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t q2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
I execute the two queues:
dispatch_async(q1, ^{
[self task:#"q1"];
});
dispatch_async(q2, ^{
[self task:#"q2"];
});
- (void)task:(NSString *)taskid
{
for (int i = 0; i < 1000; i++) {
NSLog(#"Now executing taskid:%# num : %d", taskid, i);
[NSThread sleepForTimeInterval:5];
}
}
But the result shows that the two queues are executed concurrently, but not that the queue with higher priority executed firstly. So what does priority really means?
From the docs for DISPATCH_QUEUE_PRIORITY_HIGH:
Items dispatched to the queue run at high priority; the queue is scheduled for execution before any default priority or low priority queue.
So code from different queues can still run concurrently, it's just that the higher priority queue is scheduled for execution before lower priority queues. Since the two queues may be setup in different threads, the two threads can run together. But it is quite possible that the higher priority queue (thread) will be given more opportunity to complete than the lower priority queue (thread).
It might be interesting to time how long (clock time) it take task: to run (try several iterations) and see if the higher priority queue gets done faster than lower priority queues.
Of course, the answer is in dispatch_queue_priority_t Constants, but the text is a bit misleading.
Why you don't see the behavior you expected…
This is a guess (and only a guess). You didn't crowd the CPU. In your test, the CPU has plenty of time to execute all the queues. No scheduler will run tasks based solely on priority. That leads to low priority tasks never executing. The scheduler picks a chooses tasks using priority as only one of the criteria.
If you setup a test with 100 or a 1000 concurrent DISPATCH_QUEUE_PRIORITY_DEFAULT and DISPATCH_QUEUE_PRIORITY_LOW tasks, you might start to see how the scheduler favors higher priority tasks.
UPDATE
Can't believe how wrong I was…
The answer is in dispatch_queue_priority_t Constants and it will alway pick higher priority queues over lower priority queues just like the document says.
Here is my version of the sample code:
dispatch_queue_t q1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t q2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
for (int i = 0; i < 50; ++i) {
dispatch_async(q1, ^{
for (int j = 0; j < 5; ++j) {
NSLog(#"Now executing %#[%d:%d]", #"DISPATCH_QUEUE_PRIORITY_DEFAULT", i, j);
[NSThread sleepForTimeInterval:0.00001];
}
});
}
for (int i = 0; i < 50; ++i) {
dispatch_async(q2, ^{
for (int j = 0; j < 5; ++j) {
NSLog(#"Now executing %#[%d:%d]", #"DISPATCH_QUEUE_PRIORITY_LOW", i, j);
[NSThread sleepForTimeInterval:0.00001];
}
});
}
The output:
2014-04-15 21:27:22.525 APP_NAME[10651:1103] Now executing DISPATCH_QUEUE_PRIORITY_DEFAULT[0:0]
2014-04-15 21:27:22.526 APP_NAME[10651:1103] Now executing DISPATCH_QUEUE_PRIORITY_DEFAULT[0:1]
2014-04-15 21:27:22.526 APP_NAME[10651:3803] Now executing DISPATCH_QUEUE_PRIORITY_DEFAULT[1:0]
…
2014-04-15 21:27:22.810 APP_NAME[10651:3b03] Now executing DISPATCH_QUEUE_PRIORITY_DEFAULT[47:3]
2014-04-15 21:27:22.812 APP_NAME[10651:3b03] Now executing DISPATCH_QUEUE_PRIORITY_DEFAULT[47:4]
2014-04-15 21:27:22.812 APP_NAME[10651:3f03] Now executing DISPATCH_QUEUE_PRIORITY_DEFAULT[39:4]
2014-04-15 21:27:22.813 APP_NAME[10651:3d07] Now executing DISPATCH_QUEUE_PRIORITY_LOW[0:0]
2014-04-15 21:27:22.813 APP_NAME[10651:3f03] Now executing DISPATCH_QUEUE_PRIORITY_LOW[1:0]
2014-04-15 21:27:22.813 APP_NAME[10651:3d07] Now executing DISPATCH_QUEUE_PRIORITY_LOW[0:1]
…
2014-04-15 21:27:22.998 APP_NAME[10651:3d07] Now executing DISPATCH_QUEUE_PRIORITY_LOW[49:3]
2014-04-15 21:27:22.999 APP_NAME[10651:3f03] Now executing DISPATCH_QUEUE_PRIORITY_LOW[48:4]
2014-04-15 21:27:22.999 APP_NAME[10651:3d07] Now executing DISPATCH_QUEUE_PRIORITY_LOW[49:4]
No DISPATCH_QUEUE_PRIORITY_LOW task was executed before a DISPATCH_QUEUE_PRIORITY_DEFAULT

How to lock an array from audio processing for analysis

In my app I'm doing some audio processing.
In the for loop of the audio buffer, there is a NSMutable array. The loop is called a huge number of time every second (depending on the buffer size).
As an example :
#autoreleasepool
{
for ( int i = 0; i < tempBuffer.mDataByteSize / 2; ++i )
{
if ( samples[i] > trig)
{
[self.k_Array addObject:[NSNumber numberWithInt:k]];
// other stuff
}
}
}
Then, every second, I'm calling a function for other processing.
- (void)realtimeUpdate:(NSTimer*)theTimer
{
// Create a copy of the array
NSMutableArray *k_ArrayCopy = [NSMutableArray arrayWithArray:k_Array]; // CRASH with EXC_BAD_ACCESS code 1 error
//do some stuff with k_ArrayCopy
}
I sometime receive an EXC_BAD_ACCESS error because, I think, a locking problem of the array.
I spent a lot of time trying to get information on queues, locking, working copies, etc... but I'm lost on this specific case.
My questions :
do I have to use atomic or nonatomic for k_array ?
do I have to use a dispatch_sync function ? If so, where exactly ?
should the realtimeUpdate function be called on background ?
Thanks in advance !
Use dispatch queue that will solve problem
//create queue instance variable
dispatch_queue_t q = dispatch_queue_create("com.safearrayaccess.samplequeue", NULL);
//1.
#autoreleasepool
{
for ( int i = 0; i < tempBuffer.mDataByteSize / 2; ++i )
{
if ( samples[i] > trig)
{
dispatch_async(q, ^{
//queue block
[self.k_Array addObject:[NSNumber numberWithInt:k]];
});
// other stuff NOTE: if its operation on array do it in queue block only
}
}
}
//2.
- (void)realtimeUpdate:(NSTimer*)theTimer
{
// Create a copy of the array
__block NSMutableArray *k_ArrayCopy;//when you use any variable inside block add __block before it
dispatch_async(q, ^{
//queue block
k_ArrayCopy = [NSMutableArray arrayWithArray:k_Array];
});
//do some stuff with k_ArrayCopy
}
Now your add and read array operation are on same queue and it will not conflict..
For more details in using dispatch queue go through apples Grand Central Dispatch doc
Other way of doing this is use NSConditonLock

Resources