【问题标题】:How to populate/instantiate a C# array with a single value?如何使用单个值填充/实例化 C# 数组?
【发布时间】:2010-11-04 01:57:13
【问题描述】:

我知道 C# 中的值类型的实例化数组会自动填充 default value of the type(例如 false 表示 bool,0 表示 int 等)。

有没有办法用不是默认值的种子值自动填充数组?是在创建时还是之后的内置方法(如Java的Arrays.fill())?假设我想要一个默认为 true 的布尔数组,而不是 false。有没有内置的方法可以做到这一点,还是只需要使用 for 循环遍历数组?

 // Example pseudo-code:
 bool[] abValues = new[1000000];
 Array.Populate(abValues, true);

 // Currently how I'm handling this:
 bool[] abValues = new[1000000];
 for (int i = 0; i < 1000000; i++)
 {
     abValues[i] = true;
 }

必须遍历数组并将每个值“重置”为 true 似乎效率低下。有没有办法解决?也许通过翻转所有值?

在输入这个问题并考虑之后,我猜测默认值只是 C# 如何在幕后处理这些对象的内存分配的结果,所以我想这可能是不可能的。但我还是想确定一下!

【问题讨论】:

  • 我通常把名字从is_found改为is_still_hiding。虽然喜欢答案,但我需要在测试用例中为 int 数组做类似的事情。 (好问题)

标签: c# arrays default-value


【解决方案1】:
Enumerable.Repeat(true, 1000000).ToArray();

【讨论】:

  • 虽然这可行,但它并不是一个好的解决方案,因为它非常慢;实际上,它比使用 for 循环进行迭代要慢 4 倍。
  • 是的,没错,当我们考虑性能时,for 循环会更快
  • 要查看一些真正的基准,请查看C# Initialize Array
  • Enumerable.ToArray 不知道可枚举序列的大小,所以它必须猜测数组大小。这意味着每次超出ToArray 的缓冲区时,您都会获得数组分配,最后再加上一个用于修剪的分配。可枚举对象也涉及开销。
  • 请注意,对于引用类型,这将使​​用对同一单个对象的所有引用填充整个数组。如果这不是您想要的,并且您实际上想为每个数组项生成不同的对象,请参阅stackoverflow.com/a/44937053/23715
【解决方案2】:

不知道框架方法,但您可以编写一个快速帮助程序来为您完成。

public static void Populate<T>(this T[] arr, T value ) {
  for ( int i = 0; i < arr.Length;i++ ) {
    arr[i] = value;
  }
}

【讨论】:

  • 如果您不需要副本,请首选 ++i 而不是 i++。
  • i++ 复制 i,增加 i,并返回原始值。 ++i 只返回增加的值。因此 ++i 更快,这在我们在这里讨论的大型循环中可能很重要。
  • @RobertDailey:那是编译器优化,不再适用。我刚刚测试以验证我的信念:如果 i++ 的返回值不用于任何用途,那么编译器会自动为您编译为 ++i。此外,即使我确实使用了返回值,但性能差异是如此之小,我需要做一个极端情况来衡量它。即便如此,它也只导致了几个百分比的不同运行时间。
  • 我写了一个这样的扩展方法,但我让它返回原始数组以允许方法链接,例如:int[] arr = new int[16].Populate(-1);
  • void改成T[]就可以了var a = new int[100].Polupate(1)
【解决方案3】:

用一千个true 值创建一个新数组:

var items = Enumerable.Repeat<bool>(true, 1000).ToArray();  // Or ToList(), etc.

同样,你可以生成整数序列:

var items = Enumerable.Range(0, 1000).ToArray();  // 0..999

【讨论】:

  • 不错,但它仍然比 for 循环慢大约 4 倍
  • patjbs 理论上未来 Enumerable.Repeat 会执行得更快,因为它会使用并行实现。
  • @PetarPetrov 由于缓存抖动,这永远不会发生。我相当肯定,由于 CPU 缓存的性质,在单个阵列上并行执行工作无论如何都会变慢,因为计算机需要同步工作并适当地加载数据。
  • 有意悲观!= 缺乏过早优化。
【解决方案4】:

您可以在 .NET Core 2.0+ 和 .NET Standard 2.1+ 中使用 Array.Fill

【讨论】:

  • 太棒了!尽管请注意,这是一种相对较新的方法。它在 .NET Core 2.0+ 和 .NET Standard 2.1 中可用,但在任何 .NET Framework 版本中具体。 (它将在 .NET 5.0 中,它将 .NET Framework 和 .NET Core 融合在一起)。
  • 例如Array.Fill(myArray, myDefaultValue);
【解决方案5】:

对于大型数组或可变大小的数组,您可能应该使用:

Enumerable.Repeat(true, 1000000).ToArray();

对于小型数组,您可以使用 C# 3 中的集合初始化语法:

bool[] vals = new bool[]{ false, false, false, false, false, false, false };

集合初始化语法的好处是,您不必在每个槽中使用相同的值,您可以使用表达式或函数来初始化槽。另外,我认为您可以避免将数组插槽初始化为默认值的成本。所以,例如:

bool[] vals = new bool[]{ false, true, false, !(a ||b) && c, SomeBoolMethod() };

【讨论】:

  • 并初始化一个 float[] 数组:float[] AlzCalDefault = new float[] {(float) 0.5, 18, 500, 1, 0};
  • 可以在任何版本的 C# 中完成数组的 FWIW 初始化,例如:bool[] vals = { false, true, false, !(a || b) &amp;&amp; c, SomeBoolMethod() };
  • “避免将数组槽初始化为默认值的成本”是个好主意,但您和编译器都无法控制它——.NET 分配器分发已经归零的块记忆。
【解决方案6】:

如果您的数组太大,您应该使用 BitArray。它为每个 bool 使用 1 位而不是一个字节(如在 bool 数组中),您也可以使用位运算符将所有位设置为 true。或者只是初始化为true。如果你只需要做一次,它只会花费更多。

System.Collections.BitArray falses = new System.Collections.BitArray(100000, false);
System.Collections.BitArray trues = new System.Collections.BitArray(100000, true);

// Now both contain only true values.
falses.And(trues);

【讨论】:

    【解决方案7】:

    不幸的是,我认为没有直接的方法,但是我认为您可以为数组类编写一个扩展方法来做到这一点

    class Program
    {
        static void Main(string[] args)
        {
            int[] arr = new int[1000];
            arr.Init(10);
            Array.ForEach(arr, Console.WriteLine);
        }
    }
    
    public static class ArrayExtensions
    {
        public static void Init<T>(this T[] array, T defaultVaue)
        {
            if (array == null)
                return;
            for (int i = 0; i < array.Length; i++)
            {
                array[i] = defaultVaue;
            }
        }
    }
    

    【讨论】:

    • 我越喜欢这个扩展的想法。有时,预先简单的解决方案确实是最好的!
    【解决方案8】:

    经过一番谷歌搜索和阅读后,我发现了这个:

    bool[] bPrimes = new bool[1000000];
    bPrimes = Array.ConvertAll<bool, bool>(bPrimes, b=> b=true);
    

    这肯定更接近我正在寻找的东西。但我不确定这是否比在 for 循环中遍历原始数组并仅更改值更好。事实上,经过快速测试,它看起来慢了大约 5 倍。所以这不是一个很好的解决方案!

    【讨论】:

    • 这与您尝试执行的操作类似,除了它对数组中的每个元素进行函数调用。它在语法上可能看起来更好,但它做了更多的工作......
    • 是的,它看起来就像一个简单的 for 循环一样可以完成这项工作
    • 它创建一个新数组(不改变原始实例)。
    【解决方案9】:

    下面的代码结合了小副本的简单迭代和大副本的 Array.Copy

        public static void Populate<T>( T[] array, int startIndex, int count, T value ) {
            if ( array == null ) {
                throw new ArgumentNullException( "array" );
            }
            if ( (uint)startIndex >= array.Length ) {
                throw new ArgumentOutOfRangeException( "startIndex", "" );
            }
            if ( count < 0 || ( (uint)( startIndex + count ) > array.Length ) ) {
                throw new ArgumentOutOfRangeException( "count", "" );
            }
            const int Gap = 16;
            int i = startIndex;
    
            if ( count <= Gap * 2 ) {
                while ( count > 0 ) {
                    array[ i ] = value;
                    count--;
                    i++;
                }
                return;
            }
            int aval = Gap;
            count -= Gap;
    
            do {
                array[ i ] = value;
                i++;
                --aval;
            } while ( aval > 0 );
    
            aval = Gap;
            while ( true ) {
                Array.Copy( array, startIndex, array, i, aval );
                i += aval;
                count -= aval;
                aval *= 2;
                if ( count <= aval ) {
                    Array.Copy( array, startIndex, array, i, count );
                    break;
                }
            }
        }
    

    使用 int[] 数组的不同数组长度的基准是:

             2 Iterate:     1981 Populate:     2845
             4 Iterate:     2678 Populate:     3915
             8 Iterate:     4026 Populate:     6592
            16 Iterate:     6825 Populate:    10269
            32 Iterate:    16766 Populate:    18786
            64 Iterate:    27120 Populate:    35187
           128 Iterate:    49769 Populate:    53133
           256 Iterate:   100099 Populate:    71709
           512 Iterate:   184722 Populate:   107933
          1024 Iterate:   363727 Populate:   126389
          2048 Iterate:   710963 Populate:   220152
          4096 Iterate:  1419732 Populate:   291860
          8192 Iterate:  2854372 Populate:   685834
         16384 Iterate:  5703108 Populate:  1444185
         32768 Iterate: 11396999 Populate:  3210109
    

    第一列是数组大小,然后是使用简单迭代(@JaredPared 实现)进行复制的时间。这种方法的时间是在那之后。 这些是使用四个整数结构的数组的基准

             2 Iterate:     2473 Populate:     4589
             4 Iterate:     3966 Populate:     6081
             8 Iterate:     7326 Populate:     9050
            16 Iterate:    14606 Populate:    16114
            32 Iterate:    29170 Populate:    31473
            64 Iterate:    57117 Populate:    52079
           128 Iterate:   112927 Populate:    75503
           256 Iterate:   226767 Populate:   133276
           512 Iterate:   447424 Populate:   165912
          1024 Iterate:   890158 Populate:   367087
          2048 Iterate:  1786918 Populate:   492909
          4096 Iterate:  3570919 Populate:  1623861
          8192 Iterate:  7136554 Populate:  2857678
         16384 Iterate: 14258354 Populate:  6437759
         32768 Iterate: 28351852 Populate: 12843259
    

    【讨论】:

      【解决方案10】:

      并行实现怎么样

      public static void InitializeArray<T>(T[] array, T value)
      {
          var cores = Environment.ProcessorCount;
      
          ArraySegment<T>[] segments = new ArraySegment<T>[cores];
      
          var step = array.Length / cores;
          for (int i = 0; i < cores; i++)
          {
              segments[i] = new ArraySegment<T>(array, i * step, step);
          }
          var remaining = array.Length % cores;
          if (remaining != 0)
          {
              var lastIndex = segments.Length - 1;
              segments[lastIndex] = new ArraySegment<T>(array, lastIndex * step, array.Length - (lastIndex * step));
          }
      
          var initializers = new Task[cores];
          for (int i = 0; i < cores; i++)
          {
              var index = i;
              var t = new Task(() =>
              {
                  var s = segments[index];
                  for (int j = 0; j < s.Count; j++)
                  {
                      array[j + s.Offset] = value;
                  }
              });
              initializers[i] = t;
              t.Start();
          }
      
          Task.WaitAll(initializers);
      }
      

      当只初始化一个数组时,看不到这段代码的威力,但我认为你绝对应该忘记“纯”for。

      【讨论】:

      • 这存在错误共享问题的风险,其中不同的线程竞争 CPU 缓存行,因此与单线程实现相比会降低性能。是否会发生这种情况取决于每个线程内存块的大小和 CPU 架构。
      【解决方案11】:

      或者...您可以简单地使用反转逻辑。让false 表示true,反之亦然。

      代码示例

      // bool[] isVisible = Enumerable.Repeat(true, 1000000).ToArray();
      bool[] isHidden = new bool[1000000]; // Crazy-fast initialization!
      
      // if (isVisible.All(v => v))
      if (isHidden.All(v => !v))
      {
          // Do stuff!
      }
      

      【讨论】:

      • 有趣的解决方案,尽管这对于例如 int 来说会困难得多,因为你失去了 0。
      • 如果你在变量名上“反转逻辑”,这实际上是一个可行的选择:而不是bool[] isVisible 改为bool[] isHidden
      • 人们的反应似乎是某种有趣的 hack。这是一种常见的优化技术。如果你幸运的话,编译器会为你做这件事。
      【解决方案12】:

      这里提供的许多答案都归结为一次初始化数组一个元素的循环,它没有利用设计为一次对一块内存进行操作的 CPU 指令。

      .Net Standard 2.1(在撰写本文时为预览版)提供了Array.Fill(),它有助于在运行时库中实现高性能(尽管截至目前,.NET Core doesn't seem to 充分利用了这种可能性)。

      对于早期平台上的那些,当数组大小很大时,以下扩展方法在显着优于普通循环。当我的在线代码挑战解决方案超出分配的时间预算约 20% 时,我创建了它。它减少了大约 70% 的运行时间。在这种情况下,数组填充是在另一个循环中执行的。 BLOCK_SIZE 是由直觉而不是实验设置的。可以进行一些优化(例如,复制所有已设置为所需值的字节,而不是固定大小的块)。

      internal const int BLOCK_SIZE = 256;
      public static void Fill<T>(this T[] array, T value)
      {
          if (array.Length < 2 * BLOCK_SIZE)
          {
              for (int i = 0; i < array.Length; i++) array[i] = value;
          }
          else
          {
              int fullBlocks = array.Length / BLOCK_SIZE;
              // Initialize first block
              for (int j = 0; j < BLOCK_SIZE; j++) array[j] = value;
              // Copy successive full blocks
              for (int blk = 1; blk < fullBlocks; blk++)
              {
                  Array.Copy(array, 0, array, blk * BLOCK_SIZE, BLOCK_SIZE);
              }
      
              for (int rem = fullBlocks * BLOCK_SIZE; rem < array.Length; rem++)
              {
                  array[rem] = value;
              }
          }
      }
      

      【讨论】:

      • blk 增加BLOCK_SIZE 而不是相乘可能是值得的。当然,正确的答案是 .Net Core 优化Array.Fill&lt;T&gt;
      【解决方案13】:

      如果您使用 .NET Core、.NET Standard >= 2.1,或者依赖 System.Memory 包,您还可以使用 Span&lt;T&gt;.Fill() 方法:

      var valueToFill = 165;
      var data = new int[100];
      
      data.AsSpan().Fill(valueToFill);
      
      // print array content
      for (int i = 0; i < data.Length; i++)
      {
          Console.WriteLine(data[i]);
      }
      

      https://dotnetfiddle.net/UsJ9bu

      【讨论】:

        【解决方案14】:

        只是一个基准:

        BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18363.997 (1909/November2018Update/19H2)
        Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores
        .NET Core SDK=3.1.302
          [Host]        : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
          .NET Core 3.1 : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
        
        Job=.NET Core 3.1  Runtime=.NET Core 3.1
        
        |           Method |     Mean |     Error |    StdDev |
        |----------------- |---------:|----------:|----------:|
        | EnumerableRepeat | 2.311 us | 0.0228 us | 0.0213 us |
        |  NewArrayForEach | 2.007 us | 0.0392 us | 0.0348 us |
        |        ArrayFill | 2.426 us | 0.0103 us | 0.0092 us |
        
            [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.NetCoreApp31)]
            public class InitializeArrayBenchmark {
                const int ArrayLength = 1600;
        
                [Benchmark]
                public double[] EnumerableRepeat() {
                    return Enumerable.Repeat(double.PositiveInfinity, ArrayLength).ToArray();
                }
        
                [Benchmark]
                public double[] NewArrayForEach() {
                    var array = new double[ArrayLength];
        
                    for (int i = 0; i < array.Length; i++) {
                        array[i] = double.PositiveInfinity;
                    }
        
                    return array;
                }
        
                [Benchmark]
                public double[] ArrayFill() {
                    var array = new double[ArrayLength];
                    Array.Fill(array, double.PositiveInfinity);
        
                    return array;
                }
            }
        

        【讨论】:

          【解决方案15】:

          .NET Core 2.0 及更高版本支持Array.Fill() 方法。

          这是一个示例代码。

          var arr = new int[10];
          int defaultValue = 2;
          Array.Fill(arr,defaultValue);
          

          它还有一个用于填充索引范围的重载方法。更多详情请见here

          【讨论】:

            【解决方案16】:

            这也有效...但可能没有必要

             bool[] abValues = new bool[1000];
             abValues = abValues.Select( n => n = true ).ToArray<bool>();
            

            【讨论】:

              【解决方案17】:

              没有办法将数组中的所有元素设置为单个操作,除非该值是元素类型的默认值。

              例如,如果它是一个整数数组,您可以通过一次操作将它们全部设置为零,如下所示: Array.Clear(...)

              【讨论】:

                【解决方案18】:

                这是微软放弃的我们框架用户的另一个版本。它的速度是 Array.Clear 的 4 倍,比 Panos Theof's solutionEric J'sPetar Petrov's parallel one 快​​ - 对于大型阵列,速度最高是两倍。

                首先我想向您展示函数的祖先,因为这样可以更容易地理解代码。在性能方面,这与 Panos Theof 的代码相当,而且对于某些可能已经足够的东西:

                public static void Fill<T> (T[] array, int count, T value, int threshold = 32)
                {
                    if (threshold <= 0)
                        throw new ArgumentException("threshold");
                
                    int current_size = 0, keep_looping_up_to = Math.Min(count, threshold);
                
                    while (current_size < keep_looping_up_to)
                        array[current_size++] = value;
                
                    for (int at_least_half = (count + 1) >> 1; current_size < at_least_half; current_size <<= 1)
                        Array.Copy(array, 0, array, current_size, current_size);
                
                    Array.Copy(array, 0, array, current_size, count - current_size);
                }
                

                如您所见,这是基于已初始化部分的重复加倍。这是简单而高效的,但它与现代内存架构相冲突。因此诞生了一个版本,它只使用加倍来创建一个缓存友好的种子块,然后在目标区域上迭代地爆破:

                const int ARRAY_COPY_THRESHOLD = 32;  // 16 ... 64 work equally well for all tested constellations
                const int L1_CACHE_SIZE = 1 << 15;
                
                public static void Fill<T> (T[] array, int count, T value, int element_size)
                {
                    int current_size = 0, keep_looping_up_to = Math.Min(count, ARRAY_COPY_THRESHOLD);
                
                    while (current_size < keep_looping_up_to)
                        array[current_size++] = value;
                
                    int block_size = L1_CACHE_SIZE / element_size / 2;
                    int keep_doubling_up_to = Math.Min(block_size, count >> 1);
                
                    for ( ; current_size < keep_doubling_up_to; current_size <<= 1)
                        Array.Copy(array, 0, array, current_size, current_size);
                
                    for (int enough = count - block_size; current_size < enough; current_size += block_size)
                        Array.Copy(array, 0, array, current_size, block_size);
                
                    Array.Copy(array, 0, array, current_size, count - current_size);
                }
                

                注意:早期的代码需要(count + 1) &gt;&gt; 1 作为加倍循环的限制,以确保最终的复制操作有足够的素材来覆盖剩下的所有内容。如果改用count &gt;&gt; 1,奇数计数就不会出现这种情况。对于当前版本,这无关紧要,因为线性复制循环会弥补任何不足。

                必须将数组单元格的大小作为参数传递,因为 - 令人难以置信 - 泛型不允许使用 sizeof,除非它们使用的约束 (unmanaged) 在未来。错误的估计没什么大不了,但如果值准确,则性能最好,原因如下:

                • 低估元素大小会导致块大小超过 L1 缓存的一半,从而增加复制源数据被从 L1 逐出并不得不从较慢的缓存级别重新获取的可能性。

                • 高估元素大小会导致 CPU 的 L1 缓存利用率不足,这意味着线性块复制循环的执行频率高于最佳利用率。因此,产生的固定循环/调用开销超出了严格要求。

                这是我的代码与Array.Clear 和前面提到的其他三个解决方案的基准测试。时间用于填充给定大小的整数数组 (Int32[])。为了减少缓存变幻莫测等引起的变化,每个测试都执行了两次,背靠背,并且为第二次执行计时。

                array size   Array.Clear      Eric J.   Panos Theof  Petar Petrov   Darth Gizka
                -------------------------------------------------------------------------------
                     1000:       0,7 µs        0,2 µs        0,2 µs        6,8 µs       0,2 µs 
                    10000:       8,0 µs        1,4 µs        1,2 µs        7,8 µs       0,9 µs 
                   100000:      72,4 µs       12,4 µs        8,2 µs       33,6 µs       7,5 µs 
                  1000000:     652,9 µs      135,8 µs      101,6 µs      197,7 µs      71,6 µs 
                 10000000:    7182,6 µs     4174,9 µs     5193,3 µs     3691,5 µs    1658,1 µs 
                100000000:   67142,3 µs    44853,3 µs    51372,5 µs    35195,5 µs   16585,1 µs 
                

                如果这段代码的性能不够,那么一个有希望的途径是并行化线性复制循环(所有线程使用相同的源块),或者我们的老朋友 P/Invoke。

                注意:块的清除和填充通常由运行时例程完成,这些例程使用 MMX/SSE 指令和诸如此类的分支到高度专业化的代码,因此在任何体面的环境中,人们只需调用相应的道德等价物 std::memset 并放心的专业表现水平。 IOW,库函数Array.Clear 应该让我们所有的手卷版本都尘埃落定。相反的事实表明事情真的很不正常。首先必须推出自己的Fill&lt;&gt; 也是如此,因为它仍然只在核心和标准中,但不在框架中。 .NET 已经存在将近 20 年了,我们仍然需要左右 P/Invoke 来获取最基本的东西,或者自己动手......

                【讨论】:

                • FWIW, C memset 不适用于宽于一字节的模式。如果您使用Buffer.BlockCopy 而不是Array.Copy,C# 将为您提供快速 SIMD 副本...但是它会为任何聚合类型抛出异常,BlockCopy 仅允许用于原始类型。如果使用 BlockCopy,还要注意偏移量和长度参数与 Array.Copy 的单位不同。
                【解决方案19】:

                如果您打算只设置数组中的几个值,但想在大多数情况下获取(自定义)默认值,您可以尝试以下操作:

                public class SparseArray<T>
                {
                    private Dictionary<int, T> values = new Dictionary<int, T>();
                
                    private T defaultValue;
                
                    public SparseArray(T defaultValue)
                    {
                        this.defaultValue = defaultValue;
                    }
                
                    public T this [int index]
                    {
                      set { values[index] = value; }
                      get { return values.ContainsKey(index) ? values[index] ? defaultValue; }
                    }
                }
                

                您可能需要实现其他接口以使其有用,例如array 本身的接口。

                【讨论】:

                  【解决方案20】:

                  我意识到我参加聚会迟到了,但我有个主意。编写一个包装器,该包装器具有与包装值之间的转换运算符,以便它可以用作包装类型的替身。这实际上是受到@l33t 听起来很愚蠢的回答的启发。

                  首先(来自 C++)我意识到在 C# 中,构造数组元素时不会调用默认 ctor。相反——即使存在用户定义的默认构造函数! -- 所有数组元素都是零初始化的。这确实让我感到惊讶。

                  因此,仅提供具有所需值的默认 ctor 的包装类将适用于 C++ 中的数组,但不适用于 C#。一种解决方法是让包装器类型在转换时将 0 映射到所需的种子值。这样,出于所有实际目的,零初始化值似乎是用种子初始化的:

                  public struct MyBool
                  {
                      private bool _invertedValue;
                  
                      public MyBool(bool b) 
                      {   
                          _invertedValue = !b;
                      }
                  
                      public static implicit operator MyBool(bool b)
                      {
                          return new MyBool(b);
                      }
                  
                      public static implicit operator bool(MyBool mb)
                      {
                          return !mb._invertedValue;
                      }
                  
                  }
                  
                  static void Main(string[] args)
                  {
                          MyBool mb = false; // should expose false.
                          Console.Out.WriteLine("false init gives false: " 
                                                + !mb);
                  
                          MyBool[] fakeBoolArray = new MyBool[100];
                  
                          Console.Out.WriteLine("Default array elems are true: " 
                                                + fakeBoolArray.All(b => b) );
                  
                          fakeBoolArray[21] = false;
                          Console.Out.WriteLine("Assigning false worked: " 
                                                + !fakeBoolArray[21]);
                  
                          fakeBoolArray[21] = true;
                          // Should define ToString() on a MyBool,
                          // hence the !! to force bool
                          Console.Out.WriteLine("Assigning true again worked: " 
                                                + !!fakeBoolArray[21]);
                  }
                  

                  此模式适用于所有值类型。例如,如果需要使用 4 进行初始化等,可以将整数映射为 0 到 4。

                  我很想像在 C++ 中那样制作它的模板,提供种子值作为模板参数,但我知道这在 C# 中是不可能的。还是我错过了什么? (当然在 C++ 中映射是完全没有必要的,因为可以提供一个默认的 ctor 来调用数组元素。)

                  FWIW,这是一个 C++ 等价物:https://ideone.com/wG8yEh

                  【讨论】:

                    【解决方案21】:

                    如果您可以反转您的逻辑,您可以使用Array.Clear() 方法将布尔数组设置为false。

                            int upperLimit = 21;
                            double optimizeMe = Math.Sqrt(upperLimit);
                    
                            bool[] seiveContainer = new bool[upperLimit];
                            Array.Clear(seiveContainer, 0, upperLimit);
                    

                    【讨论】:

                      【解决方案22】:

                      我有点惊讶没有人制作非常简单但超快的 SIMD 版本:

                        public static void PopulateSimd<T>(T[] array, T value) where T : struct
                        {
                           var vector = new Vector<T>(value);
                           var i = 0;
                           var s = Vector<T>.Count;
                           var l = array.Length & ~(s-1);
                           for (; i < l; i += s) vector.CopyTo(array, i);
                           for (; i < array.Length; i++) array[i] = value;
                        }
                      

                      基准:(数字是针对 Framework 4.8,但 Core3.1 在统计上是相同的)

                      |方法 | N |平均值 |错误 |标准差 |比率 |比率SD | |----------- |-------- |---------------:|----------- ----:|--------------:|------:|--------:| |达斯吉兹卡 | 10 | 25.975 纳秒 | 1.2430 纳秒 | 0.1924 纳秒 | 1.00 | 0.00 | |西姆德 | 10 | 3.438 纳秒 | 0.4427 纳秒 | 0.0685 纳秒 | 0.13 | 0.00 | | | | | | | | | |达斯吉兹卡 | 100 | 81.155 纳秒 | 3.8287 纳秒 | 0.2099 纳秒 | 1.00 | 0.00 | |西姆德 | 100 | 12.178 纳秒 | 0.4547 纳秒 | 0.0704 纳秒 | 0.15 | 0.00 | | | | | | | | | |达斯吉兹卡 | 1000 | 201.138 纳秒 | 8.9769 纳秒 | 1.3892 纳秒 | 1.00 | 0.00 | |西姆德 | 1000 | 100.397 纳秒 | 4.0965 纳秒 | 0.6339 纳秒 | 0.50 | 0.00 | | | | | | | | | |达斯吉兹卡 | 10000 | 1,292.660 纳秒 | 38.4965 纳秒 | 5.9574 纳秒 | 1.00 | 0.00 | |西姆德 | 10000 | 1,272.819 纳秒 | 68.5148 纳秒 | 10.6027 纳秒 | 0.98 | 0.01 | | | | | | | | | |达斯吉兹卡 | 100000 | 16,156.106 纳秒 | 366.1133 纳秒 | 56.6564 纳秒 | 1.00 | 0.00 | |西姆德 | 100000 | 17,627.879 纳秒 | 1,589.7423 纳秒 | 246.0144 纳秒 | 1.09 | 0.02 | | | | | | | | | |达斯吉兹卡 | 1000000 | 176,625.870 纳秒 | 32,235.9957 纳秒 | 1,766.9637 纳秒 | 1.00 | 0.00 | |西姆德 | 1000000 | 186,812.920 纳秒 | 18,069.1517 纳秒 | 2,796.2212 纳秒 | 1.07 | 0.01 |

                      可以看出,它在

                      【讨论】:

                      【解决方案23】:

                      关于这个(重复?)问题还有更多答案:What is the equivalent of memset in C#?

                      有人对替代方案进行了基准测试(他们包括一个不安全的版本,但他们没有尝试memset):http://techmikael.blogspot.co.uk/2009/12/filling-array-with-default-value.html

                      【讨论】:

                        【解决方案24】:

                        这是System.Collections.BitArray 的另一种方法,它具有这样的构造函数。

                        bool[] result = new BitArray(1000000, true).Cast<bool>().ToArray();
                        

                        bool[] result = new bool[1000000];
                        new BitArray(1000000, true).CopyTo(result, 0);
                        

                        【讨论】:

                          【解决方案25】:

                          在你创建数组的地方创建一个私有类,并为其设置一个 getter 和 setter。除非您需要数组中的每个位置都是唯一的,例如随机,否则使用 int?作为一个数组,然后 get 如果位置相等 null 填充该位置并返回新的随机值。

                          IsVisibleHandler
                          {
                          
                            private bool[] b = new bool[10000];
                          
                            public bool GetIsVisible(int x)
                            {
                            return !b[x]
                            }
                          
                            public void SetIsVisibleTrueAt(int x)
                            {
                            b[x] = false //!true
                            }
                          }
                          

                          或者使用

                          public void SetIsVisibleAt(int x, bool isTrue)
                          {
                          b[x] = !isTrue;
                          }
                          

                          作为二传手。

                          【讨论】:

                            【解决方案26】:
                            Boolean[] data = new Boolean[25];
                            
                            new Action<Boolean[]>((p) => { BitArray seed = new BitArray(p.Length, true); seed.CopyTo(p, 0); }).Invoke(data);
                            

                            【讨论】:

                            • 请使用更好的格式和一些解释性的词,以便其他人可以更好地理解您的解决方案。
                            • 您可以通过对目标数组进行分区并将种子复制到各个分区来使用它来提高初始化性能。这只是为了提供一个想法 - 这是我的第一篇文章,也是我最后一篇文章。
                            猜你喜欢
                            • 1970-01-01
                            • 2016-07-24
                            • 1970-01-01
                            • 2013-01-18
                            • 1970-01-01
                            • 1970-01-01
                            • 2013-10-29
                            • 2018-01-06
                            • 1970-01-01
                            相关资源
                            最近更新 更多