Skip to content

Commit e99e981

Browse files
committed
Reduce allocations in WhereAsArray IA/IA.Builder extension methods
Very heavily inspired by Roslyn's implementation (https://github.com/dotnet/roslyn/blob/fadd60c39d63743cdaeacd2c3f8aa003f5bb7f58/src/Dependencies/Collections/Extensions/ImmutableArrayExtensions.cs#L546-L613) I needed the IA.Builder version of this for local changes, but separated this out into a separate PR.
1 parent bf3d9c9 commit e99e981

File tree

2 files changed

+163
-6
lines changed

2 files changed

+163
-6
lines changed

src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/ImmutableArrayExtensionsTests.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Collections.Immutable;
7+
using System.Runtime.InteropServices;
78
using Xunit;
89

910
namespace Microsoft.AspNetCore.Razor.Utilities.Shared.Test;
@@ -232,4 +233,68 @@ public void InsertRange_WithReferenceTypes_InsertsCorrectly()
232233
Assert.Equal(4, builder.Count);
233234
Assert.Equal(["apple", "cherry", "date", "banana"], builder.ToArray());
234235
}
236+
237+
[Fact]
238+
public void WhereAsArray_ImmutableArray()
239+
{
240+
ImmutableArray<int> data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
241+
ImmutableArray<int> expected = [2, 4, 6, 8, 10];
242+
243+
var actual = data.WhereAsArray(static x => x % 2 == 0);
244+
Assert.Equal<int>(expected, actual);
245+
}
246+
247+
[Fact]
248+
public void WhereAsArray_ImmutableArray_None()
249+
{
250+
ImmutableArray<int> data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
251+
ImmutableArray<int> expected = [];
252+
253+
var actual = data.WhereAsArray(static x => false);
254+
Assert.Equal<int>(expected, actual);
255+
}
256+
257+
[Fact]
258+
public void WhereAsArray_ImmutableArray_All()
259+
{
260+
ImmutableArray<int> data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
261+
var expected = data;
262+
263+
var actual = data.WhereAsArray(static x => true);
264+
Assert.Equal<int>(expected, actual);
265+
Assert.Same(ImmutableCollectionsMarshal.AsArray(expected), ImmutableCollectionsMarshal.AsArray(actual));
266+
}
267+
268+
[Fact]
269+
public void WhereAsArray_ImmutableArrayBuilder()
270+
{
271+
var data = ImmutableArray.CreateBuilder<int>();
272+
data.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
273+
ImmutableArray<int> expected = [2, 4, 6, 8, 10];
274+
275+
var actual = data.WhereAsArray(static x => x % 2 == 0);
276+
Assert.Equal<int>(expected, actual);
277+
}
278+
279+
[Fact]
280+
public void WhereAsArray_ImmutableArrayBuilder_None()
281+
{
282+
var data = ImmutableArray.CreateBuilder<int>();
283+
data.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
284+
ImmutableArray<int> expected = [];
285+
286+
var actual = data.WhereAsArray(static x => false);
287+
Assert.Equal<int>(expected, actual);
288+
}
289+
290+
[Fact]
291+
public void WhereAsArray_ImmutableArrayBuilder_All()
292+
{
293+
var data = ImmutableArray.CreateBuilder<int>();
294+
data.AddRange(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
295+
var expected = data;
296+
297+
var actual = data.WhereAsArray(static x => true);
298+
Assert.Equal<int>(expected, actual);
299+
}
235300
}

src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/ImmutableArrayExtensions.cs

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,22 +146,114 @@ public static ImmutableArray<TResult> SelectManyAsArray<TSource, TResult>(this I
146146

147147
public static ImmutableArray<T> WhereAsArray<T>(this ImmutableArray<T> source, Func<T, bool> predicate)
148148
{
149-
if (source is [])
149+
using var builder = new PooledArrayBuilder<T>();
150+
var none = true;
151+
var all = true;
152+
153+
var n = source.Length;
154+
for (var i = 0; i < n; i++)
150155
{
151-
return [];
156+
var a = source[i];
157+
158+
if (predicate(a))
159+
{
160+
none = false;
161+
if (all)
162+
{
163+
continue;
164+
}
165+
166+
Debug.Assert(i > 0);
167+
builder.Add(a);
168+
}
169+
else
170+
{
171+
if (none)
172+
{
173+
all = false;
174+
continue;
175+
}
176+
177+
Debug.Assert(i > 0);
178+
if (all)
179+
{
180+
all = false;
181+
for (var j = 0; j < i; j++)
182+
{
183+
builder.Add(source[j]);
184+
}
185+
}
186+
}
152187
}
153188

189+
if (all)
190+
{
191+
return source;
192+
}
193+
else if (none)
194+
{
195+
return ImmutableArray<T>.Empty;
196+
}
197+
else
198+
{
199+
return builder.ToImmutableAndClear();
200+
}
201+
}
202+
203+
public static ImmutableArray<T> WhereAsArray<T>(this ImmutableArray<T>.Builder source, Func<T, bool> predicate)
204+
{
154205
using var builder = new PooledArrayBuilder<T>();
206+
var none = true;
207+
var all = true;
155208

156-
foreach (var item in source)
209+
var n = source.Count;
210+
for (var i = 0; i < n; i++)
157211
{
158-
if (predicate(item))
212+
var a = source[i];
213+
214+
if (predicate(a))
215+
{
216+
none = false;
217+
if (all)
218+
{
219+
continue;
220+
}
221+
222+
Debug.Assert(i > 0);
223+
builder.Add(a);
224+
}
225+
else
159226
{
160-
builder.Add(item);
227+
if (none)
228+
{
229+
all = false;
230+
continue;
231+
}
232+
233+
Debug.Assert(i > 0);
234+
if (all)
235+
{
236+
all = false;
237+
for (var j = 0; j < i; j++)
238+
{
239+
builder.Add(source[j]);
240+
}
241+
}
161242
}
162243
}
163244

164-
return builder.ToImmutableAndClear();
245+
if (all)
246+
{
247+
return source.ToImmutable();
248+
}
249+
else if (none)
250+
{
251+
return ImmutableArray<T>.Empty;
252+
}
253+
else
254+
{
255+
return builder.ToImmutableAndClear();
256+
}
165257
}
166258

167259
/// <summary>

0 commit comments

Comments
 (0)