Skip to content

Replace boxing Hashtable in Grid's Measure, improve performance #10684

New issue

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

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

Already on GitHub? # to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all 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
@@ -624,6 +624,7 @@
<Compile Include="System\Windows\Controls\FlowDocumentScrollViewer.cs" />
<Compile Include="System\Windows\Controls\Frame.cs" />
<Compile Include="System\Windows\Controls\Grid.cs" />
<Compile Include="System\Windows\Controls\GridSpanKey.cs" />
<Compile Include="System\Windows\Controls\GridSplitter.cs" />
<Compile Include="System\Windows\Controls\GridView.cs" />
<Compile Include="System\Windows\Controls\GridViewColumn.cs" />
Original file line number Diff line number Diff line change
@@ -21,13 +21,14 @@
using System.Threading;
using System.Windows.Media;
using System.Windows.Markup;
using System.Runtime.InteropServices;

namespace System.Windows.Controls
{
/// <summary>
/// Grid
/// </summary>
public class Grid : Panel, IAddChild
public partial class Grid : Panel, IAddChild
{
//------------------------------------------------------
//
@@ -1194,7 +1195,7 @@ private void MeasureCellsGroup(
}

UIElementCollection children = InternalChildren;
Hashtable spanStore = null;
Dictionary<GridSpanKey, double> spanStore = null;
bool ignoreDesiredSizeV = forceInfinityV;

int i = cellsHead;
@@ -1214,12 +1215,7 @@ private void MeasureCellsGroup(
}
else
{
RegisterSpan(
ref spanStore,
PrivateCells[i].ColumnIndex,
PrivateCells[i].ColumnSpan,
true,
children[i].DesiredSize.Width);
RegisterSpan(ref spanStore, PrivateCells[i].ColumnIndex, PrivateCells[i].ColumnSpan, true, children[i].DesiredSize.Width);
}
}

@@ -1231,62 +1227,49 @@ private void MeasureCellsGroup(
}
else
{
RegisterSpan(
ref spanStore,
PrivateCells[i].RowIndex,
PrivateCells[i].RowSpan,
false,
children[i].DesiredSize.Height);
RegisterSpan(ref spanStore, PrivateCells[i].RowIndex, PrivateCells[i].RowSpan, false, children[i].DesiredSize.Height);
}
}

i = PrivateCells[i].Next;
} while (i < PrivateCells.Length);

if (spanStore != null)
if (spanStore is not null)
{
foreach (DictionaryEntry e in spanStore)
foreach (KeyValuePair<GridSpanKey, double> item in spanStore)
{
SpanKey key = (SpanKey)e.Key;
double requestedSize = (double)e.Value;

EnsureMinSizeInDefinitionRange(
key.U ? DefinitionsU : DefinitionsV,
key.Start,
key.Count,
requestedSize,
key.U ? referenceSize.Width : referenceSize.Height);
definitions: item.Key.U ? DefinitionsU : DefinitionsV,
start: item.Key.Start,
count: item.Key.Count,
requestedSize: item.Value,
percentReferenceSize: item.Key.U ? referenceSize.Width : referenceSize.Height);
}
}
}

/// <summary>
/// Helper method to register a span information for delayed processing.
/// </summary>
/// <param name="store">Reference to a hashtable object used as storage.</param>
/// <param name="store">Reference to a <see cref="Dictionary{TKey, TValue}"/> object used as storage.</param>
/// <param name="start">Span starting index.</param>
/// <param name="count">Span count.</param>
/// <param name="u"><c>true</c> if this is a column span. <c>false</c> if this is a row span.</param>
/// <param name="isColumnSpan"><c>true</c> if this is a column span. <c>false</c> if this is a row span.</param>
/// <param name="value">Value to store. If an entry already exists the biggest value is stored.</param>
private static void RegisterSpan(
ref Hashtable store,
int start,
int count,
bool u,
double value)
private static void RegisterSpan(ref Dictionary<GridSpanKey, double> store, int start, int count, bool isColumnSpan, double value)
{
if (store == null)
GridSpanKey key = new(start, count, isColumnSpan);

if (store is null)
{
store = new Hashtable();
store = new Dictionary<GridSpanKey, double> { { key, value } };
}

SpanKey key = new SpanKey(start, count, u);
object o = store[key];

if ( o == null
|| value > (double)o )
else
{
store[key] = value;
ref double savedValue = ref CollectionsMarshal.GetValueRefOrAddDefault(store, key, out bool exists);

if (!exists || value > savedValue)
savedValue = value;
}
}

@@ -3524,70 +3507,6 @@ private struct CellCache
internal bool IsAutoV { get { return ((SizeTypeV & LayoutTimeSizeType.Auto) != 0); } }
}

/// <summary>
/// Helper class for representing a key for a span in hashtable.
/// </summary>
private class SpanKey
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="start">Starting index of the span.</param>
/// <param name="count">Span count.</param>
/// <param name="u"><c>true</c> for columns; <c>false</c> for rows.</param>
internal SpanKey(int start, int count, bool u)
{
_start = start;
_count = count;
_u = u;
}

/// <summary>
/// <see cref="object.GetHashCode"/>
/// </summary>
public override int GetHashCode()
{
int hash = (_start ^ (_count << 2));

if (_u) hash &= 0x7ffffff;
else hash |= 0x8000000;

return (hash);
}

/// <summary>
/// <see cref="object.Equals(object)"/>
/// </summary>
public override bool Equals(object obj)
{
SpanKey sk = obj as SpanKey;
return ( sk != null
&& sk._start == _start
&& sk._count == _count
&& sk._u == _u );
}

/// <summary>
/// Returns start index of the span.
/// </summary>
internal int Start { get { return (_start); } }

/// <summary>
/// Returns span count.
/// </summary>
internal int Count { get { return (_count); } }

/// <summary>
/// Returns <c>true</c> if this is a column span.
/// <c>false</c> if this is a row span.
/// </summary>
internal bool U { get { return (_u); } }

private int _start;
private int _count;
private bool _u;
}

private static int SpanPreferredDistributionOrderComparer(DefinitionBase x, DefinitionBase y)
{
int result;
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Windows.Controls;

public partial class Grid
{
/// <summary>
/// Helper struct for representing a key for a span in <see cref="Dictionary{TKey, TValue}"/>.
/// </summary>
private readonly struct GridSpanKey : IEquatable<GridSpanKey>
{
/// <summary>
/// Returns start index of the span.
/// </summary>
internal int Start { get; }

/// <summary>
/// Returns span count.
/// </summary>
internal int Count { get; }

/// <summary>
/// Returns <see langword="true"/> if this is a column span.
/// <see langword="false"/> if this is a row span.
/// </summary>
internal bool U { get; }

/// <summary>
/// Constructor.
/// </summary>
/// <param name="start">Starting index of the span.</param>
/// <param name="count">Span count.</param>
/// <param name="u"><see langword="true"/> for columns; <see langword="false"/> for rows.</param>
internal GridSpanKey(int start, int count, bool u)
{
Start = start;
Count = count;
U = u;
}

/// <summary>
/// <see cref="object.GetHashCode"/>
/// </summary>
public override int GetHashCode()
{
int hash = Start ^ (Count << 2);

return U ? hash &= 0x7ffffff : hash |= 0x8000000;
}

/// <summary>
/// <see cref="object.Equals(object)"/>
/// </summary>
public override bool Equals(object obj)
{
return obj is GridSpanKey other && Equals(other);
}

/// <summary>
/// The <see cref="IEquatable{SpanKey}"/> implementation.
/// </summary>
public bool Equals(GridSpanKey other)
{
return other.Start == Start && other.Count == Count && other.U == U;
}
}
}