我有一个非常大的集合,它实现了通用IList接口,包含了数千万个元素,我想使用PLINQ并行处理它们。我注意到并行性的开销相当大,因为处理每个单独的元素都是非常轻量级的,所以我正在寻找通过将IList<T>分割成小段来分组处理的方法。我的目标是最终得到这样的东西:
IList<Person> source = GetAllPersons();
double averageAge = source
.Segmentate(1000) // Hypothetical operator that segmentates the source
.AsParallel()
.Select(segment => segment.Select(person => (double)person.CalculateAge()).Sum())
.Sum() / source.Count;我可以使用来自Batch库的MoreLinq操作符,或者使用许多 相关 问题的任何答案,但是所有这些解决方案都在分配多个小数组(或列表),并将源代码复制到这些容器中,我不想这样做。在我的例子中,我有一个额外的要求,就是尽可能多地保持垃圾收集器空闲。
我注意到.NET具有ArraySegment类型,这似乎完全符合我的要求:
// Delimits a section of a one-dimensional array.
public readonly struct ArraySegment<T> : ICollection<T>, IEnumerable<T>,
IEnumerable, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T>我可以使用这种类型来实现这样的无分配Segmentate操作符:
/// <summary>Segmentates the source array into sized segments.</summary>
public static IEnumerable<ArraySegment<T>> Segmentate<T>(this T[] source, int size)
{
for (int offset = 0; offset < source.Length; offset += size)
{
yield return new ArraySegment<T>(source, offset,
Math.Min(size, source.Length - offset));
}
}但是我不能使用这种类型,因为我的源是IList<T>而不是数组。而将其复制到数组并不是一个真正的选项,因为源经常发生变异。并且一直创建新的数组副本违背了我的要求。
因此,我正在搜索ListSegment<T>类型,但据我所知,它并不存在于.NET中,是否必须自己实现它?如果是,怎么做?或者是否有其他方法可以在不引起分配的情况下分割IList<T>?
Clarification:我的源代码集合不是List。它是一个实现IList<T>接口的自定义类。
发布于 2020-12-10 10:04:26
您需要为IList<T>实现一个等价的IList<T>。见下文的实施。要获得最佳性能,请考虑使用跨度。
ListSegment结构
public readonly struct ListSegment<T> : IList<T>
{
public List<T> Items { get; }
public int Offset { get; }
public int Count { get; }
public ListSegment(List<T> items, int offset, int count)
{
Items = items ?? throw new ArgumentNullException(nameof(items));
Offset = offset;
Count = count;
if (items.Count < offset + count)
{
throw new ArgumentException("List segment out of range.", nameof(count));
}
}
public void CopyTo(T[] array, int index)
{
if (Count > 0)
{
Items.CopyTo(Offset, array, index, Count);
}
}
public bool Contains(T item) => IndexOf(item) != -1;
public int IndexOf(T item)
{
for (var i = Offset; i < Offset + Count; i++)
{
if (Items[i].Equals(item))
{
return i;
}
}
return -1;
}
private T ElementAt(int index)
{
if (Count > 0)
{
return Items[Offset + index];
}
throw new ArgumentOutOfRangeException(nameof(index));
}
public ListSegmentEnumerator GetEnumerator() => new ListSegmentEnumerator(this);
#region IEnumerable<T> interface
IEnumerator<T> IEnumerable<T>.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
#endregion
#region ICollection<T> interface
bool ICollection<T>.IsReadOnly => true;
void ICollection<T>.Add(T item) => throw new NotImplementedException();
bool ICollection<T>.Remove(T item) => throw new NotImplementedException();
void ICollection<T>.Clear() => throw new NotImplementedException();
#endregion
#region IList<T> interface
void IList<T>.Insert(int index, T item) => throw new NotImplementedException();
void IList<T>.RemoveAt(int index) => throw new NotImplementedException();
T IList<T>.this[int index]
{
get => ElementAt(index);
set => throw new NotImplementedException();
}
#endregion
public struct ListSegmentEnumerator : IEnumerator<T>
{
private readonly List<T> items;
private readonly int start;
private readonly int end;
private int current;
public ListSegmentEnumerator(ListSegment<T> segment)
{
items = segment.Items;
start = segment.Offset;
end = start + segment.Count;
current = start - 1;
}
public bool MoveNext()
{
if (current < end)
{
current++;
return current < end;
}
return false;
}
public T Current => items[current];
object IEnumerator.Current => Current;
void IEnumerator.Reset() => current = start - 1;
void IDisposable.Dispose() { }
}
}发布于 2020-12-10 07:08:45
这个答案假设您的具体IList是List类型的。您可以使用GetRange函数,它可以满足您的需要:
引用类型集合的浅表副本或该集合的子集仅包含对集合元素的引用。对象本身不被复制。新列表中的引用指向与原始列表中的引用相同的对象。
甚至ArraySegment<T>也会创建某种引用对象来存储数组段,因此您不能完全避免这种情况。
如果您想避免存储引用(也称为浅拷贝),那么应该使用Span,但您的评论是您的集合更改与这冲突。
在使用Span时,不应从列表中添加或删除项目。
所以,你唯一的解决办法就是自己创造一个,就像你提到的那样。
警告:有一个原因,为什么这样的东西不存在。数组的大小是固定的,因此获取一个段要安全得多。在创建这样的构造时,要小心意外的后果和副作用。这就是为什么Span警告你它是不安全的。您只知道您的需求以及您的数据是如何变化的,所以您的集合包装器应该考虑它们并相应地处理它们。
https://stackoverflow.com/questions/65229811
复制相似问题