On 30/12/2014 09:55, Gruenke,Matt wrote:
-----Original Message----- From: Boost [mailto:boost-bounces@lists.boost.org] On Behalf Of Thomas M Sent: Tuesday, December 30, 2014 2:49 To: boost@lists.boost.org Subject: Re: [boost] Synchronization (RE: [compute] review)
BTW, for command_queue-level operations, Kyle seems to agree with my proposal to have a sort of guard object that calls event::wait(), upon destruction. I actually wonder if it makes sense to call this a 'guarantee', since it doesn't quite behave like traditional guard objects. He's even accepted my offer to code this up.
If you want to code that functionality up that's more than fine with me. I am not happy though with the need to explicitly, i.e. manually, associate guards with command-queue operations just for the sake of exception-safety (I am referring only to that here). And out of the box I cannot come up with a single example when a memory container is fine to die while OpenCL operations are still running on it or enqueued to run on it, so that's why I prefer a mechanisms which does it always, implicitly (unless maybe the user wants to take over it explicitly).
Your proposal looks like:
boost::compute::guard wg(cq.enqueue_write_buffer_async(dv, 0, hv.size(), &hv.front()));
which appears to me quite lengthy and more error-prone (forgetting the guard) to get exception safety.
Yes, except I'm now proposing to call it a 'guarantee', since it guarantees that the event will have completed within its lifetime.
Now assume that an iterator can store its underlying container, isn't it possible that the enqueue_write_buffer_async itself places some guard-like structure into the container, and when the container's destructor is invoked it calls wait() on all of them?
Keep in mind that the guarantee is proposed to be used with command_queue operations, which accept only pointers and buffer objects - not iterators. I think it's not unreasonable if the low level interface requires a bit more care to use, if that makes it more flexible and efficient.
Ok I missed the non-iterator cases and see your point. If you are going to implement such RAII guards here's a short wish-list of features / guard classes: a) make guards "transferable" across functions b) a container of guards and/or a guard for a wait_list as whole c) a guard for a command-queue as whole [possibly guards for other classes as well] a) + b) because something like this is really useful: void foo() { // setup all memory objects etc. some_guard guard; make_async_copy(..., guard); some_guard_container cont; // or cont is a wait_list on which a guard can be placed more_async_copies(..., cont); yet_more_async_copies(..., cont); even_more_async_copies(..., cont); cont.wait(); some_guard guard2; make_async_copy(..., guard2); } The functions ...async_copy thus can launch asynchronous operations but themselves do not wait on return; ownership of initially obtained guards gets passed on to foo. And a single guard can refer to multiple events/asynchronous operations as a whole group. With c) I have something like this in mind: { some_cmd_queue_guard qu_guard(queue); cq.enqueue_write_buffer_async(...); transform(..., queue); // implicitly async cq.enqueue_read_buffer_async(...); // here automatic synchronization occurs } where the destructor of some_cmd_queue_guard calls queue.finish(), or similar. So there is no need to place a separate guard on every single operation, for regularly executing code at the end of the block automatic synchronization with the host occurs, and in the case of an exception automatic protection is given. Thomas