-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Conversation
…s instead of 4 * Replaces 2 calls each to Math.Min and Math.Max (a total of 4 required comparisons) with a single call to MinMaxRgb (at least 2 required comparisons and at worst 3).
Tagging subscribers to this area: @safern, @tarekgh Issue Details…s instead of 4
|
dotnet/performance microbenchmarks filtered with System.Drawing.Tests.Perf_Color*: Base = .NET6 Before this changeset No Slower results for the provided threshold = 2% and noise filter = 25ns.
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
private void MinMaxRgb(out int min, out int max, int r, int g, int b) | ||
{ | ||
if (b < g) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move trailing braces to their own lines for consistency with existing .NET code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, updated.
Do we have sufficient functional test coverage? |
Thanks @danmoseley . There were a couple of test cases missing which I've just added: All Possible Cases:
Existing Test Coverage:
For:
|
Also I wasn't sure if I should put this new method in Math.cs and make it a general use case to find the min and max of three values so I've left it inside Color.cs for now. |
Drawing wouldn't be able to use anything from Math unless it was public: the libraries can only use each other's public surface. And to make new public API involves a review process, which would include consideration of how broadly useful the API would be. So I think it is in the right place. |
if (r < g) | ||
max = g; | ||
else | ||
max = r; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could simplify this to use ?
assignment?
if (r < g) | |
max = g; | |
else | |
max = r; | |
max = r < g ? g : r; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, updated.
if (g < r) | ||
min = g; | ||
else | ||
min = r; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto:
if (g < r) | |
min = g; | |
else | |
min = r; | |
min = g < r ? g : r; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, updated.
int min, max; | ||
MinMaxRgb(out min, out max, r, g, b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int min, max; | |
MinMaxRgb(out min, out max, r, g, b); | |
MinMaxRgb(out int min, out int max, r, g, b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, updated.
int min, max; | ||
MinMaxRgb(out min, out max, r, g, b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int min, max; | |
MinMaxRgb(out min, out max, r, g, b); | |
MinMaxRgb(out int min, out int max, r, g, b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, updated.
int min, max; | ||
MinMaxRgb(out min, out max, r, g, b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
int min, max; | |
MinMaxRgb(out min, out max, r, g, b); | |
MinMaxRgb(out int min, out int max, r, g, b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, updated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than small style improvements, looks good. Thanks!
Thanks @safern , I've updated it with the requested changes. |
Hello @safern! Because this pull request has the p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (
|
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; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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:
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; | |
} | |
} |
There was a problem hiding this comment.
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: 3No 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: 3No 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: 3No 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: 2No 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: 3No 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: 3Slower | 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:
- 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).
- 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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;
}
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if these local variables will be eliminated due to inlining, so performance will be about the same?
* Credit for this simplification: stephentoub
Thanks for the contribution @L2 - perhaps you'd like to pick up something else? |
…shim_mono # By Aaron Robinson (10) and others # Via GitHub * upstream/main: (108 commits) [mbr] Add Apple sample (dotnet#50740) make EstablishProxyTunnelAsync throw on failure status code from proxy (dotnet#50763) Improve RGB Min Max evaluation performance by using 2 or 3 comparison… (dotnet#50622) [mono] More domain cleanups (dotnet#50479) Fix Crossgen2 of PlatformDefaultMemberFunction methods and calls. (dotnet#50754) Disable EventSource generator in design-time builds (dotnet#50741) Fix X509 test failures on Android (dotnet#50301) Do not confuse fgDispBasicBlocks in fgMorphBlocks (dotnet#50703) Enforce 64KB event payload size limit on EventPipe (dotnet#50600) Reorganize CoreCLR native build to reduce CMake reconfigures when the build system is untouched (dotnet#49906) [mbr] Turn on hot reload for iOS, tvOS and MacCatalyst (dotnet#50458) improve connection scavenge logic by doing zero-byte read (dotnet#50545) Resolve call mdtokens when making tier 1 inline observations (dotnet#50675) Annotate APIs in System.Private.Xml (dotnet#49682) Support compiling against OpenSSL 3 headers Change Configuration.Json to use a regular Dictionary. (dotnet#50611) Remove unused BigNumFromBinary P/Invoke (dotnet#50670) Make Ninja the default CMake generator on Windows for the repo (dotnet#49715) [AppleAppBuilder] Entitlements to run tests on catalyst using the JIT (dotnet#50637) [mono] Fix delegate invokes to dynamic methods in mixed mode. (dotnet#50547) ... # Conflicts: # src/mono/dlls/mscordbi/CMakeLists.txt
Fixes: #46153
…s instead of 4
comparisons) with a single call to MinMaxRgb (at least 2 required
comparisons and at worst 3).