每次调用RemoveAt() 时,它必须将指定索引之后的每个元素向上移动一个元素。如果在一个非常大的列表中有数千个元素要删除,那将导致很多很多(不必要的)移动。
My thinking 是,如果您可以计算每个移动的开始索引和长度,则可以在单次处理中处理列表,而不会出现重叠移动。这是通过比较要删除的索引列表中的相邻元素来完成的。虽然这确实意味着要构建第三个 List<> 的移动操作来执行,但我希望提前计划的最有效、最小的移动策略最终会得到回报(或者也许有一种方法可以在不分配任何对象的情况下做到这一点目前还没有发生在我身上)。
您可以在下面的基准代码中看到我的实现,RemoveUsingMoveStrategy()。在test 模式下运行下面的启动程序,我们可以看到它——以及其他答案——在给定索引0、1、5、10、11、 15、15(重复)、18 和 19 从 20 元素中删除 List<int>...
PS> dotnet run --configuration release --framework netcoreapp3.1 --test
[剪辑]
移除使用移动策略():
元素:{ 2, 3, 4, 6, 7, 8, 9, 12, 13, 14, 16, 17 }
计数:12
序列:等于控制列表
持续时间:3.95 毫秒
返回:输入列表
[剪辑]
基准说明
-
我打算以十亿元素 List<int> 为基准,但 RemoveUsingRemoveAt()(受问题中的代码启发)如此效率太低,以至于花费了太长时间,所以我只增加了 1000 万个元素。
-
我仍然希望运行一个不包括RemoveUsingRemoveAt() 的十亿元素基准测试。出于这个原因,我引入了一个名为RemoveUsingListCopy() 的不太天真的实现,作为比较所有列表大小的基线。顾名思义,它不会修改输入列表,而是创建一个应用了删除的新列表。
-
由于并非所有实现都要求要删除的索引列表是唯一的和/或排序的,所以我认为最公平的基准测试方法是在方法需要的基准时间中包含该开销它。
-
我对其他答案中的代码所做的唯一其他更改是利用基准列表(DataList 和 RemovalList)并将 var 更改为显式类型以使读者清楚。
-
RemovalListLocation 表示从哪里删除了DataList 索引。
- 对于
Beginning、Middle 和End,它是从该位置移除的一个连续的RemovalListSize 大小的块。
- 对于
Random,它是RemovalListSize 随机、有效、未排序、不保证唯一的索引,从常量种子生成。
为了使结果保持简短(呃),我选择只对 Middle 进行基准测试——认为这是一个很好的中间折衷——和 Random 值.
基准测试结果
-
RemoveUsingRemoveAt() 是可怕。不要那样做。
-
对于较小的列表,RemoveUsingListCopy() 始终是最快的,但内存使用量增加了一倍。
-
对于较大的列表,Vernou's answer 的速度至少是其他答案的 5 倍,但代价是依赖实施细节。
这只是表明您不一定总是偏爱List<> 而不是数组——除非你需要它的额外功能——因为它并不总是优越的。它隔离了底层数组,防止您(无需反射)在其上使用更高性能的访问方法,如 Array.Copy() 和 unsafe 代码。
-
Theodor Zoulias's answer 在较小的列表中表现良好,但我认为必须“触及”所有 1000 万个索引——每个索引都调用HashSet<>.Contains() 并增加一个索引变量——对于较大的列表来说确实伤害了它。
-
其余实现(包括我的)具有大致相同的性能:不是最好的,但仍然相当不错。
基准数据
以benchmark 模式运行此答案后面定义的启动程序,我从BenchmarkDotNet 获得这些结果...
PS> dotnet run --configuration release --framework netcoreapp3.1 -- benchmark
[剪辑]
// * 概括 *
BenchmarkDotNet=v0.12.1,操作系统=Windows 10.0.19041.450 (2004/?/20H1)
Intel Core i7 CPU 860 2.80GHz (Nehalem),1 个 CPU,8 个逻辑核心和 4 个物理核心
.NET 核心 SDK=3.1.401
[主机]:.NET Core 3.1.7(CoreCLR 4.700.20.36602,CoreFX 4.700.20.37001),X64 RyuJIT
中运行:.NET Framework 4.8 (4.8.4200.0),X64 RyuJIT
Job=MediumRun InvocationCount=1 IterationCount=15
LaunchCount=2 UnrollFactor=1 WarmupCount=10
|方法 |运行时 |数据列表大小 |删除列表大小 |删除列表位置 |平均值 |错误 |标准差 |中位数 |比率 |比率SD |
|------------------------------ |-------------- |--- ---------- |---------------- |-------- |- --------------:|--------------:|--------------:|-- ----------:|--------:|--------:|
| RemoveUsingListCopy | .NET 4.8 | 10000 | 2000 |随机 | 379.9 微秒 | 15.93 微秒 | 23.36 微秒 | 380.4 微秒 | 1.00 | 0.00 |
| RemoveUsingRemoveAt | .NET 4.8 | 10000 | 2000 |随机 | 1,463.7 微秒 | 93.79 微秒 | 137.47 微秒 | 1,434.7 微秒 | 3.87 | 0.41 |
| RemoveUsingMoveStrategy | .NET 4.8 | 10000 | 2000 |随机 | 635.9 微秒 | 19.77 微秒 | 29.60 微秒 | 624.0 微秒 | 1.67 | 0.14 |
| Answer63496225_TheodorZoulias | .NET 4.8 | 10000 | 2000 |随机 | 372.1 微秒 | 11.52 微秒 | 16.15 微秒 | 373.8 微秒 | 0.99 | 0.07 |
| Answer63496768_CoolBots | .NET 4.8 | 10000 | 2000 |随机 | 594.5 微秒 | 25.13 微秒 | 36.03 微秒 | 593.1 微秒 | 1.57 | 0.12 |
| Answer63495657_NetMage | .NET 4.8 | 10000 | 2000 |随机 | 618.8 微秒 | 17.53 微秒 | 23.99 微秒 | 622.4 微秒 | 1.65 | 0.13 |
| Answer63496256_Vernou | .NET 4.8 | 10000 | 2000 |随机 | 645.6 微秒 | 27.28 微秒 | 39.99 微秒 | 632.2 微秒 | 1.71 | 0.16 |
| | | | | | | | | | | |
| RemoveUsingListCopy | .NET 核心 3.1 | 10000 | 2000 |随机 | 391.5 微秒 | 10.39 微秒 | 15.55 微秒 | 391.6 微秒 | 1.00 | 0.00 |
| RemoveUsingRemoveAt | .NET 核心 3.1 | 10000 | 2000 |随机 | 1,402.2 微秒 | 44.20 微秒 | 64.80 微秒 | 1,407.6 微秒 | 3.59 | 0.21 |
| RemoveUsingMoveStrategy | .NET 核心 3.1 | 10000 | 2000 |随机 | 557.9 微秒 | 19.73 微秒 | 27.00 微秒 | 557.2 微秒 | 1.43 | 0.10 |
| Answer63496225_TheodorZoulias | .NET 核心 3.1 | 10000 | 2000 |随机 | 424.3 微秒 | 20.90 微秒 | 29.30 微秒 | 424.2 微秒 | 1.09 | 0.09 |
| Answer63496768_CoolBots | .NET 核心 3.1 | 10000 | 2000 |随机 | 535.0 微秒 | 19.37 微秒 | 27.16 微秒 | 537.1 微秒 | 1.37 | 0.08 |
| Answer63495657_NetMage | .NET 核心 3.1 | 10000 | 2000 |随机 | 557.7 微秒 | 18.73 微秒 | 25.63 微秒 | 550.0 微秒 | 1.43 | 0.09 |
| Answer63496256_Vernou | .NET 核心 3.1 | 10000 | 2000 |随机 | 554.2 微秒 | 13.82 微秒 | 18.45 微秒 | 554.0 微秒 | 1.42 | 0.07 |
| | | | | | | | | | | |
| RemoveUsingListCopy | .NET 4.8 | 10000 | 2000 |中 | 221.6 微秒 | 7.25 微秒 | 10.63 微秒 | 222.5 微秒 | 1.00 | 0.00 |
| RemoveUsingRemoveAt | .NET 4.8 | 10000 | 2000 |中 | 1,195.3 微秒 | 20.01 微秒 | 28.69 微秒 | 1,187.7 微秒 | 5.42 | 0.30 |
| RemoveUsingMoveStrategy | .NET 4.8 | 10000 | 2000 |中 | 405.0 微秒 | 13.65 微秒 | 19.14 微秒 | 410.7 微秒 | 1.83 | 0.10 |
| Answer63496225_TheodorZoulias | .NET 4.8 | 10000 | 2000 |中 | 206.3 微秒 | 8.62 微秒 | 12.09 微秒 | 204.9 微秒 | 0.94 | 0.08 |
| Answer63496768_CoolBots | .NET 4.8 | 10000 | 2000 |中 | 427.5 微秒 | 15.56 微秒 | 22.81 微秒 | 435.4 微秒 | 1.93 | 0.13 |
| Answer63495657_NetMage | .NET 4.8 | 10000 | 2000 |中 | 405.4 微秒 | 13.80 微秒 | 19.35 微秒 | 403.8 微秒 | 1.84 | 0.11 |
| Answer63496256_Vernou | .NET 4.8 | 10000 | 2000 |中 | 413.9 微秒 | 15.26 微秒 | 20.89 微秒 | 419.8 微秒 | 1.87 | 0.12 |
| | | | | | | | | | | |
| RemoveUsingListCopy | .NET 核心 3.1 | 10000 | 2000 |中 | 235.2 微秒 | 10.73 微秒 | 15.73 微秒 | 236.2 微秒 | 1.00 | 0.00 |
| RemoveUsingRemoveAt | .NET 核心 3.1 | 10000 | 2000 |中 | 1,345.6 微秒 | 32.07 微秒 | 43.90 微秒 | 1,352.7 微秒 | 5.77 | 0.41 |
| RemoveUsingMoveStrategy | .NET 核心 3.1 | 10000 | 2000 |中 | 324.0 微秒 | 4.92 微秒 | 7.05 微秒 | 326.6 微秒 | 1.39 | 0.09 |
| Answer63496225_TheodorZoulias | .NET 核心 3.1 | 10000 | 2000 |中 | 262.9 微秒 | 6.18 微秒 | 9.06 微秒 | 265.4 微秒 | 1.12 | 0.08 |
| Answer63496768_CoolBots | .NET 核心 3.1 | 10000 | 2000 |中 | 333.6 微秒 | 10.14 微秒 | 13.87 微秒 | 340.9 微秒 | 1.43 | 0.11 |
| Answer63495657_NetMage | .NET 核心 3.1 | 10000 | 2000 |中 | 313.5 微秒 | 9.05 微秒 | 12.69 微秒 | 310.5 微秒 | 1.34 | 0.11 |
| Answer63496256_Vernou | .NET 核心 3.1 | 10000 | 2000 |中 | 332.3 微秒 | 6.70 微秒 | 8.95 微秒 | 331.9 微秒 | 1.43 | 0.09 |
| | | | | | | | | | | |
| RemoveUsingListCopy | .NET 4.8 | 10000000 | 2000 |随机 | 253,977.1 微秒 | 2,721.70 微秒 | 3,989.43 微秒 | 253,809.0 微秒 | 1.00 | 0.00 |
| RemoveUsingRemoveAt | .NET 4.8 | 10000000 | 2000 |随机 | 5,191,083.4 微秒 | 13,200.66 微秒 | 18,931.99 微秒 | 5,187,162.3 微秒 | 20.43 | 0.34 |
| RemoveUsingMoveStrategy | .NET 4.8 | 10000000 | 2000 |随机 | 65,365.4 微秒 | 422.41 微秒 | 592.16 微秒 | 65,307.3 微秒 | 0.26 | 0.00 |
| Answer63496225_TheodorZoulias | .NET 4.8 | 10000000 | 2000 |随机 | 240,584.4 微秒 | 3,687.89 微秒 | 5,048.03 微秒 | 244,336.1 微秒 | 0.95 | 0.02 |
| Answer63496768_CoolBots | .NET 4.8 | 10000000 | 2000 |随机 | 54,168.4 微秒 | 1,001.37 微秒 | 1,436.14 微秒 | 53,390.3 微秒 | 0.21 | 0.01 |
| Answer63495657_NetMage | .NET 4.8 | 10000000 | 2000 |随机 | 72,501.4 微秒 | 452.46 微秒 | 634.29 微秒 | 72,161.2 微秒 | 0.29 | 0.00 |
| Answer63496256_Vernou | .NET 4.8 | 10000000 | 2000 |随机 | 5,814.0 微秒 | 89.71 微秒 | 128.67 微秒 | 5,825.3 微秒 | 0.02 | 0.00 |
| | | | | | | | | | | |
| RemoveUsingListCopy | .NET 核心 3.1 | 10000000 | 2000 |随机 | 239,784.0 微秒 | 2,721.35 微秒 | 3,902.88 微秒 | 241,125.5 微秒 | 1.00 | 0.00 |
| RemoveUsingRemoveAt | .NET 核心 3.1 | 10000000 | 2000 |随机 | 5,538,337.5 微秒 | 353,505.30 微秒 | 495,565.06 微秒 | 5,208,226.1 微秒 | 23.12 | 2.15 |
| RemoveUsingMoveStrategy | .NET 核心 3.1 | 10000000 | 2000 |随机 | 33,071.8 微秒 | 103.80 微秒 | 138.57 微秒 | 33,030.5 微秒 | 0.14 | 0.00 |
| Answer63496225_TheodorZoulias | .NET 核心 3.1 | 10000000 | 2000 |随机 | 240,825.5 微秒 | 851.49 微秒 | 1,248.11 微秒 | 240,520.9 微秒 | 1.00 | 0.02 |
| Answer63496768_CoolBots | .NET 核心 3.1 | 10000000 | 2000 |随机 | 26,265.0 微秒 | 90.76 微秒 | 124.23 微秒 | 26,253.0 微秒 | 0.11 | 0.00 |
| Answer63495657_NetMage | .NET 核心 3.1 | 10000000 | 2000 |随机 | 48,670.6 微秒 | 581.51 微秒 | 833.99 微秒 | 48,303.0 微秒 | 0.20 | 0.00 |
| Answer63496256_Vernou | .NET 核心 3.1 | 10000000 | 2000 |随机 | 5,905.5 微秒 | 96.27 微秒 | 131.78 微秒 | 5,915.1 微秒 | 0.02 | 0.00 |
| | | | | | | | | | | |
| RemoveUsingListCopy | .NET 4.8 | 10000000 | 2000 |中 | 153,776.2 微秒 | 2,454.90 微秒 | 3,674.38 微秒 | 152,872.0 微秒 | 1.00 | 0.00 |
| RemoveUsingRemoveAt | .NET 4.8 | 10000000 | 2000 |中 | 5,245,952.0 微秒 | 13,845.58 微秒 | 20,294.67 微秒 | 5,252,922.4 微秒 | 34.10 | 0.81 |
| RemoveUsingMoveStrategy | .NET 4.8 | 10000000 | 2000 |中 | 33,233.6 微秒 | 110.33 微秒 | 158.24 微秒 | 33,217.3 微秒 | 0.22 | 0.01 |
| Answer63496225_TheodorZoulias | .NET 4.8 | 10000000 | 2000 |中 | 128,949.8 微秒 | 560.72 微秒 | 804.17 微秒 | 128,724.9 微秒 | 0.84 | 0.02 |
| Answer63496768_CoolBots | .NET 4.8 | 10000000 | 2000 |中 | 48,965.1 微秒 | 70.75 微秒 | 94.45 微秒 | 48,957.3 微秒 | 0.32 | 0.01 |
| Answer63495657_NetMage | .NET 4.8 | 10000000 | 2000 |中 | 32,641.5 微秒 | 66.85 微秒 | 91.51 微秒 | 32,610.0 微秒 | 0.21 | 0.01 |
| Answer63496256_Vernou | .NET 4.8 | 10000000 | 2000 |中 | 2,982.2 微秒 | 29.47 微秒 | 41.31 微秒 | 2,961.9 微秒 | 0.02 | 0.00 |
| | | | | | | | | | | |
| RemoveUsingListCopy | .NET 核心 3.1 | 10000000 | 2000 |中 | 144,208.7 微秒 | 2,035.88 微秒 | 2,984.16 微秒 | 142,693.2 微秒 | 1.00 | 0.00 |
| RemoveUsingRemoveAt | .NET 核心 3.1 | 10000000 | 2000 |中 | 5,235,957.7 微秒 | 13,674.19 微秒 | 20,043.46 微秒 | 5,241,536.1 微秒 | 36.32 | 0.78 |
| RemoveUsingMoveStrategy | .NET 核心 3.1 | 10000000 | 2000 |中 | 16,547.3 微秒 | 72.72 微秒 | 101.95 微秒 | 16,520.7 微秒 | 0.11 | 0.00 |
| Answer63496225_TheodorZoulias | .NET 核心 3.1 | 10000000 | 2000 |中 | 137,218.2 微秒 | 716.45 微秒 | 980.69 微秒 | 137,027.0 微秒 | 0.95 | 0.02 |
| Answer63496768_CoolBots | .NET 核心 3.1 | 10000000 | 2000 |中 | 23,728.5 微秒 | 79.84 微秒 | 111.93 微秒 | 23,689.9 微秒 | 0.16 | 0.00 |
| Answer63495657_NetMage | .NET 核心 3.1 | 10000000 | 2000 |中 | 17,298.3 微秒 | 216.46 微秒 | 310.44 微秒 | 17,165.5 微秒 | 0.12 | 0.00 |
| Answer63496256_Vernou | .NET 核心 3.1 | 10000000 | 2000 |中 | 2,999.7 微秒 | 85.78 微秒 | 123.03 微秒 | 2,957.1 微秒 | 0.02 | 0.00 |
[剪辑]
基准代码
要查看各种实现,向下滚动三分之一并查找带有[Benchmark()] 修饰的方法。
这需要BenchmarkDotNet package。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BenchmarkDotNet.Attributes;
namespace SO63495264
{
public class Benchmarks
{
public enum RemovalLocation
{
Random,
Beginning,
Middle,
End
}
[Params(10_000, 10_000_000)]
public int DataListSize
{
get; set;
}
[Params(2_000)]
public int RemovalListSize
{
get; set;
}
//[ParamsAllValues()]
[Params(RemovalLocation.Random, RemovalLocation.Middle)]
public RemovalLocation RemovalListLocation
{
get; set;
}
public List<int> DataList
{
get;
set;
}
public IReadOnlyList<int> RemovalList
{
get;
set;
}
[GlobalSetup()]
public void GlobalSetup()
{
IEnumerable<int> removalIndices;
if (RemovalListLocation == RemovalLocation.Random)
{
// Pass a constant seed so the same indices get generated for every run
Random random = new Random(12345);
removalIndices = Enumerable.Range(0, RemovalListSize)
.Select(i => random.Next(DataListSize));
}
else
{
int removalSegmentOffset = RemovalListLocation switch {
RemovalLocation.Beginning => 0,
RemovalLocation.Middle => (DataListSize - RemovalListSize) / 2,
RemovalLocation.End => DataListSize - RemovalListSize,
_ => throw new Exception($"Unexpected {nameof(RemovalLocation)} enumeration value {RemovalListLocation}.")
};
removalIndices = Enumerable.Range(removalSegmentOffset, RemovalListSize);
}
// For efficiency, create a single List<int> to be reused by all iterations for a given set of parameters
DataList = new List<int>(DataListSize);
RemovalList = removalIndices.ToList().AsReadOnly();
}
[IterationSetup()]
public void IterationSetup()
{
// Each iteration could modify DataList, so repopulate its elements for the
// next iteration. DataList is either new or has been Clear()ed by IterationCleanup().
for (int i = 0; i < DataListSize; i++)
DataList.Add(i);
}
[IterationCleanup()]
public void IterationCleanup()
{
DataList.Clear();
}
[GlobalCleanup()]
public void GlobalCleanup()
{
// Force collection of the List<> for the current set of parameters
int generation = GC.GetGeneration(DataList);
DataList = null;
GC.Collect(generation, GCCollectionMode.Forced, true);
}
[Benchmark(Baseline = true)]
public List<int> RemoveUsingListCopy()
{
HashSet<int> removalSet = RemovalList.ToHashSet();
List<int> newList = new List<int>(DataList.Count - removalSet.Count);
for (int index = 0; index < DataList.Count; index++)
if (!removalSet.Contains(index))
newList.Add(DataList[index]);
return newList;
}
// Based on Stack Overflow question 63495264
// https://stackoverflow.com/q/63495264/150605
[Benchmark()]
public List<int> RemoveUsingRemoveAt()
{
// The collection of indices to remove must be in decreasing order
// with no duplicates; all are assumed to be in-range for DataList
foreach (int index in RemovalList.Distinct().OrderByDescending(i => i))
DataList.RemoveAt(index);
return DataList;
}
// From Stack Overflow answer 63498191
// https://stackoverflow.com/a/63498191/150605
[Benchmark()]
public List<int> RemoveUsingMoveStrategy()
{
// The collection of indices to remove must be in increasing order
// with no duplicates; all are assumed to be in-range for DataList
int[] coreIndices = RemovalList.Distinct().OrderBy(i => i).ToArray();
List<(int startIndex, int count, int distance)> moveOperations
= new List<(int, int, int)>();
int candidateIndex;
for (int i = 0; i < coreIndices.Length; i = candidateIndex)
{
int currentIndex = coreIndices[i];
int elementCount = 1;
// Merge removal of consecutive indices into one operation
while ((candidateIndex = i + elementCount) < coreIndices.Length
&& coreIndices[candidateIndex] == currentIndex + elementCount
)
{
elementCount++;
}
int nextIndex = candidateIndex < coreIndices.Length
? coreIndices[candidateIndex]
: DataList.Count;
int moveCount = nextIndex - currentIndex - elementCount;
// Removing one or more elements from the end of the
// list will result in a no-op, 0-length move; omit it
if (moveCount > 0)
{
moveOperations.Add(
(
startIndex: currentIndex + elementCount,
count: moveCount,
distance: i + elementCount
)
);
}
}
// Perform the element moves
foreach ((int startIndex, int count, int distance) in moveOperations)
{
for (int offset = 0; offset < count; offset++)
{
int sourceIndex = startIndex + offset;
int targetIndex = sourceIndex - distance;
DataList[targetIndex] = DataList[sourceIndex];
}
}
// "Trim" the number of removed elements from the end of the list
DataList.RemoveRange(DataList.Count - coreIndices.Length, coreIndices.Length);
return DataList;
}
// Adapted from Stack Overflow answer 63496225 revision 4
// https://stackoverflow.com/revisions/63496225/4
[Benchmark()]
public List<int> Answer63496225_TheodorZoulias()
{
HashSet<int> indicesSet = new HashSet<int>(RemovalList);
int index = 0;
DataList.RemoveAll(_ => indicesSet.Contains(index++));
return DataList;
}
// Adapted from Stack Overflow answer 63496768 revision 2
// https://stackoverflow.com/revisions/63496768/2
[Benchmark()]
public List<int> Answer63496768_CoolBots()
{
List<int> sortedIndicies = RemovalList.Distinct().OrderBy(i => i).ToList();
int sourceStartIndex = 0;
int destStartIndex = 0;
int spanLength = 0;
int skipCount = 0;
// Copy items up to last index to be skipped
foreach (int skipIndex in sortedIndicies)
{
spanLength = skipIndex - sourceStartIndex;
destStartIndex = sourceStartIndex - skipCount;
for (int i = sourceStartIndex; i < sourceStartIndex + spanLength; i++)
{
DataList[destStartIndex] = DataList[i];
destStartIndex++;
}
sourceStartIndex = skipIndex + 1;
skipCount++;
}
// Copy remaining items (between last index to be skipped and end of list)
spanLength = DataList.Count - sourceStartIndex;
destStartIndex = sourceStartIndex - skipCount;
for (int i = sourceStartIndex; i < sourceStartIndex + spanLength; i++)
{
DataList[destStartIndex] = DataList[i];
destStartIndex++;
}
DataList.RemoveRange(destStartIndex, sortedIndicies.Count);
return DataList;
}
// Adapted from Stack Overflow answer 63495657 revision 6
// https://stackoverflow.com/revisions/63495657/6
[Benchmark()]
public List<int> Answer63495657_NetMage()
{
List<int> removeAtList = RemovalList.Distinct().OrderBy(i => i).ToList();
int srcCount = DataList.Count;
int ralCount = removeAtList.Count;
int removeAtIndice = 1;
int freeIndex = removeAtList[0];
int current = freeIndex + 1;
while (current < srcCount)
{
while (removeAtIndice < ralCount && current == removeAtList[removeAtIndice])
{
++current;
++removeAtIndice;
}
if (current < srcCount)
DataList[freeIndex++] = DataList[current++];
}
DataList.RemoveRange(freeIndex, srcCount - freeIndex);
return DataList;
}
// Adapted from Stack Overflow answer 63496256 revision 3
// https://stackoverflow.com/revisions/63496256/3
[Benchmark()]
public List<int> Answer63496256_Vernou()
{
List<int> indices = RemovalList.Distinct().OrderBy(i => i).ToList();
//Get the internal array
int[] largeArray = (int[]) typeof(List<int>)
.GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(DataList);
int current = 0;
int copyFrom = 0;
for (int i = 0; i < indices.Count; i++)
{
int copyTo = indices[i];
if (copyTo < copyFrom)
{
//In case the indice is duplicate,
//The item is already passed
continue;
}
int copyLength = copyTo - copyFrom;
Array.Copy(largeArray, copyFrom, largeArray, current, copyLength);
current += copyLength;
copyFrom = copyTo + 1;
}
//Resize the internal array
DataList.RemoveRange(DataList.Count - indices.Count, indices.Count);
return DataList;
}
}
}
启动器代码
RunBenchmark() 定义要运行的基准作业的类型以及运行时。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
namespace SO63495264
{
class Program
{
static void Main(string[] args)
{
if (args?.Length == 0)
{
string assemblyFilePath = Assembly.GetExecutingAssembly().Location;
string assemblyFileName = System.IO.Path.GetFileName(assemblyFilePath);
Console.WriteLine($"{assemblyFileName} {{ benchmark | test }}");
}
else if (string.Equals(args[0], "benchmark", StringComparison.OrdinalIgnoreCase))
RunBenchmark();
else if (string.Equals(args[0], "test", StringComparison.OrdinalIgnoreCase))
RunTest();
else
Console.WriteLine($"Unexpected parameter \"{args[0]}\"");
}
static void RunBenchmark()
{
Job baseJob = Job.MediumRun;
IConfig config = DefaultConfig.Instance;
foreach (Runtime runtime in new Runtime[] { CoreRuntime.Core31, ClrRuntime.Net48 })
{
config = config.AddJob(
baseJob.WithRuntime(runtime)
);
}
BenchmarkDotNet.Running.BenchmarkRunner.Run<Benchmarks>(config);
}
static void RunTest()
{
const int ListSize = 20;
const int MaxDisplayElements = 20;
IEnumerable<int> data = Enumerable.Range(0, ListSize);
IReadOnlyList<int> indices = new List<int>(
new int[] {
0, 1, // First two indices
ListSize / 4,
ListSize / 2, ListSize / 2 + 1, // Consecutive indices
ListSize / 4 * 3, ListSize / 4 * 3, // Duplicate indices
ListSize - 2, ListSize - 1 // Last two indices
}
).AsReadOnly();
// Discover and invoke the benchmark methods the same way BenchmarkDotNet would
Benchmarks benchmarks = new Benchmarks() {
RemovalList = indices
};
IEnumerable<MethodInfo> benchmarkMethods = benchmarks.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.Where(
method => method.CustomAttributes.Any(
attribute => attribute.AttributeType == typeof(BenchmarkAttribute)
)
);
// Call a known-good method to get the correct results for comparison
benchmarks.DataList = data.ToList();
List<int> controlList = benchmarks.RemoveUsingListCopy();
foreach (MethodInfo benchmarkMethod in benchmarkMethods)
{
List<int> inputList = data.ToList();
benchmarks.DataList = inputList;
Stopwatch watch = Stopwatch.StartNew();
List<int> outputList = (List<int>) benchmarkMethod.Invoke(benchmarks, Array.Empty<object>());
watch.Stop();
Console.WriteLine($"{benchmarkMethod.Name}():");
Console.WriteLine($"\tElements: {{ {string.Join(", ", outputList.Take(MaxDisplayElements))}{(outputList.Count > MaxDisplayElements ? ", ..." : "") } }}");
Console.WriteLine($"\t Count: {outputList.Count:N0}");
Console.WriteLine($"\tSequence: {(outputList.SequenceEqual(controlList) ? "E" : "*Not* e")}qual to control list");
Console.WriteLine($"\tDuration: {watch.Elapsed.TotalMilliseconds:N2} milliseconds");
Console.WriteLine($"\tReturned: {(object.ReferenceEquals(outputList, inputList) ? "Input list" : "New list")}");
}
}
}
}
为了在 .NET Framework (4.8) 上进行基准测试,我必须将以下属性添加到我的 .NET Core .csproj 项目文件中:
<TargetFrameworks>netcoreapp3.1;net48</TargetFrameworks>
<LangVersion>8.0</LangVersion>
41 个字符 备用!