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);
}