Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve RGB Min Max evaluation performance by using 2 or 3 comparison… #50622

Merged
merged 5 commits into from
Apr 6, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -487,12 +487,42 @@ private void GetRgbValues(out int r, out int g, out int b)
b = (int)(value & ARGBBlueMask) >> ARGBBlueShift;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MinMaxRgb(out int min, out int max, int r, int g, int b)
{
if (b < g)
{
if (b < r)
{
min = b;
max = r < g ? g : r;
}
else
{
min = r;
max = g;
}
}
else
{
if (r < b)
{
max = b;
min = g < r ? g : r;
}
else
{
max = r;
min = g;
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified to instead be:

Suggested change
private void MinMaxRgb(out int min, out int max, int r, int g, int b)
{
if (b < g)
{
if (b < r)
{
min = b;
max = r < g ? g : r;
}
else
{
min = r;
max = g;
}
}
else
{
if (r < b)
{
max = b;
min = g < r ? g : r;
}
else
{
max = r;
min = g;
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void MinMaxRgb(out int min, out int max, int r, int g, int b)
{
if (r > g)
{
max = r;
min = g;
}
else
{
max = g;
min = r;
}
if (b > max)
{
max = b;
}
else if (b < min)
{
min = b;
}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @stephentoub , I ran the perf_color microbenchmarks against 1) before this PR changeset, 2) the current PR's changeset, 3) proposed simplification

**.NET6 Before VS .NET6 with current PR changeset: Run1** summary: better: 3, geomean: 1.151 total diff: 3

No Slower results for the provided threshold = 2% and noise filter = 25ns.

Faster base/diff Base Median (ns) Diff Median (ns) Modality
System.Drawing.Tests.Perf_Color.GetBrightness 1.23 1549.96 1259.50
System.Drawing.Tests.Perf_Color.GetHue 1.18 1753.24 1484.57
System.Drawing.Tests.Perf_Color.GetSaturation 1.05 1451.59 1383.79
**.NET6 Before VS .NET6 with current PR changeset: Run2** summary: better: 3, geomean: 1.208 total diff: 3

No Slower results for the provided threshold = 2% and noise filter = 25ns.

Faster base/diff Base Median (ns) Diff Median (ns) Modality
System.Drawing.Tests.Perf_Color.GetBrightness 1.23 1543.19 1259.71
System.Drawing.Tests.Perf_Color.GetSaturation 1.22 1682.03 1384.17
System.Drawing.Tests.Perf_Color.GetHue 1.18 1758.68 1485.53
**.NET6 Before VS .NET6 with current PR changeset: Run3** summary: better: 3, geomean: 1.189 total diff: 3

No Slower results for the provided threshold = 2% and noise filter = 25ns.

Faster base/diff Base Median (ns) Diff Median (ns) Modality
System.Drawing.Tests.Perf_Color.GetSaturation 1.22 1682.42 1384.03
System.Drawing.Tests.Perf_Color.GetBrightness 1.19 1497.18 1259.28
System.Drawing.Tests.Perf_Color.GetHue 1.16 1725.25 1483.41


**.NET6 Before VS .NET6 with proposed simplification: Run1** summary: better: 2, geomean: 1.123 total diff: 2

No Slower results for the provided threshold = 2% and noise filter = 25ns.

Faster base/diff Base Median (ns) Diff Median (ns) Modality
System.Drawing.Tests.Perf_Color.GetBrightness 1.13 1549.96 1375.52
System.Drawing.Tests.Perf_Color.GetHue 1.12 1753.24 1565.75
**.NET6 Before VS .NET6 with proposed simplification: Run2** summary: better: 3, geomean: 1.135 total diff: 3

No Slower results for the provided threshold = 2% and noise filter = 25ns.

Faster base/diff Base Median (ns) Diff Median (ns) Modality
System.Drawing.Tests.Perf_Color.GetSaturation 1.16 1682.03 1448.45
System.Drawing.Tests.Perf_Color.GetHue 1.12 1758.68 1566.17
System.Drawing.Tests.Perf_Color.GetBrightness 1.12 1543.19 1375.17
**.NET6 Before VS .NET6 with proposed simplification: Run3** summary: better: 2, geomean: 1.132 worse: 1, geomean: 1.050 total diff: 3
Slower diff/base Base Median (ns) Diff Median (ns) Modality
System.Drawing.Tests.Perf_Color.GetBrightness 1.05 1497.18 1572.36
Faster base/diff Base Median (ns) Diff Median (ns) Modality
System.Drawing.Tests.Perf_Color.GetSaturation 1.16 1682.42 1446.59
System.Drawing.Tests.Perf_Color.GetHue 1.10 1725.25 1565.42
---------------------- ----------------------

Some of the benchmarks seem to get a bit slower on average after the simplification. I think what is happening may be two causes:

  1. There are paths with an extra store (we store max & min in the first conditional and then if the second conditional is taken one of these is overwritten).
  2. Just my guess, but processor pipelining with branch prediction may find it easier to go through each of the conditionals in the current implementation as there are no dependencies. The simplification introduces a dependency on the second conditional as it needs the values from the first? Just a guess here.

I will follow yours and @safern 's recommendation on which to keep. Thanks again.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't look at the disassembly but there might be an aliasing complication also, if the JIT compiler has to account for the possibility that min and max refer to the same location. Although perhaps it can disprove that after inlining.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually see them get faster on my machine. Regardless, though, I'd rather keep the code smaller, especially with it triplicated due to the aggressive inlining.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more possible improvement - using local valiables instead of out parameters:

		private static void MinMaxRgb(out int min, out int max, int r, int g, int b)
		{
			int minLoc, maxLoc;
			if (r > g)
			{
				maxLoc = r;
				minLoc = g;
			}
			else
			{
				maxLoc = g;
				minLoc = r;
			}

			if (b > maxLoc)
			{
				maxLoc = b;
			}
			else if (b < minLoc)
			{
				minLoc = b;
			}

			max = maxLoc;
			min = minLoc;
		}

Copy link
Contributor Author

@L2 L2 Apr 5, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually see them get faster on my machine. Regardless, though, I'd rather keep the code smaller, especially with it triplicated due to the aggressive inlining.

Sounds good @stephentoub , I've updated it with the requested changes. (I'm not sure how crediting works in this project as this is my second pull request, so I've added your username to the commit details as the originator of the simplification).

One more possible improvement - using local valiables instead of out parameters:

Thanks @hypeartist, I'm wondering if these local variables will be eliminated due to inlining, so performance will be about the same?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how crediting works in this project as this is my second pull request, so I've added your username to the commit details as the originator of the simplification

Thanks, but not necessary... I comment on a lot of PRs :)


public float GetBrightness()
{
GetRgbValues(out int r, out int g, out int b);

int min = Math.Min(Math.Min(r, g), b);
int max = Math.Max(Math.Max(r, g), b);
MinMaxRgb(out int min, out int max, r, g, b);

return (max + min) / (byte.MaxValue * 2f);
}
Expand All @@ -504,8 +534,7 @@ public float GetHue()
if (r == g && g == b)
return 0f;

int min = Math.Min(Math.Min(r, g), b);
int max = Math.Max(Math.Max(r, g), b);
MinMaxRgb(out int min, out int max, r, g, b);

float delta = max - min;
float hue;
Expand All @@ -531,8 +560,7 @@ public float GetSaturation()
if (r == g && g == b)
return 0f;

int min = Math.Min(Math.Min(r, g), b);
int max = Math.Max(Math.Max(r, g), b);
MinMaxRgb(out int min, out int max, r, g, b);

int div = max + min;
if (div > byte.MaxValue)
Expand Down
6 changes: 6 additions & 0 deletions src/libraries/System.Drawing.Primitives/tests/ColorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,12 @@ private void CheckRed(Color color)
[InlineData(51, 255, 51, 0.6f)]
[InlineData(51, 51, 255, 0.6f)]
[InlineData(51, 51, 51, 0.2f)]
[InlineData(0, 51, 255, 0.5f)]
[InlineData(51, 255, 0, 0.5f)]
[InlineData(0, 255, 51, 0.5f)]
[InlineData(255, 0, 51, 0.5f)]
[InlineData(51, 0, 255, 0.5f)]
[InlineData(255, 51, 0, 0.5f)]
public void GetBrightness(int r, int g, int b, float expected)
{
Assert.Equal(expected, Color.FromArgb(r, g, b).GetBrightness());
Expand Down