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 WeakList performance during staggered removals #3893
Merged
Merged
Changes from 1 commit
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
748b348
Improve weaklist performance
smoogipoo de44497
Add test
smoogipoo de1df2a
Improve code quality, only iterate over valid items normally
smoogipoo 0fedeba
Make method private
smoogipoo 0ddd33e
Rename parameter
smoogipoo 1a6aaf1
Improve performance a bit
smoogipoo 7bb4ef0
Fix current item not being set correctly
smoogipoo 49f4e81
Remove unnecessary line
smoogipoo ad63553
Don't use weakreference as second-chance
smoogipoo c0558e9
Split enumerators and cleanup
smoogipoo 43ce956
Use object equality as absolute truth
smoogipoo 614b0ef
Remove enumerator in favour of manual enumeration
smoogipoo 39d0b5f
Remove null handling
smoogipoo ba2f666
Merge branch 'master' into weaklist-improvement
peppy File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System; | ||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Runtime.CompilerServices; | ||
|
||
namespace osu.Framework.Lists | ||
{ | ||
public partial class WeakList<T> | ||
{ | ||
/// <summary> | ||
/// An enumerator over all items in a <see cref="WeakList{T}"/>. Does not guarantee the validity of items. | ||
/// </summary> | ||
private struct AllItemsEnumerator : IEnumerator<T> | ||
smoogipoo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
private readonly WeakList<T> weakList; | ||
|
||
/// <summary> | ||
/// Creates a new <see cref="AllItemsEnumerator"/>. | ||
/// </summary> | ||
/// <param name="weakList">The <see cref="WeakList{T}"/> to enumerate over.</param> | ||
internal AllItemsEnumerator(WeakList<T> weakList) | ||
{ | ||
this.weakList = weakList; | ||
|
||
CurrentItemIndex = weakList.listStart - 1; // The first MoveNext() should bring the iterator to the start | ||
} | ||
|
||
public bool MoveNext() | ||
{ | ||
while (true) | ||
{ | ||
++CurrentItemIndex; | ||
|
||
// Check whether we're still within the valid range of the list. | ||
if (CurrentItemIndex >= weakList.listEnd) | ||
return false; | ||
|
||
if (weakList.list[CurrentItemIndex].Reference != null) | ||
return true; | ||
} | ||
} | ||
|
||
public void Reset() | ||
{ | ||
CurrentItemIndex = weakList.listStart - 1; | ||
} | ||
|
||
public readonly T Current => throw new NotImplementedException("This enumerator doesn't support retrieving the current item."); | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public readonly bool CheckEquals(int hashCode) | ||
=> weakList.list[CurrentItemIndex].ObjectHashCode == hashCode; | ||
|
||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | ||
public readonly bool CheckEquals(WeakReference<T> weakReference) | ||
=> weakList.list[CurrentItemIndex].Reference == weakReference; | ||
|
||
internal int CurrentItemIndex { get; private set; } | ||
|
||
readonly object IEnumerator.Current => Current; | ||
|
||
public void Dispose() | ||
{ | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System.Collections; | ||
using System.Collections.Generic; | ||
|
||
namespace osu.Framework.Lists | ||
{ | ||
public partial class WeakList<T> | ||
{ | ||
/// <summary> | ||
/// An enumerator over only the valid items of a <see cref="WeakList{T}"/>. | ||
/// </summary> | ||
public struct ValidItemsEnumerator : IEnumerator<T> | ||
{ | ||
private WeakList<T> weakList; | ||
private int currentItemIndex; | ||
|
||
/// <summary> | ||
/// Creates a new <see cref="ValidItemsEnumerator"/>. | ||
/// </summary> | ||
/// <param name="weakList">The <see cref="WeakList{T}"/> to enumerate over.</param> | ||
internal ValidItemsEnumerator(WeakList<T> weakList) | ||
{ | ||
this.weakList = weakList; | ||
|
||
currentItemIndex = weakList.listStart - 1; // The first MoveNext() should bring the iterator to the start | ||
Current = null; | ||
} | ||
|
||
public bool MoveNext() | ||
{ | ||
while (true) | ||
{ | ||
++currentItemIndex; | ||
|
||
// Check whether we're still within the valid range of the list. | ||
if (currentItemIndex >= weakList.listEnd) | ||
return false; | ||
|
||
var weakReference = weakList.list[currentItemIndex].Reference; | ||
|
||
// Check whether the reference exists. | ||
if (weakReference == null || !weakReference.TryGetTarget(out var obj)) | ||
{ | ||
// If the reference doesn't exist, it must have previously been removed and can be skipped. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that I've removed the This enumerator is only used by |
||
continue; | ||
} | ||
|
||
Current = obj; | ||
return true; | ||
} | ||
} | ||
|
||
public void Reset() | ||
{ | ||
currentItemIndex = weakList.listStart - 1; | ||
Current = null; | ||
} | ||
|
||
public T Current { get; private set; } | ||
|
||
readonly object IEnumerator.Current => Current; | ||
|
||
public void Dispose() | ||
{ | ||
weakList = null; | ||
Current = null; | ||
} | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 don't know that this is correct, unfortunately... For one thing, this can break if the class implements
IEquatable<T>
, but even if you use the more-correct-for-that-caseRuntimeHelpers.GetHashCode()
, a docs note states:Unless I'm reading this wrong, this indicates that collisions are potentially possible and that the hash code is not an uniquely-identifying invertible function. Sure, that possibility is probably slim, but I don't know that we ever want to debug one of those...
I suppose for full correctness this would be salvageable by keeping hash buckets, iterating over those rather than stopping at first match, and using
ReferenceEquals
for absolute certainty, but that might kneecap the optimisation completely.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.
There's little cost associated with using object equality as absolute truth. It's technically already using "hash buckets", so it'll degenerate back to the original performance with many hash collisions, but that's a rare case.
I'll keep using EqualityComparer though - want to avoid boxing.
Updated the o!f benchmarks, only about 100us difference in RemoveAllStaggered(1000).