Skip to content
Merged
Show file tree
Hide file tree
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
Expand Up @@ -142,8 +142,17 @@ public static int SlowRuntimeTimeoutModifier

public static bool IsStartingProcessesSupported => !IsiOS && !IstvOS;

public static bool IsSpeedOptimized => !IsSizeOptimized;
public static bool IsSizeOptimized => IsBrowser || IsWasi || IsAndroid || IsAppleMobile;
public static bool IsLinqSpeedOptimized => !IsLinqSizeOptimized;
public static bool IsLinqSizeOptimized => s_linqIsSizeOptimized.Value;
private static readonly Lazy<bool> s_linqIsSizeOptimized = new Lazy<bool>(ComputeIsLinqSizeOptimized);
private static bool ComputeIsLinqSizeOptimized()
{
#if NET
return (bool)typeof(Enumerable).GetMethod("get_IsSizeOptimized", BindingFlags.NonPublic | BindingFlags.Static).Invoke(null, Array.Empty<object>());
#else
return false;
#endif
}

public static bool IsBrowserDomSupported => IsEnvironmentVariableTrue("IsBrowserDomSupported");
public static bool IsBrowserDomSupportedOrNotBrowser => IsNotBrowser || IsBrowserDomSupported;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace System.IO.Compression.Tests;
[Collection(nameof(DisableParallelization))]
public class zip_LargeFiles : ZipFileTestBase
{
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[OuterLoop("It requires almost 12 GB of free disk space")]
public static void UnzipOver4GBZipFile()
{
Expand Down Expand Up @@ -49,7 +49,7 @@ private static void FillWithHardToCompressData(byte[] buffer)
Random.Shared.NextBytes(buffer);
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[OuterLoop("It requires 5~6 GB of free disk space and a lot of CPU time for compressed tests")]
[InlineData(false)]
[InlineData(true)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ private static void FillWithHardToCompressData(byte[] buffer)
Random.Shared.NextBytes(buffer);
}

[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsSpeedOptimized), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotMobile), nameof(PlatformDetection.Is64BitProcess))] // don't run it on slower runtimes
[InlineData(false)]
[InlineData(true)]
[OuterLoop("It requires 5~6 GB of free disk space and a lot of CPU time for compressed tests")]
Expand Down
59 changes: 23 additions & 36 deletions src/libraries/System.Linq/src/System.Linq.csproj
Original file line number Diff line number Diff line change
@@ -1,98 +1,85 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(NetCoreAppCurrent);$(NetCoreAppCurrent)-browser;$(NetCoreAppCurrent)-wasi;$(NetCoreAppCurrent)-android;$(NetCoreAppCurrent)-ios;$(NetCoreAppCurrent)-tvos</TargetFrameworks>
<TargetFramework>$(NetCoreAppCurrent)</TargetFramework>
<UseCompilerGeneratedDocXmlFile>false</UseCompilerGeneratedDocXmlFile>
</PropertyGroup>

<!-- DesignTimeBuild requires all the TargetFramework Derived Properties to not be present in the first property group. -->
<PropertyGroup>
<TargetPlatformIdentifier>$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)'))</TargetPlatformIdentifier>
<OptimizeForSize Condition="'$(TargetPlatformIdentifier)' == 'browser' or '$(TargetPlatformIdentifier)' == 'android' or '$(TargetPlatformIdentifier)' == 'ios' or '$(TargetPlatformIdentifier)' == 'tvos'">true</OptimizeForSize>
Copy link
Member

@pavelsavara pavelsavara Jan 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OptimizeForSize was set true for some targets but it's not anymore.
WasmFeatures.props set the default for wasm workload, but default runtime pack also needs to be optimized for size - when people don't use workload in Blazor.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OptimizeForSize was set true for some targets but it's not anymore.

Yes, that is the purpose of this PR. To only have one build of this library (#111743 (comment))

WasmFeatures.props set the default for wasm workload, but default runtime pack also needs to be optimized for size - when people don't use workload in Blazor.

Could you please point me to which .targets we need to update? Is it the Microsoft.NET.Sdk.BlazorWebAssembly.6_0.targets linked from #111743 (comment)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(To be clear, the OptimizeForSize property controls whether we build System.Linq assembly in a not-fully-compatible manner (see the tests I'm touching where it behaves differently) that has better size characteristics. This PR deletes that build and changes it to always build the compatible Linq implementation. It introduces a feature switch that allows switching to the not-fully-compatible mode. The default for the feature switch is disabled. It can be enabled where the tradeoff is worth it (I have PRs out enabling it in places, linked above). The advantage is that now if a customer runs into the incompatibility, they can just set a property and unblock themselves.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Folded the BlazorWebAssembly.targets change into dotnet/sdk#46375.

WasmFeatures.props change is already part of the PR here.

<DefineConstants Condition="'$(OptimizeForSize)' == 'true'">$(DefineConstants);OPTIMIZE_FOR_SIZE</DefineConstants>
</PropertyGroup>

<ItemGroup Condition="'$(OptimizeForSize)' == true">
<Compile Include="System\Linq\Skip.SizeOpt.cs" />
<Compile Include="System\Linq\Take.SizeOpt.cs" />
</ItemGroup>

<ItemGroup Condition="'$(OptimizeForSize)' != true">
<Compile Include="System\Linq\AppendPrepend.SpeedOpt.cs" />
<Compile Include="System\Linq\Cast.SpeedOpt.cs" />
<Compile Include="System\Linq\Concat.SpeedOpt.cs" />
<Compile Include="System\Linq\DefaultIfEmpty.SpeedOpt.cs" />
<Compile Include="System\Linq\Distinct.SpeedOpt.cs" />
<Compile Include="System\Linq\Grouping.SpeedOpt.cs" />
<Compile Include="System\Linq\Iterator.SpeedOpt.cs" />
<Compile Include="System\Linq\Lookup.SpeedOpt.cs" />
<Compile Include="System\Linq\OfType.SpeedOpt.cs" />
<Compile Include="System\Linq\OrderedEnumerable.SpeedOpt.cs" />
<Compile Include="System\Linq\Range.SpeedOpt.cs" />
<Compile Include="System\Linq\Repeat.SpeedOpt.cs" />
<Compile Include="System\Linq\Reverse.SpeedOpt.cs" />
<Compile Include="System\Linq\Select.SpeedOpt.cs" />
<Compile Include="System\Linq\SelectMany.SpeedOpt.cs" />
<Compile Include="System\Linq\Skip.SpeedOpt.cs" />
<Compile Include="System\Linq\SkipTake.SpeedOpt.cs" />
<Compile Include="System\Linq\Take.SpeedOpt.cs" />
<Compile Include="System\Linq\Union.SpeedOpt.cs" />
<Compile Include="System\Linq\Where.SpeedOpt.cs" />
</ItemGroup>

<ItemGroup>
<Compile Include="System\Linq\Aggregate.cs" />
<Compile Include="System\Linq\AnyAll.cs" />
<Compile Include="System\Linq\AppendPrepend.cs" />
<Compile Include="System\Linq\AppendPrepend.SpeedOpt.cs" />
<Compile Include="System\Linq\Average.cs" />
<Compile Include="System\Linq\Cast.cs" />
<Compile Include="System\Linq\Cast.SpeedOpt.cs" />
<Compile Include="System\Linq\Chunk.cs" />
<Compile Include="System\Linq\Concat.cs" />
<Compile Include="System\Linq\Concat.SpeedOpt.cs" />
<Compile Include="System\Linq\Contains.cs" />
<Compile Include="System\Linq\AggregateBy.cs" />
<Compile Include="System\Linq\CountBy.cs" />
<Compile Include="System\Linq\Count.cs" />
<Compile Include="System\Linq\DebugView.cs" />
<Compile Include="System\Linq\DefaultIfEmpty.cs" />
<Compile Include="System\Linq\DefaultIfEmpty.SpeedOpt.cs" />
<Compile Include="System\Linq\Distinct.cs" />
<Compile Include="System\Linq\Distinct.SpeedOpt.cs" />
<Compile Include="System\Linq\ElementAt.cs" />
<Compile Include="System\Linq\Enumerable.cs" />
<Compile Include="System\Linq\Except.cs" />
<Compile Include="System\Linq\First.cs" />
<Compile Include="System\Linq\Grouping.cs" />
<Compile Include="System\Linq\Grouping.SpeedOpt.cs" />
<Compile Include="System\Linq\GroupJoin.cs" />
<Compile Include="System\Linq\Index.cs" />
<Compile Include="System\Linq\Intersect.cs" />
<Compile Include="System\Linq\Iterator.cs" />
<Compile Include="System\Linq\Iterator.SpeedOpt.cs" />
<Compile Include="System\Linq\Join.cs" />
<Compile Include="System\Linq\Last.cs" />
<Compile Include="System\Linq\LeftJoin.cs" />
<Compile Include="System\Linq\Lookup.cs" />
<Compile Include="System\Linq\Lookup.SpeedOpt.cs" />
<Compile Include="System\Linq\Max.cs" />
<Compile Include="System\Linq\MaxMin.cs" />
<Compile Include="System\Linq\Min.cs" />
<Compile Include="System\Linq\OfType.cs" />
<Compile Include="System\Linq\OfType.SpeedOpt.cs" />
<Compile Include="System\Linq\OrderBy.cs" />
<Compile Include="System\Linq\OrderedEnumerable.cs" />
<Compile Include="System\Linq\OrderedEnumerable.SpeedOpt.cs" />
<Compile Include="System\Linq\PartialArrayEnumerator.cs" />
<Compile Include="System\Linq\Range.cs" />
<Compile Include="System\Linq\Range.SpeedOpt.cs" />
<Compile Include="System\Linq\Repeat.cs" />
<Compile Include="System\Linq\Repeat.SpeedOpt.cs" />
<Compile Include="System\Linq\Reverse.cs" />
<Compile Include="System\Linq\Reverse.SpeedOpt.cs" />
<Compile Include="System\Linq\RightJoin.cs" />
<Compile Include="System\Linq\SegmentedArrayBuilder.cs" />
<Compile Include="System\Linq\Select.cs" />
<Compile Include="System\Linq\Select.SpeedOpt.cs" />
<Compile Include="System\Linq\SelectMany.cs" />
<Compile Include="System\Linq\SelectMany.SpeedOpt.cs" />
<Compile Include="System\Linq\SequenceEqual.cs" />
<Compile Include="System\Linq\Single.cs" />
<Compile Include="System\Linq\SingleLinkedNode.cs" />
<Compile Include="System\Linq\Skip.cs" />
<Compile Include="System\Linq\Skip.SizeOpt.cs" />
<Compile Include="System\Linq\Skip.SpeedOpt.cs" />
<Compile Include="System\Linq\SkipTake.SpeedOpt.cs" />
<Compile Include="System\Linq\Sum.cs" />
<Compile Include="System\Linq\Take.cs" />
<Compile Include="System\Linq\Take.SizeOpt.cs" />
<Compile Include="System\Linq\Take.SpeedOpt.cs" />
<Compile Include="System\Linq\ThrowHelper.cs" />
<Compile Include="System\Linq\ToCollection.cs" />
<Compile Include="System\Linq\Union.cs" />
<Compile Include="System\Linq\Union.SpeedOpt.cs" />
<Compile Include="System\Linq\Utilities.cs" />
<Compile Include="System\Linq\Where.cs" />
<Compile Include="System\Linq\Where.SpeedOpt.cs" />
<Compile Include="System\Linq\Zip.cs" />
</ItemGroup>

Expand Down
4 changes: 1 addition & 3 deletions src/libraries/System.Linq/src/System/Linq/AnyAll.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ public static bool Any<TSource>(this IEnumerable<TSource> source)
return gc.Count != 0;
}

#if !OPTIMIZE_FOR_SIZE
if (source is Iterator<TSource> iterator)
if (!IsSizeOptimized && source is Iterator<TSource> iterator)
{
int count = iterator.GetCount(onlyIfCheap: true);
if (count >= 0)
Expand All @@ -32,7 +31,6 @@ public static bool Any<TSource>(this IEnumerable<TSource> source)
iterator.TryGetFirst(out bool found);
return found;
}
#endif

if (source is ICollection ngc)
{
Expand Down
8 changes: 2 additions & 6 deletions src/libraries/System.Linq/src/System/Linq/Count.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ public static int Count<TSource>(this IEnumerable<TSource> source)
return collectionoft.Count;
}

#if !OPTIMIZE_FOR_SIZE
if (source is Iterator<TSource> iterator)
if (!IsSizeOptimized && source is Iterator<TSource> iterator)
{
return iterator.GetCount(onlyIfCheap: false);
}
#endif

if (source is ICollection collection)
{
Expand Down Expand Up @@ -115,8 +113,7 @@ public static bool TryGetNonEnumeratedCount<TSource>(this IEnumerable<TSource> s
return true;
}

#if !OPTIMIZE_FOR_SIZE
if (source is Iterator<TSource> iterator)
if (!IsSizeOptimized && source is Iterator<TSource> iterator)
{
int c = iterator.GetCount(onlyIfCheap: true);
if (c >= 0)
Expand All @@ -125,7 +122,6 @@ public static bool TryGetNonEnumeratedCount<TSource>(this IEnumerable<TSource> s
return true;
}
}
#endif

if (source is ICollection collection)
{
Expand Down
8 changes: 2 additions & 6 deletions src/libraries/System.Linq/src/System/Linq/ElementAt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, int i

bool found;
TSource? element =
#if !OPTIMIZE_FOR_SIZE
source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
#endif
!IsSizeOptimized && source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
TryGetElementAtNonIterator(source, index, out found);

if (!found)
Expand Down Expand Up @@ -123,9 +121,7 @@ public static TSource ElementAt<TSource>(this IEnumerable<TSource> source, Index
}

return
#if !OPTIMIZE_FOR_SIZE
source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
#endif
!IsSizeOptimized && source is Iterator<TSource> iterator ? iterator.TryGetElementAt(index, out found) :
TryGetElementAtNonIterator(source, index, out found);
}

Expand Down
4 changes: 4 additions & 0 deletions src/libraries/System.Linq/src/System/Linq/Enumerable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace System.Linq
{
public static partial class Enumerable
{
[FeatureSwitchDefinition("System.Linq.Enumerable.IsSizeOptimized")]
internal static bool IsSizeOptimized { get; } = AppContext.TryGetSwitch("System.Linq.Enumerable.IsSizeOptimized", out bool isEnabled) ? isEnabled : false;

public static IEnumerable<TSource> AsEnumerable<TSource>(this IEnumerable<TSource> source) => source;

/// <summary>Returns an empty <see cref="IEnumerable{TResult}"/>.</summary>
Expand Down
4 changes: 1 addition & 3 deletions src/libraries/System.Linq/src/System/Linq/First.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ public static TSource FirstOrDefault<TSource>(this IEnumerable<TSource> source,
}

return
#if !OPTIMIZE_FOR_SIZE
source is Iterator<TSource> iterator ? iterator.TryGetFirst(out found) :
#endif
!IsSizeOptimized && source is Iterator<TSource> iterator ? iterator.TryGetFirst(out found) :
TryGetFirstNonIterator(source, out found);
}

Expand Down
8 changes: 3 additions & 5 deletions src/libraries/System.Linq/src/System/Linq/Iterator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,9 @@ public Iterator<TSource> GetEnumerator()
/// <typeparam name="TResult">The type of the mapped items.</typeparam>
/// <param name="selector">The selector used to map each item.</param>
public virtual IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) =>
#if OPTIMIZE_FOR_SIZE
new IEnumerableSelectIterator<TSource, TResult>(this, selector);
#else
new IteratorSelectIterator<TSource, TResult>(this, selector);
#endif
!IsSizeOptimized
? new IteratorSelectIterator<TSource, TResult>(this, selector)
: new IEnumerableSelectIterator<TSource, TResult>(this, selector);


/// <summary>
Expand Down
4 changes: 1 addition & 3 deletions src/libraries/System.Linq/src/System/Linq/Last.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,7 @@ public static TSource LastOrDefault<TSource>(this IEnumerable<TSource> source, F
}

return
#if !OPTIMIZE_FOR_SIZE
source is Iterator<TSource> iterator ? iterator.TryGetLast(out found) :
#endif
!IsSizeOptimized && source is Iterator<TSource> iterator ? iterator.TryGetLast(out found) :
TryGetLastNonIterator(source, out found);
}

Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Linq/src/System/Linq/Skip.SizeOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace System.Linq
{
public static partial class Enumerable
{
private static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count)
private static IEnumerable<TSource> SizeOptimizedSkipIterator<TSource>(IEnumerable<TSource> source, int count)
{
using IEnumerator<TSource> e = source.GetEnumerator();
while (count > 0 && e.MoveNext()) count--;
Expand Down
2 changes: 1 addition & 1 deletion src/libraries/System.Linq/src/System/Linq/Skip.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace System.Linq
{
public static partial class Enumerable
{
private static IEnumerable<TSource> SkipIterator<TSource>(IEnumerable<TSource> source, int count) =>
private static IEnumerable<TSource> SpeedOptimizedSkipIterator<TSource>(IEnumerable<TSource> source, int count) =>
source is IList<TSource> sourceList ?
(IEnumerable<TSource>)new IListSkipTakeIterator<TSource>(sourceList, count, int.MaxValue) :
new IEnumerableSkipTakeIterator<TSource>(source, count, -1);
Expand Down
6 changes: 2 additions & 4 deletions src/libraries/System.Linq/src/System/Linq/Skip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,12 @@ public static IEnumerable<TSource> Skip<TSource>(this IEnumerable<TSource> sourc

count = 0;
}
#if !OPTIMIZE_FOR_SIZE
else if (source is Iterator<TSource> iterator)
else if (!IsSizeOptimized && source is Iterator<TSource> iterator)
{
return iterator.Skip(count) ?? Empty<TSource>();
}
#endif

return SkipIterator(source, count);
return IsSizeOptimized ? SizeOptimizedSkipIterator(source, count) : SpeedOptimizedSkipIterator(source, count);
}

public static IEnumerable<TSource> SkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/System.Linq/src/System/Linq/Take.SizeOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Linq
{
public static partial class Enumerable
{
private static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
private static IEnumerable<TSource> SizeOptimizedTakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
Debug.Assert(count > 0);

Expand All @@ -19,7 +19,7 @@ private static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> s
}
}

private static IEnumerable<TSource> TakeRangeIterator<TSource>(IEnumerable<TSource> source, int startIndex, int endIndex)
private static IEnumerable<TSource> SizeOptimizedTakeRangeIterator<TSource>(IEnumerable<TSource> source, int startIndex, int endIndex)
{
Debug.Assert(source is not null);
Debug.Assert(startIndex >= 0 && startIndex < endIndex);
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/System.Linq/src/System/Linq/Take.SpeedOpt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace System.Linq
{
public static partial class Enumerable
{
private static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
private static IEnumerable<TSource> SpeedOptimizedTakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
Debug.Assert(source is not null && !IsEmptyArray(source));
Debug.Assert(count > 0);
Expand All @@ -19,7 +19,7 @@ private static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> s
new IEnumerableSkipTakeIterator<TSource>(source, 0, count - 1);
}

private static IEnumerable<TSource> TakeRangeIterator<TSource>(IEnumerable<TSource> source, int startIndex, int endIndex)
private static IEnumerable<TSource> SpeedOptimizedTakeRangeIterator<TSource>(IEnumerable<TSource> source, int startIndex, int endIndex)
{
Debug.Assert(source is not null && !IsEmptyArray(source));
Debug.Assert(startIndex >= 0 && startIndex < endIndex);
Expand Down
Loading
Loading