From c7e6d19cc1c3369c48b1908170188fcfc8292e6e Mon Sep 17 00:00:00 2001 From: woutware <35376607+woutware@users.noreply.github.com> Date: Fri, 27 Apr 2018 18:54:53 +0200 Subject: [PATCH] Performance improvements. Ditched the use of modulo (%). Modulo is slow, and if you use it to just get the lower 8 bits (1 byte), you can just directly cast to byte. Also moved one if < out of the loop for speed. The less branching the better. Similarly replaced an if < 128 by cast to sbyte and taking its abs value. Performance gain in writing to PNG file seems to be roughly 20% (release mode, 1000x1000 bitmap). In ImageFramesCollectionTests I've set the culture to en-US to ensure getting english exception texts. --- .../Formats/Png/Filters/AverageFilter.cs | 22 ++++++++++++++++ .../Formats/Png/Filters/PaethFilter.cs | 26 +++++++++++++++++-- .../Formats/Png/Filters/SubFilter.cs | 20 ++++++++++++++ .../Formats/Png/Filters/UpFilter.cs | 11 ++++++++ .../Image/ImageFramesCollectionTests.cs | 3 +++ 5 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs index de62d4702..e832b2d7f 100644 --- a/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/AverageFilter.cs @@ -69,6 +69,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2) resultBaseRef = 3; +#if OLD_AND_SLOW for (int x = 0; x < scanline.Length; x++) { if (x - bytesPerPixel < 0) @@ -89,6 +90,27 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += res < 128 ? res : 256 - res; } } +#else + int x = 0; + for (; x < bytesPerPixel;) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - (above >> 1)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } + + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - Average(left, above)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } +#endif sum -= 3; } diff --git a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs index 7e05d736f..652269e27 100644 --- a/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/PaethFilter.cs @@ -44,7 +44,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters byte left = Unsafe.Add(ref scanBaseRef, x - bytesPerPixel); byte above = Unsafe.Add(ref prevBaseRef, x); byte upperLeft = Unsafe.Add(ref prevBaseRef, x - bytesPerPixel); - scan = (byte)(scan + PaethPredicator(left, above, upperLeft)); + scan = (byte)(scan + PaethPredictor(left, above, upperLeft)); } } @@ -70,6 +70,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x), Prior(x - bpp)) resultBaseRef = 4; +#if OLD_AND_SLOW for (int x = 0; x < scanline.Length; x++) { if (x - bytesPerPixel < 0) @@ -91,6 +92,27 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += res < 128 ? res : 256 - res; } } +#else + int x = 0; + for (; x < bytesPerPixel; ++x) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ref byte res = ref Unsafe.Add(ref resultBaseRef, x + 1); + res = (byte)(scan - PaethPredictor(0, above, 0)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } + + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte left = Unsafe.Add(ref scanBaseRef, xLeft); + byte above = Unsafe.Add(ref prevBaseRef, x); + byte upperLeft = Unsafe.Add(ref prevBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - PaethPredictor(left, above, upperLeft)); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } +#endif sum -= 4; } @@ -106,7 +128,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters /// The . /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte PaethPredicator(byte left, byte above, byte upperLeft) + private static byte PaethPredictor(byte left, byte above, byte upperLeft) { int p = left + above - upperLeft; int pa = ImageMaths.FastAbs(p - left); diff --git a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs index c0db7da93..f389cb2cd 100644 --- a/src/ImageSharp/Formats/Png/Filters/SubFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/SubFilter.cs @@ -60,6 +60,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Sub(x) = Raw(x) - Raw(x-bpp) resultBaseRef = 1; +#if OLD_AND_SLOW for (int x = 0; x < scanline.Length; x++) { if (x - bytesPerPixel < 0) @@ -78,6 +79,25 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters sum += res < 128 ? res : 256 - res; } } +#else + int x = 0; + for (; x < bytesPerPixel;) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = scan; + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } + + for (int xLeft = x - bytesPerPixel; x < scanline.Length; ++xLeft) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte prev = Unsafe.Add(ref scanBaseRef, xLeft); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - prev); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } +#endif sum -= 1; } diff --git a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs index 81c063ea9..45ece23ef 100644 --- a/src/ImageSharp/Formats/Png/Filters/UpFilter.cs +++ b/src/ImageSharp/Formats/Png/Filters/UpFilter.cs @@ -57,6 +57,7 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters // Up(x) = Raw(x) - Prior(x) resultBaseRef = 2; +#if OLD_AND_SLOW for (int x = 0; x < scanline.Length; x++) { byte scan = Unsafe.Add(ref scanBaseRef, x); @@ -65,6 +66,16 @@ namespace SixLabors.ImageSharp.Formats.Png.Filters res = (byte)((scan - above) % 256); sum += res < 128 ? res : 256 - res; } +#else + for (int x = 0; x < scanline.Length;) { + byte scan = Unsafe.Add(ref scanBaseRef, x); + byte above = Unsafe.Add(ref prevBaseRef, x); + ++x; + ref byte res = ref Unsafe.Add(ref resultBaseRef, x); + res = (byte)(scan - above); + sum += ImageMaths.FastAbs(unchecked((sbyte)res)); + } +#endif sum -= 2; } diff --git a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs index 4f00931de..4c760e681 100644 --- a/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs +++ b/tests/ImageSharp.Tests/Image/ImageFramesCollectionTests.cs @@ -15,6 +15,9 @@ namespace SixLabors.ImageSharp.Tests public ImageFramesCollectionTests() { + // Needed to get English exception messages, which are checked in several tests. + System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo("en-US"); + this.image = new Image(10, 10); this.collection = new ImageFrameCollection(this.image, 10, 10); }