Skip to content

Commit

Permalink
perf: Use field aliasing to improve Color.GetHashCode and Color.Equals
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Feb 4, 2022
1 parent 81e9ce8 commit 429d367
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Suite\SpanBench\SimpleSpanBenchmark.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Controls\BenchmarkDotNetControl.xaml.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Controls\BenchmarkDotNetTestsPage.xaml.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Suite\Windows_UI\ColorBench\XamlControlsResourcesCreationBenchmark.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Suite\Windows_UI_Xaml\ResourceDictionaryBench\XamlControlsResourcesCreationBenchmark.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Suite\Windows_UI_Xaml\ResourceDictionaryBench\XamlControlsResourcesReadBenchmark.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Suite\Windows_UI_Xaml\ResourceDictionaryBench\XamlControlsResourcesCreationRetrievalBenchmark.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Text;
using BenchmarkDotNet.Attributes;
using Microsoft.UI.Xaml.Controls;
using Windows.UI;

namespace SamplesApp.Benchmarks.Suite.Windows_UI.ColorBench
{
public class ColorBenchmark
{
Color _color1 = Color.FromArgb(0xFF, 0xFF, 0x00, 0x00), _color2 = Color.FromArgb(0xFF, 0x00, 0xFF, 0x00);

[Benchmark]
public void Create_Color()
{
var c = new Color();
}

[Benchmark]
public void Create_Color_WithParams()
{
var c = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF);
}

[Benchmark]
public void Create_Color_GetHashCode()
{
var c = Color.FromArgb(0xFF, 0xFF, 0xFF, 0xFF).GetHashCode();
}

[Benchmark]
public void Create_Color_op_Equals()
{
var b = _color1 == _color2;
}

[Benchmark]
public void Create_Color_Equals()
{
var b = _color1.Equals(_color2);
}
}
}
67 changes: 67 additions & 0 deletions src/Uno.UI.RuntimeTests/Tests/Windows_UI/Given_Color.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Windows.Foundation;
using Windows.System;
using Windows.UI;
using Windows.UI.Core;

namespace Uno.UI.RuntimeTests.Tests.Windows_UI_Core
{
[TestClass]
public class Given_Color
{
#if HAS_UNO
[TestMethod]
[DynamicData(nameof(GetData), DynamicDataSourceType.Method)]
public async Task When_FromArgb(byte a, byte r, byte g, byte b, uint result)
=> Assert.AreEqual(result, Color.FromArgb(a, r, g, b).AsUInt32());

[TestMethod]
[DynamicData(nameof(GetData), DynamicDataSourceType.Method)]
public async Task When_GetHashCode(byte a, byte r, byte g, byte b, uint result)
=> Assert.AreEqual(result, (uint)Color.FromArgb(a, r, g, b).GetHashCode());

public static IEnumerable<object[]> GetData()
{
yield return new object[] { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (uint)0x00000000 };
yield return new object[] { (byte)0xFF, (byte)0x00, (byte)0x00, (byte)0x00, (uint)0xFF000000 };
yield return new object[] { (byte)0x00, (byte)0xFF, (byte)0x00, (byte)0x00, (uint)0x00FF0000 };
yield return new object[] { (byte)0xFF, (byte)0x00, (byte)0xFF, (byte)0x00, (uint)0xFF00FF00 };
yield return new object[] { (byte)0xFF, (byte)0x00, (byte)0x00, (byte)0xFF, (uint)0xFF0000FF };
yield return new object[] { (byte)0x7F, (byte)0x00, (byte)0x00, (byte)0x00, (uint)0x7F000000 };
yield return new object[] { (byte)0xFF, (byte)0x7F, (byte)0x00, (byte)0x00, (uint)0xFF7F0000 };
yield return new object[] { (byte)0xFF, (byte)0x00, (byte)0x7F, (byte)0x00, (uint)0xFF007F00 };
yield return new object[] { (byte)0xFF, (byte)0x00, (byte)0x00, (byte)0x7F, (uint)0xFF00007F };
yield return new object[] { (byte)0xFF, (byte)0xFF, (byte)0xFF, (byte)0xFF, (uint)0xFFFFFFFF };
}
#endif

[TestMethod]
[DynamicData(nameof(GetCompare), DynamicDataSourceType.Method)]
public async Task When_Equals(Color left, Color right, bool result)
=> Assert.AreEqual(result, left.Equals(right));

[TestMethod]
[DynamicData(nameof(GetCompare), DynamicDataSourceType.Method)]
public async Task When_op_Equals(Color left, Color right, bool result)
=> Assert.AreEqual(result, left == right);

public static IEnumerable<object[]> GetCompare()
{
yield return new object[] { ColorHelper.FromArgb(0xFF, 0xFF, 0xFF, 0xFF), ColorHelper.FromArgb(0xFF, 0xFF, 0xFF, 0xFF), true };
yield return new object[] { ColorHelper.FromArgb(0xFF, 0x7F, 0xFF, 0xFF), ColorHelper.FromArgb(0xFF, 0xFF, 0xFF, 0xFF), false };
yield return new object[] { ColorHelper.FromArgb(0xFF, 0xFF, 0x7F, 0xFF), ColorHelper.FromArgb(0xFF, 0xFF, 0xFF, 0xFF), false };
yield return new object[] { ColorHelper.FromArgb(0xFF, 0xFF, 0xFF, 0x7F), ColorHelper.FromArgb(0xFF, 0xFF, 0xFF, 0xFF), false };
yield return new object[] { ColorHelper.FromArgb(0x00, 0x00, 0x00, 0x00), ColorHelper.FromArgb(0xFF, 0xFF, 0xFF, 0xFF), false };
yield return new object[] { ColorHelper.FromArgb(0x00, 0x00, 0x00, 0x00), ColorHelper.FromArgb(0x00, 0x00, 0x00, 0x00), true };
yield return new object[] { ColorHelper.FromArgb(0x7F, 0xFF, 0xFF, 0xFF), ColorHelper.FromArgb(0x7F, 0xFF, 0xFF, 0xFF), true };
yield return new object[] { ColorHelper.FromArgb(0xFF, 0x7F, 0xFF, 0xFF), ColorHelper.FromArgb(0xFF, 0x7F, 0xFF, 0xFF), true };
yield return new object[] { ColorHelper.FromArgb(0xFF, 0xFF, 0x7F, 0xFF), ColorHelper.FromArgb(0xFF, 0xFF, 0x7F, 0xFF), true };
yield return new object[] { ColorHelper.FromArgb(0xFF, 0xFF, 0xFF, 0x7F), ColorHelper.FromArgb(0xFF, 0xFF, 0xFF, 0x7F), true };
}
}
}
48 changes: 34 additions & 14 deletions src/Uno.UWP/UI/Color.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,57 @@
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace Windows.UI
{
[StructLayout(LayoutKind.Explicit)]
public partial struct Color : IFormattable
{
public byte A { get; set; }
/// <summary>
/// Alias individual fields to avoid bitshifting and GetHashCode / compare costs
/// </summary>
[FieldOffset(0)]
private uint _color;

public byte B { get; set; }
//
// This memory layout assumes that the system uses little-endianness.
//
[FieldOffset(3)]
private byte _a;
[FieldOffset(2)]
private byte _r;
[FieldOffset(1)]
private byte _g;
[FieldOffset(0)]
private byte _b;

public byte G { get; set; }
public byte A { get => _a; set => _a = value; }

public byte R { get; set; }
public byte B { get => _b; set => _b = value; }

public byte G { get => _g; set => _g = value; }

public byte R { get => _r; set => _r = value; }

internal bool IsTransparent => A == 0;

public static Color FromArgb(byte a, byte r, byte g, byte b) => new Color(a, r, g, b);

private Color(byte a, byte r, byte g, byte b)
{
A = a;
R = r;
G = g;
B = b;
_color = 0; // Required for field initialization rules in C#
_b = b;
_g = g;
_r = r;
_a = a;
}

public override bool Equals(object o) => o is Color color && Equals(color);

public bool Equals(Color color) =>
color.A == A
&& color.R == R
&& color.G == G
&& color.B == B;
public bool Equals(Color color) =>
color._color == _color;

public override int GetHashCode() => (A << 8) ^ (R << 6) ^ (G << 4) ^ B;
public override int GetHashCode() => (int)_color;

public override string ToString() => ToString(null, null);

Expand All @@ -43,6 +61,8 @@ private Color(byte a, byte r, byte g, byte b)

internal Color WithOpacity(double opacity) => new Color((byte)(A * opacity), R, G, B);

internal uint AsUInt32() => _color;

string IFormattable.ToString(string format, IFormatProvider formatProvider) => ToString(format, formatProvider);

private string ToString(string format, IFormatProvider formatProvider) => string.Format(formatProvider, "#{0:X2}{1:X2}{2:X2}{3:X2}", A, R, G, B);
Expand Down

0 comments on commit 429d367

Please sign in to comment.