diff --git a/src/main/java/com/aparapi/Execution.java b/src/main/java/com/aparapi/Execution.java new file mode 100644 index 00000000..0966cab0 --- /dev/null +++ b/src/main/java/com/aparapi/Execution.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2016 - 2018 Syncleus, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.aparapi; + +/** + * Handle returned by asynchronous kernel execution. + */ +public final class Execution { + private final Kernel kernel; + private final Thread thread; + private volatile Throwable failure; + private volatile boolean finished; + + Execution(final Kernel _kernel, final Runnable _execution) { + if (_kernel == null) { + throw new NullPointerException("_kernel"); + } + if (_execution == null) { + throw new NullPointerException("_execution"); + } + kernel = _kernel; + thread = new Thread(new Runnable() { + @Override public void run() { + try { + _execution.run(); + } catch (Throwable t) { + failure = t; + } finally { + finished = true; + } + } + }, "Aparapi Kernel Async Execution"); + thread.start(); + } + + /** + * Wait until the asynchronous execution has finished. + * + * @return the executed kernel + */ + public Kernel waitUntilFinished() { + boolean interrupted = false; + while (true) { + try { + thread.join(); + break; + } catch (InterruptedException e) { + interrupted = true; + } + } + if (interrupted) { + Thread.currentThread().interrupt(); + } + if (failure instanceof RuntimeException) { + throw (RuntimeException) failure; + } + if (failure instanceof Error) { + throw (Error) failure; + } + if (failure != null) { + throw new RuntimeException(failure); + } + return kernel; + } + + public boolean isFinished() { + return finished; + } + + public Kernel getKernel() { + return kernel; + } +} diff --git a/src/main/java/com/aparapi/Kernel.java b/src/main/java/com/aparapi/Kernel.java index 4b9686db..03700190 100644 --- a/src/main/java/com/aparapi/Kernel.java +++ b/src/main/java/com/aparapi/Kernel.java @@ -2907,6 +2907,75 @@ public synchronized Kernel execute(String _entrypoint, Range _range, int _passes return prepareKernelRunner().execute(_entrypoint, _range, _passes); } + /** + * Start execution of _range kernels asynchronously. + * + * @param _range The range of Kernels that we would like to initiate. + * @return an execution handle which can be used to wait for completion + */ + public Execution executeAsync(final Range _range) { + return executeAsync(_range, 1); + } + + /** + * Start execution of _range kernels asynchronously. + * + * @param _range The number of Kernels that we would like to initiate. + * @return an execution handle which can be used to wait for completion + */ + public Execution executeAsync(final int _range) { + return executeAsync(createRange(_range), 1); + } + + /** + * Start execution of _passes iterations of _range kernels asynchronously. + * + * @param _range The range of Kernels that we would like to initiate. + * @param _passes The number of passes to make. + * @return an execution handle which can be used to wait for completion + */ + public Execution executeAsync(final Range _range, final int _passes) { + return executeAsync("run", _range, _passes); + } + + /** + * Start execution of _passes iterations over the _range of kernels asynchronously. + * + * @param _range The number of Kernels that we would like to initiate. + * @param _passes The number of passes to make. + * @return an execution handle which can be used to wait for completion + */ + public Execution executeAsync(final int _range, final int _passes) { + return executeAsync(createRange(_range), _passes); + } + + /** + * Start execution of _range kernels for the given entrypoint asynchronously. + * + * @param _entrypoint is the name of the method we wish to use as the entrypoint to the kernel + * @param _range The range of Kernels that we would like to initiate. + * @return an execution handle which can be used to wait for completion + */ + public Execution executeAsync(final String _entrypoint, final Range _range) { + return executeAsync(_entrypoint, _range, 1); + } + + /** + * Start execution of _passes iterations for the given entrypoint asynchronously. + * + * @param _entrypoint is the name of the method we wish to use as the entrypoint to the kernel + * @param _range The range of Kernels that we would like to initiate. + * @param _passes The number of passes to make. + * @return an execution handle which can be used to wait for completion + */ + public Execution executeAsync(final String _entrypoint, final Range _range, final int _passes) { + return new Execution(this, new Runnable() { + @Override public void run() { + execute(_entrypoint, _range, _passes); + } + }); + } + /** * Force pre-compilation of the kernel for a given device, without executing it. * diff --git a/src/test/java/com/aparapi/runtime/KernelAsyncExecutionTest.java b/src/test/java/com/aparapi/runtime/KernelAsyncExecutionTest.java new file mode 100644 index 00000000..772b3003 --- /dev/null +++ b/src/test/java/com/aparapi/runtime/KernelAsyncExecutionTest.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2016 - 2018 Syncleus, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.aparapi.runtime; + +import com.aparapi.Execution; +import com.aparapi.Kernel; +import com.aparapi.Range; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class KernelAsyncExecutionTest { + + @Test(timeout = 5000) + public void executeAsyncRangeRunsInBackgroundAndCanBeWaitedOn() throws Exception { + final int[] output = new int[1]; + final CountDownLatch enteredKernel = new CountDownLatch(1); + final CountDownLatch releaseKernel = new CountDownLatch(1); + Kernel kernel = new Kernel() { + @Override + public void run() { + enteredKernel.countDown(); + try { + releaseKernel.await(2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + output[getGlobalId()] = getGlobalId() + 1; + } + }; + kernel.setExecutionMode(Kernel.EXECUTION_MODE.JTP); + + Execution execution = kernel.executeAsync(Range.create(1)); + + Assert.assertTrue(enteredKernel.await(2, TimeUnit.SECONDS)); + Assert.assertFalse(execution.isFinished()); + releaseKernel.countDown(); + Assert.assertSame(kernel, execution.waitUntilFinished()); + Assert.assertTrue(execution.isFinished()); + Assert.assertEquals(1, output[0]); + } +}