diff --git a/src/main/java/net/imglib2/loops/FastCursorRandomAccessLoops.java b/src/main/java/net/imglib2/loops/FastCursorRandomAccessLoops.java new file mode 100644 index 000000000..446f0eaef --- /dev/null +++ b/src/main/java/net/imglib2/loops/FastCursorRandomAccessLoops.java @@ -0,0 +1,181 @@ +/* + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2021 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package net.imglib2.loops; + +import net.imglib2.Cursor; +import net.imglib2.RandomAccess; +import net.imglib2.loops.ClassCopyProvider; +import net.imglib2.loops.LoopBuilder; + +import java.util.Arrays; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.LongConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * This class aims to avoid performance problems of the Java just in time compilation when + * running a loop that executes an action on a {@link Cursor} and multiple {@link RandomAccess}es. + * Such a loop might look like this: + * + *
+ * {@code
+ *
+ * while(--n >= 0) {
+ *   A a = cursorA.next();
+ *   randomAccessB.setPosition( cursorA );
+ *   randomAccessC.setPosition( cursorA );
+ *   action.accept( a, randomAccessB.get(), randomAccessC.get() );
+ * }
+ * }
+ * 
+ * + * Usually such a loop has significant performance problems when used together multiple different classes + * that implement {@link Cursor}, {@link RandomAccess}, and action interfaces. This is caused by the JIT-compiler + * who simple performs badly in these situations. + * + * This class solves these performance problems by holding multiple copies of the bytecode of these loops. + * A bytecode copy can be individually optimized by the Java JIT compiler to perform optimally for a + * specific use case. + */ +final class FastCursorRandomAccessLoops +{ + private FastCursorRandomAccessLoops() + { + // prevent from instantiation + } + + static void loop( final Object action, long n, final Cursor< ? > cursor, final List< ? extends RandomAccess< ? > > randomAccesses ) + { + createLoop( action, cursor, randomAccesses ).accept( n ); + } + + private static final List< ClassCopyProvider< LongConsumer > > factories = Arrays.asList( + new ClassCopyProvider<>( OneCursorLoop.class, LongConsumer.class ), + new ClassCopyProvider<>( TwoCursorLoop.class, LongConsumer.class ), + new ClassCopyProvider<>( ThreeCursorLoop.class, LongConsumer.class ) ); + + private static LongConsumer createLoop( final Object action, final Cursor< ? > cursor, final List< ? extends RandomAccess< ? > > randomAccesses ) + { + final Object[] arguments = Stream.concat( Stream.of( action, cursor ), randomAccesses.stream() ).toArray(); + ClassCopyProvider< LongConsumer > factory = factories.get( randomAccesses.size() ); + final List< Class< ? > > key = Stream.of( arguments ).map( Object::getClass ).collect( Collectors.toList() ); + return factory.newInstanceForKey( key, arguments ); + } + + public static class OneCursorLoop< A > implements LongConsumer + { + + private final Consumer< A > action; + + private final Cursor< A > cursorA; + + public OneCursorLoop( final Consumer< A > action, final Cursor< A > cursorA ) + { + this.action = action; + this.cursorA = cursorA; + } + + @Override + public void accept( long n ) + { + while ( --n >= 0 ) + action.accept( cursorA.next() ); + } + } + + public static class TwoCursorLoop< A, B > implements LongConsumer + { + + private final BiConsumer< A, B > action; + + private final Cursor< A > cursorA; + + private final RandomAccess< B > randomAccessB; + + public TwoCursorLoop( final BiConsumer< A, B > action, final Cursor< A > cursorA, final RandomAccess< B > randomAccessB ) + { + this.action = action; + this.cursorA = cursorA; + this.randomAccessB = randomAccessB; + } + + @Override + public void accept( long n ) + { + while ( --n >= 0 ) + { + A a = cursorA.next(); + randomAccessB.setPosition( cursorA ); + action.accept( a, randomAccessB.get() ); + } + } + } + + public static class ThreeCursorLoop< A, B, C > implements LongConsumer + { + + private final LoopBuilder.TriConsumer< A, B, C > action; + + private final Cursor< A > cursorA; + + private final RandomAccess< B > randomAccessB; + + private final RandomAccess< C > randomAccessC; + + public ThreeCursorLoop( final LoopBuilder.TriConsumer< A, B, C > action, final Cursor< A > cursorA, final RandomAccess< B > randomAccessB, final RandomAccess< C > randomAccessC ) + { + this.action = action; + this.cursorA = cursorA; + this.randomAccessB = randomAccessB; + this.randomAccessC = randomAccessC; + } + + @Override + public void accept( long n ) + { + while ( --n >= 0 ) + { + A a = cursorA.next(); + randomAccessB.setPosition( cursorA ); + randomAccessC.setPosition( cursorA ); + action.accept( a, randomAccessB.get(), randomAccessC.get() ); + } + } + } + +} diff --git a/src/main/java/net/imglib2/loops/IterableLoopBuilder.java b/src/main/java/net/imglib2/loops/IterableLoopBuilder.java new file mode 100644 index 000000000..2d52a7fe2 --- /dev/null +++ b/src/main/java/net/imglib2/loops/IterableLoopBuilder.java @@ -0,0 +1,186 @@ +/* + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2021 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ + +package net.imglib2.loops; + +import net.imglib2.Cursor; +import net.imglib2.FinalInterval; +import net.imglib2.Interval; +import net.imglib2.IterableInterval; +import net.imglib2.RandomAccess; +import net.imglib2.RandomAccessible; +import net.imglib2.loops.IntervalChunks; +import net.imglib2.loops.LoopBuilder; +import net.imglib2.parallel.Parallelization; +import net.imglib2.parallel.TaskExecutor; +import net.imglib2.parallel.TaskExecutors; + +import java.util.Arrays; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Similar to {@link LoopBuilder}, but expects the first image to be an {@link IterableInterval} + * and the other images to be {@link RandomAccessible}. + *

+ * Please note: It is usually preferable to use LoopBuilder as it often has a better performance, + * and using {@link net.imglib2.RandomAccessibleInterval RandomAccessibleInterval} for all images is simpler. + *

+ * Here is an usage example, that calculates the sum of two images: + * + *

+ * {@code
+ * IterableInterval sum = ...
+ * RandomAccessible imageA = ...
+ * RandomAccessible imageB = ...
+ *
+ * IterableLoopBuilder.setImages(sum, imageA, imageB).forEachPixel(
+ *     (s, a, b) -> {
+ *          s.setReal(a.getRealDouble() + b.getRealDouble());
+ *     }
+ * );
+ * }
+ * 
+ * + * @see LoopBuilder + */ +public class IterableLoopBuilder< T > +{ + + private TaskExecutor taskExecutor = TaskExecutors.singleThreaded(); + + private final IterableInterval< ? > firstImage; + + private final List< RandomAccessible< ? > > otherImages; + + private IterableLoopBuilder( IterableInterval< ? > firstImage, RandomAccessible< ? > ... otherImages ) + { + this.firstImage = firstImage; + this.otherImages = Arrays.asList( otherImages ); + } + + /** + * Set the image to loop over. + */ + public static < A > IterableLoopBuilder< Consumer< A > > setImages( IterableInterval< A > a ) + { + return new IterableLoopBuilder<>( a ); + } + + /** + * Sets the images to loop over. + */ + public static < A, B > IterableLoopBuilder< BiConsumer< A, B > > setImages( IterableInterval< A > a, RandomAccessible< B > b ) + { + return new IterableLoopBuilder<>( a, b ); + } + + /** + * Sets the images to loop over. + */ + public static < A, B, C > IterableLoopBuilder< LoopBuilder.TriConsumer< A, B, C > > setImages( IterableInterval< A > a, RandomAccessible< B > b, RandomAccessible< C > c ) + { + return new IterableLoopBuilder<>( a, b, c ); + } + + /** + * Executes the given action pixel wise for the given images. + */ + public void forEachPixel( T action ) + { + forEachChunk( chunk -> { + chunk.forEachPixel( action ); + return null; + } ); + } + + /** + * @see LoopBuilder#forEachChunk + */ + public < R > List< R > forEachChunk( Function< LoopBuilder.Chunk< T >, R > chunkAction ) + { + List< Interval > intervals = IntervalChunks.chunkInterval( new FinalInterval( firstImage.size() ), taskExecutor.suggestNumberOfTasks() ); + List< Chunk< T > > chunks = ListUtils.map( interval -> new Chunk< T >( firstImage, otherImages, interval ), intervals ); + return taskExecutor.forEachApply( chunks, chunkAction ); + } + + /** + * This will cause the loop to be executed in a multi threaded fashion. + * Details can be set using the {@link Parallelization} framework. + */ + public IterableLoopBuilder< T > multithreaded() + { + return multithreaded( Parallelization.getTaskExecutor() ); + } + + /** + * This will cause the loop to be executed in a multi threaded fashion. + * The give {@link TaskExecutor} will be used for multi threading. + */ + public IterableLoopBuilder< T > multithreaded( TaskExecutor taskExecutor ) + { + this.taskExecutor = taskExecutor; + return this; + } + + private static class Chunk< T > implements LoopBuilder.Chunk< T > { + + private final IterableInterval< ? > firstImage; + + private final List< RandomAccessible< ? > > otherImages; + + private final Interval interval; + + private Chunk( IterableInterval< ? > firstImage, List< RandomAccessible< ? > > otherImages, Interval interval ) + { + this.firstImage = firstImage; + this.otherImages = otherImages; + this.interval = interval; + } + + @Override + public void forEachPixel( T action ) + { + Cursor< ? > cursor = firstImage.localizingCursor(); + List< RandomAccess< ? > > randomAccesses = otherImages.stream().map( RandomAccessible::randomAccess ).collect( Collectors.toList()); + cursor.jumpFwd( interval.min( 0 ) ); + long size = interval.dimension( 0 ); + FastCursorRandomAccessLoops.loop( action, size, cursor, randomAccesses ); + } + } + +} diff --git a/src/test/java/net/imglib2/loops/IterableLoopBuilderTest.java b/src/test/java/net/imglib2/loops/IterableLoopBuilderTest.java new file mode 100644 index 000000000..178ef7b6b --- /dev/null +++ b/src/test/java/net/imglib2/loops/IterableLoopBuilderTest.java @@ -0,0 +1,95 @@ +/* + * #%L + * ImgLib2: a general-purpose, multidimensional image processing library. + * %% + * Copyright (C) 2009 - 2021 Tobias Pietzsch, Stephan Preibisch, Stephan Saalfeld, + * John Bogovic, Albert Cardona, Barry DeZonia, Christian Dietz, Jan Funke, + * Aivar Grislis, Jonathan Hale, Grant Harris, Stefan Helfrich, Mark Hiner, + * Martin Horn, Steffen Jaensch, Lee Kamentsky, Larry Lindsey, Melissa Linkert, + * Mark Longair, Brian Northan, Nick Perry, Curtis Rueden, Johannes Schindelin, + * Jean-Yves Tinevez and Michael Zinsmaier. + * %% + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * #L% + */ +package net.imglib2.loops; + +import net.imglib2.IterableInterval; +import net.imglib2.RandomAccessible; +import net.imglib2.RandomAccessibleInterval; +import net.imglib2.img.Img; +import net.imglib2.img.array.ArrayImgs; +import net.imglib2.parallel.Parallelization; +import net.imglib2.test.ImgLib2Assert; +import net.imglib2.type.numeric.integer.IntType; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Tests {@link IterableLoopBuilder}. + */ +public class IterableLoopBuilderTest { + + @Test + public void testSingleImage() { + // Test IterableLoopBuilder by setting all pixels in a single image to one. + Img image = ArrayImgs.ints(2,2); + IterableInterval iterableInterval = image; + IterableLoopBuilder.setImages(iterableInterval).forEachPixel(pixel -> pixel.setOne()); + Img expected = ArrayImgs.ints(new int[]{1, 1, 1, 1}, 2, 2); + ImgLib2Assert.assertImageEquals(expected, image); + } + + @Test + public void testTwoImages() { + // Test IterableLoopBuilder by using it to copy the content of a tiny image. + Img source = ArrayImgs.ints(new int[]{1, 2, 3, 4}, 2,2); + Img target = ArrayImgs.ints(2,2); + IterableInterval iterableIntervalSource = source; + RandomAccessible randomAccessibleTarget = target; + IterableLoopBuilder.setImages(iterableIntervalSource, randomAccessibleTarget).forEachPixel((s, t) -> t.set(s)); + ImgLib2Assert.assertImageEquals(source, target); + } + + @Test + public void testThreeImages() { + // Test IterableLoopBuilder by using it to calculate the difference between to tiny images. + Img target = ArrayImgs.ints(2, 2); + IterableInterval targetIterable = target; + RandomAccessible imageA = ArrayImgs.ints(new int[]{1, 1, 0, 0}, 2, 2); + RandomAccessible imageB = ArrayImgs.ints(new int[]{0, 2, 2, 0}, 2, 2); + IterableLoopBuilder.setImages(targetIterable, imageA, imageB).forEachPixel((t, a, b) -> t.setInteger(a.getInteger() - b.getInteger())); + RandomAccessibleInterval expected = ArrayImgs.ints(new int[]{1, -1, -2, 0}, 2, 2); + ImgLib2Assert.assertImageEquals(expected, target); + } + + @Test + public void testMultiThreading() { + // Test if IterableLoopBuilder still produces a correct result when multi-threading is enabled. + Img image = ArrayImgs.ints(10, 10); + Parallelization.runMultiThreaded(() -> { + IterableLoopBuilder.setImages(image).multithreaded().forEachPixel(pixel -> pixel.setOne()); + }); + image.forEach(pixel -> assertEquals(1, pixel.getInteger())); + } +}