Skip to content

System.Linq.Parallel is missing optimizations for IReadOnlyList<T> and IReadOnlyCollection<T> #122817

@poizan42

Description

@poizan42

Description

PartitionedDataSource.InitializePartitions only checks for IList<T> here:

if (source is IList<T> sourceAsList)
{
QueryOperatorEnumerator<T, int>[] partitions = new QueryOperatorEnumerator<T, int>[partitionCount];
// We use this below to specialize enumerators when possible.
T[]? sourceAsArray = source as T[];
// If range partitioning is used, chunk size will be unlimited, i.e. -1.
int maxChunkSize = -1;
if (useStriping)
{
maxChunkSize = Scheduling.GetDefaultChunkSize<T>();
// The minimum chunk size is 1.
if (maxChunkSize < 1)
{
maxChunkSize = 1;
}
}
// Calculate indexes and construct enumerators that walk a subset of the input.
for (int i = 0; i < partitionCount; i++)
{
if (sourceAsArray != null)
{
// If the source is an array, we can use a fast path below to index using
// 'ldelem' instructions rather than making interface method calls.
if (useStriping)
{
partitions[i] = new ArrayIndexRangeEnumerator(sourceAsArray, partitionCount, i, maxChunkSize);
}
else
{
partitions[i] = new ArrayContiguousIndexRangeEnumerator(sourceAsArray, partitionCount, i);
}
TraceHelpers.TraceInfo("ContiguousRangePartitionExchangeStream::MakePartitions - (array) #{0} {1}", i, maxChunkSize);
}
else
{
// Create a general purpose list enumerator object.
if (useStriping)
{
partitions[i] = new ListIndexRangeEnumerator(sourceAsList, partitionCount, i, maxChunkSize);
}
else
{
partitions[i] = new ListContiguousIndexRangeEnumerator(sourceAsList, partitionCount, i);
}
TraceHelpers.TraceInfo("ContiguousRangePartitionExchangeStream::MakePartitions - (list) #{0} {1})", i, maxChunkSize);
}
}
Debug.Assert(partitions.Length == partitionCount);
_partitions = partitions;
}
else
{
// We couldn't use an in-place partition. Shucks. Defer to the other overload which
// accepts an enumerator as input instead.
_partitions = MakePartitions(source.GetEnumerator(), partitionCount);
}

ScanQueryOperator has paths specifically for IList<T> but not IReadOnlyList<T> here

and here

ParallelEnumerable.Count only uses the count from ICollection<T> and not IReadOnlyCollection<T> here

if (sourceAsWrapper.WrappedEnumerable is ICollection<TSource> sourceAsCollection)
and here
if (sourceAsWrapper.WrappedEnumerable is ICollection<TSource> sourceAsCollection)

This looks like some holdover from the pre-netfx4.5 days. Consumers really shouldn't have to implement IList<T> with all the modifying members throwing exceptions for every read-only collection just to get better performance from PLINQ.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions