从数组中删除,镜像(奇怪)行为

时间:2014-09-03 12:54:32

标签: c# algorithm

标题可能看起来有些奇怪,因为我不知道如何用一句话来形容这一点。

对于课程算法,我们必须微观优化一些东西,一个是找出如何从数组中删除。赋值是从数组中删除某些内容并重新对齐内容以便没有间隙,我认为它与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

所以额外的评估似乎可以提高我的表现,尽管做了额外的检查。这怎么可能?

1 个答案:

答案 0 :(得分:2)

您正在使用Stopwatch为您的方法计时。这将计算方法调用期间的总 clock 时间,其中可能包括the time required for .Net to initially JIT your methodinterruptions for garbage collection或由其他进程的系统负载引起的速度减慢。由于缓存未命中,来自这些来源的噪声可能会主导噪声。

This answer提供了一些建议,说明如何最大限度地减少垃圾收集或其他进程中的一些噪音。要消除JIT噪声,您应该在没有计时的情况下调用方法一次 - 或者在结果表的单独列中显示第一次调用所花费的时间,因为它会如此不同。您可能还会考虑using a proper profiler,它会准确报告您的代码使用的时间,而不包括来自其他线程或进程的“噪音”。

最后,我会注意到你从数组中删除匹配项并将其他所有内容向下移动的算法使用嵌套循环,这不是必需的,并且会在匹配索引两次后访问数组中的项。标准算法如下所示:

    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()对您的版本进行实验,而不是使用标准算法,因为(我相信)内部它会在非托管代码中执行删除。