【发布时间】:2012-11-24 18:13:02
【问题描述】:
编辑:我将添加一些基准测试结果。对于列表中的大约 1000 - 5000 个项目,IList 和 RemoveAt 优于 ISet 和 Remove,但这并不值得担心,因为差异很小。当收藏规模扩大到 10000 或更多时,真正的乐趣就开始了。我只发布那些数据
我昨晚在回复a question here,遇到了一个奇怪的情况。
先一组简单的方法:
static Random rnd = new Random();
public static int GetRandomIndex<T>(this ICollection<T> source)
{
return rnd.Next(source.Count);
}
public static T GetRandom<T>(this IList<T> source)
{
return source[source.GetRandomIndex()];
}
------------------------------------------ -------------------------------------------------- --------------------------------------
假设我从一个集合中随机删除 N 个项目。我会写这个函数:
public static void RemoveRandomly1<T>(this ISet<T> source, int countToRemove)
{
int countToRemain = source.Count - countToRemove;
var inList = source.ToList();
int i = 0;
while (source.Count > countToRemain)
{
source.Remove(inList.GetRandom());
i++;
}
}
或
public static void RemoveRandomly2<T>(this IList<T> source, int countToRemove)
{
int countToRemain = source.Count - countToRemove;
int j = 0;
while (source.Count > countToRemain)
{
source.RemoveAt(source.GetRandomIndex());
j++;
}
}
如您所见,第一个函数是为ISet 编写的,第二个函数是为普通IList 编写的。在第一个函数中,我从ISet 中按项目删除,并按IList 中的索引删除,我相信这两个都是O(1)。为什么第二个函数的性能比第一个差这么多,尤其是当列表变大时?
赔率(我的看法):
1) 在第一个函数中,ISet 被转换为IList(从IList 中获取随机项),而在第二个函数中没有执行这样的操作。
优势 IList。
2) 在第一个函数中调用GetRandomItem,而在第二个函数中,调用GetRandomIndex,这又少了一步。
虽然微不足道,但优势 IList。
3) 在第一个函数中,随机项是从一个单独的列表中获取的,因此获取的项可能已经从ISet 中删除。这导致在第一个函数的while 循环中进行更多迭代。在第二个函数中,随机索引是从正在迭代的源中获取的,因此永远不会重复迭代。 我已经对此进行了测试并验证了这一点。
i > j 总是,优势
IList。
我认为这种行为的原因是 List 在添加或删除项目时需要不断调整大小。但显然在其他一些测试中没有。我跑了:
public static void Remove1(this ISet<int> set)
{
int count = set.Count;
for (int i = 0; i < count; i++)
{
set.Remove(i + 1);
}
}
public static void Remove2(this IList<int> lst)
{
for (int i = lst.Count - 1; i >= 0; i--)
{
lst.RemoveAt(i);
}
}
发现第二个函数跑得更快了。
试验台:
var f = Enumerable.Range(1, 100000);
var s = new HashSet<int>(f);
var l = new List<int>(f);
Benchmark(() =>
{
//some examples...
s.RemoveRandomly1(2500);
l.RemoveRandomly2(2500);
s.Remove1();
l.Remove2();
}, 1);
public static void Benchmark(Action method, int iterations = 10000)
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < iterations; i++)
method();
sw.Stop();
MsgBox.ShowDialog(sw.Elapsed.TotalMilliseconds.ToString());
}
只是想知道这两种结构是怎么回事..谢谢..
结果:
var f = Enumerable.Range(1, 10000);
s.RemoveRandomly1(7500); => 5ms
l.RemoveRandomly2(7500); => 20ms
var f = Enumerable.Range(1, 100000);
s.RemoveRandomly1(7500); => 7ms
l.RemoveRandomly2(7500); => 275ms
var f = Enumerable.Range(1, 1000000);
s.RemoveRandomly1(75000); => 50ms
l.RemoveRandomly2(75000); => 925000ms
不过,对于大多数典型的需求,一个列表就可以了..!
【问题讨论】:
-
你没有量化你的结果。如果没有一些数字,我们不知道“更快”是什么意思。 (例如,您没有预热虚拟机,因此很难知道这是否是您观察到的行为的原因)
-
@KirkWoll +1,我会在短时间内添加几个。
-
@KirkWoll 我在问题中添加了一些结果..
标签: c# performance list collections hashset