从阵列中删除,镜像(奇怪)行为
本文关键字:奇怪 行为 镜像 阵列 删除 | 更新日期: 2023-09-27 18:20:40
标题可能看起来有点奇怪,因为我不知道如何用一句话来描述它。
对于"算法"课程,我们必须对一些东西进行微观优化,其中之一是找出从数组中删除的工作原理。赋值是从数组中删除一些东西,并重新对齐内容,这样就没有间隙了,我认为这与std::vector::erase在c++中的工作方式非常相似。
因为我喜欢理解所有低级事物的想法,所以我走得更远,尝试用我的解决方案。这带来了一些奇怪的结果。
首先,这里有一个我使用的小代码:
class Test {
Stopwatch sw;
Obj[] objs;
public Test() {
this.sw = new Stopwatch();
this.objs = new Obj[1000000];
// Fill objs
for (int i = 0; i < objs.Length; i++) {
objs[i] = new Obj(i);
}
}
public void test() {
// Time deletion
sw.Restart();
deleteValue(400000, objs);
sw.Stop();
// Show timings
Console.WriteLine(sw.Elapsed);
}
// Delete function
// value is the to-search-for item in the list of objects
private static void deleteValue(int value, Obj[] list) {
for (int i = 0; i < list.Length; i++) {
if (list[i].Value == value) {
for (int j = i; j < list.Length - 1; j++) {
list[j] = list[j + 1];
//if (list[j + 1] == null) {
// break;
//}
}
list[list.Length - 1] = null;
break;
}
}
}
}
我只需要创建这个类并调用test()方法。我做了25次循环。
我的发现:
- 第一轮比其他24轮要长得多,我认为这是因为缓存,但我不确定
- 当我使用列表开头的值时,它必须在内存中移动比我使用末尾的值更多的项目,尽管它似乎仍然需要更少的时间
- 工作时间差别很大
- 当我启用注释的if时,即使我搜索的值几乎在列表的末尾,性能也会提高(10-20%)(这意味着if会关闭很多次,但实际上并没有用处)
我不知道为什么会发生这些事情,有人能解释(其中的一些)吗?也许如果有人看到这个专业人士,我在哪里可以找到更多信息来以最有效的方式做到这一点?
测试后编辑:
我做了一些测试,发现了一些有趣的结果。我在一个大小为一百万个项目的数组上运行测试,其中填充了一百万个对象。我运行了25次,并以毫秒为单位报告累积时间。我这样做了10次,并将其平均值作为最终值。
当我用上面描述的函数运行测试时,我得到的分数为:362,1
当我用dbc的答案运行它时,我得到的分数是:846,4
所以我的速度更快,但后来我开始用半空的空数组进行实验,事情开始变得奇怪。为了消除不可避免的nullPointerExceptions,我对if添加了一个额外的检查(认为这会破坏更多的性能),如下所示:
if (fromItem != null && fromItem.Value != value)
list[to++] = fromItem;
这似乎不仅有效,而且大大提高了性能!现在我得到的分数是:247,9
奇怪的是,分数似乎很低,但有时会飙升,这是我从中获得的平均值:94、26、966、36、632、95、47、35、109、439
因此,尽管做了额外的检查,但额外的评估似乎提高了我的表现。这怎么可能?
您正在使用Stopwatch
为方法计时。这将计算方法调用期间所用的时钟总时间,其中可能包括.Net初始JIT方法所需的时间、垃圾收集的中断或其他进程的系统负载导致的速度减慢。由于缓存未命中,来自这些源的噪声可能会主导噪声。
这个答案给出了一些建议,说明如何最大限度地减少垃圾收集或其他过程中的一些噪音。为了消除JIT干扰,您应该在不计时的情况下调用一次方法,或者在结果表的单独列中显示第一次调用所花费的时间,因为它会非常不同。您还可以考虑使用适当的探查器,该探查器将准确报告您的代码使用了多少时间,不包括来自其他线程或进程的"噪声"。
最后,我要注意的是,从数组中删除匹配项并向下移动其他项的算法使用了嵌套循环,这是不必要的,并且将在匹配索引之后访问数组中的项两次。标准算法如下:
public static void RemoveFromArray(this Obj[] array, int value)
{
int to = 0;
for (int from = 0; from < array.Length; from++)
{
var fromItem = array[from];
if (fromItem.Value != value)
array[to++] = fromItem;
}
for (; to < array.Length; to++)
{
array[to] = default(Obj);
}
}
然而,您可以在您的版本中使用Array.RemoveAt()
,而不是使用标准算法,因为(我相信)它在内部执行非托管代码中的删除操作。