5

可能重复:
c# 更精简的初始化 int 数组的方法

基本上我想知道是否有比下面显示的更有效的代码

    private static int[] GetDefaultSeriesArray(int size, int value)
    {
        int[] result = new int[size];
        for (int i = 0; i < size; i++)
        {
            result[i] = value;
        }
        return result;
    }

其中大小可以从 10 到 150000 不等。对于小型阵列不是问题,但应该有更好的方法来完成上述操作。我正在使用 VS2010(.NET 4.0)

4

6 回答 6

8

C#/CLR 没有内置方法来用非默认值初始化数组。

如果您衡量每个项目的操作,您的代码将尽可能高效。

如果您并行初始化大块数组,您可以获得更快的初始化速度。由于多线程操作的成本很高,这种方法需要仔细调整。

通过分析您的需求并可能一起删除整个初始化,可以获得更好的结果。即,如果数组通常包含常量值,您可以实现某种 COW(写入时复制)方法,其中您的对象最初没有后备数组并且 simpy 返回常量值,在写入元素时它将创建(可能是部分)后备数组用于修改段。

更慢但更紧凑的代码(可能更容易阅读)将使用Enumerable.Repeat. 请注意,这ToArray将导致为大型数组分配大量内存(这也可能最终导致 LOH 上的分配) - Enumerable.Range 的内存消耗高?.

 var result = Enumerable.Repeat(value, size).ToArray();
于 2012-12-20T21:18:02.353 回答
4

提高速度的一种方法是利用Array.Copy. 它能够在较低级别上工作,在该级别上批量分配更大的内存部分。

通过批处理分配,您最终可以将数组从一个部分复制到其自身。

最重要的是,批次本身可以非常有效地并行化。

这是我的初始代码。在我的机器(只有两个内核)上,样本数组大小为 1000 万个项目,我得到了 15% 左右的加速。您需要调整批量大小(尝试保持页面大小的倍数以保持效率)以将其调整为您拥有的项目的大小。对于较小的数组,它最终将与您的代码几乎相同,因为它不会超过填充第一批,但在这些情况下它也不会(明显)更糟。

private const int batchSize = 1048576;
private static int[] GetDefaultSeriesArray2(int size, int value)
{

    int[] result = new int[size];

    //fill the first batch normally
    int end = Math.Min(batchSize, size);
    for (int i = 0; i < end; i++)
    {
        result[i] = value;
    }

    int numBatches = size / batchSize;

    Parallel.For(1, numBatches, batch =>
    {
        Array.Copy(result, 0, result, batch * batchSize, batchSize);
    });

    //handle partial leftover batch
    for (int i = numBatches * batchSize; i < size; i++)
    {
        result[i] = value;
    }

    return result;
}
于 2012-12-20T22:01:19.647 回答
1

另一种提高性能的方法是使用一种非常基本的技术:循环展开。

我编写了一些代码来初始化一个包含 2000 万个项目的数组,重复执行 100 次并计算平均值。如果不展开循环,这大约需要 44 毫秒。循环展开为 10,该过程在 23 毫秒内完成。

 private void Looper()
        {
            int repeats = 100;
            float avg = 0;

            ArrayList times = new ArrayList();

            for (int i = 0; i < repeats; i++)
                times.Add(Time()); 

            Console.WriteLine(GetAverage(times)); //44

            times.Clear();

            for (int i = 0; i < repeats; i++)            
                times.Add(TimeUnrolled()); 

            Console.WriteLine(GetAverage(times)); //22

        }

 private float GetAverage(ArrayList times)
        {
            long total = 0;
            foreach (var item in times)
            {
                total += (long)item;
            }

            return total / times.Count;
        }

        private long Time()
        {
            Stopwatch sw = new Stopwatch();
            int size = 20000000;
            int[] result = new int[size];
            sw.Start();


            for (int i = 0; i < size; i++)
            {
                result[i] = 5;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            return sw.ElapsedMilliseconds;
        }

        private long TimeUnrolled()
        {
            Stopwatch sw = new Stopwatch();
            int size = 20000000;
            int[] result = new int[size];
            sw.Start();


            for (int i = 0; i < size; i += 10)
            {
                result[i] = 5;
                result[i + 1] = 5;
                result[i + 2] = 5;
                result[i + 3] = 5;
                result[i + 4] = 5;
                result[i + 5] = 5;
                result[i + 6] = 5;
                result[i + 7] = 5;
                result[i + 8] = 5;
                result[i + 9] = 5;
            }
            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);
            return sw.ElapsedMilliseconds;
        }
于 2012-12-20T22:21:36.333 回答
0

阅读 Enumerable.Repeat 比循环的 ops 标准慢 20 倍,我发现唯一可能提高其速度的是

private static int[] GetDefaultSeriesArray(int size, int value)
{
    int[] result = new int[size];
    for (int i = 0; i < size; ++i)
    {
        result[i] = value;
    }
    return result;
}

注意 i++ 更改为 ++i。i++ 复制 i,增加 i,并返回原始值。++i 只返回递增的值

于 2012-12-20T21:32:19.300 回答
0
Enumerable.Repeat(value, size).ToArray();
于 2012-12-20T21:18:07.537 回答
-2

正如有人已经提到的,您可以像这样利用并行处理:

int[] result = new int[size];
Parallel.ForEach(result, x => x = value);
return result;

抱歉,我没有时间对此进行性能测试(这台机器上没有安装 VS),但如果你能做到并分享结果,那就太好了。

编辑:根据评论,虽然我仍然认为它们在性能方面是等效的,但您可以尝试并行 for 循环:

Parallel.For(0, size, i => result[i] = value);
于 2012-12-20T21:28:08.660 回答