【问题标题】:How to initialize integer array in C# [duplicate]如何在 C# 中初始化整数数组 [重复]
【发布时间】:2012-12-08 11:15:15
【问题描述】:

可能重复:
c# Leaner way of initializing int array

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

    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)

【问题讨论】:

  • 您将value 设置为数组的所有字段?
  • 是的,他正在将每​​个元素设置为非默认 int 值——这可能是除了微优化之外最快的方法(毫无疑问,有人会展示一些微优化技术)
  • 不管怎么做,在C#中用非默认值初始化数组都是O(N)操作。
  • @PaulSullivan 您可以从有效的并行化中获得不平凡的改进。
  • 只有微优化是在for循环中++i

标签: c# .net


【解决方案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;
        }

【讨论】:

  • 不是最好的代码,但它表明了我的观点。总而言之,这是一个 50% 的改进,如果你这样做一次,不会有多大,但加起来。
【解决方案2】:

提高速度的一种方法是利用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;
}

【讨论】:

  • +1。不错的建议。您是否有机会比较 Copy 单独(没有 Parallel)带来的改进?
  • @AlexeiLevenkov 一些简单的测试使其接近相同。这需要很好地调整才能从中获得很多好处(例如,您需要有正确的批量大小)。
【解决方案3】:

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

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

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

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

Parallel.For(0, size, i => result[i] = value);

【讨论】:

  • 这不是并行化的有效方法。几乎肯定会更糟,因为你会失去记忆的局部性,而且工作单元太小了。如果你使用parallel.for,至少它会有更好的镜头。
  • @EDIT:你总是可以很容易地测试性能......那么你就不必猜测了。
  • 我只是对这两个版本进行了一些简单的测试,并行版本的时间大约延长了 5 倍,这与我的预期差不多。
【解决方案4】:

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

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

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

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

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

 var result = Enumerable.Repeat(value, size).ToArray();

【讨论】:

  • 这会有点效率较低,而不是效率更高
  • @Servy:这可能就是为什么 Alexei 在给出这个答案之前说“你的代码尽可能高效”。
  • @HonzaBrestan 好吧,我不同意这种说法。除此之外,这个问题要求一个更好的选择。如果你没有更好的选择,那就不要回答,而不是提供你知道更糟糕的东西。
  • 顺便说一句,这有像Enumerable.Range 这样的问题,由于ToArray 后面的加倍算法,你可能以数组结尾的问题几乎是必要的两倍,因为Enumerable.Repeat 产生未知大小.这是一个很好的例子,为什么 OP 的方式是最好的,但只有一个解释。
  • @Servy,您介意提供一个答案,以比 OP 的 for 循环更低的开销提供数组创建/初始化吗?我很乐意支持它...到目前为止,您建议并行初始化会更快,但在某种程度上效率较低,因为它需要每个元素进行更多操作...
【解决方案5】:

读取 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 只返回递增的值

【讨论】:

    【解决方案6】:
    Enumerable.Repeat(value, size).ToArray();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-22
      • 1970-01-01
      • 2014-06-10
      • 1970-01-01
      • 2016-06-12
      • 1970-01-01
      相关资源
      最近更新 更多