【问题标题】:Initiate a float list with zeros in C#在 C# 中使用零启动一个浮点列表
【发布时间】:2015-10-05 22:27:11
【问题描述】:

我想用零 (0.0) 启动 N 个对象的列表。我想过这样做:

var TempList = new List<float>(new float[(int)(N)]);

有没有更好(更有效)的方法来做到这一点?

【问题讨论】:

  • 这种方法有什么低效的地方?
  • 我不知道,我想问问专业人士这样是否正确。初始化之后,有没有办法将列表设置为零? (与列表一起使用后)
  • 如何将列表设置为零?
  • 0.0 是float 的默认值,因此您无需执行任何额外的初始化步骤。
  • 如果您想提高效率,请将其保留为float[]

标签: c# list initialization space-efficiency


【解决方案1】:

为什么不:

var itemsWithZeros = new float[length];

【讨论】:

    【解决方案2】:

    有什么问题

    float[] A = new float[N];
    

    List<float> A = new List<float>(N);
    

    请注意,尝试对编译器进行微观管理并不是优化。从做你想做的最干净的代码开始,让编译器做它的事情。

    编辑 1 List&lt;float&gt; 的解决方案生成一个空列表,仅在内部初始化 N 项。所以我们可以通过一些反射来欺骗它

        static void Main(string[] args)
        {
            int N=100;
    
            float[] array = new float[N];
    
            List<float> list=new List<float>(N);
    
            var size=typeof(List<float>).GetField("_size", BindingFlags.Instance|BindingFlags.NonPublic);
            size.SetValue(list, N);
    
            // Now list has 100 zero items
        }
    

    【讨论】:

    • 第二个不等价,因为它会生成一个带有Count == 0的列表
    • 哦,是的。得分。
    【解决方案3】:

    您当前的解决方案创建一个数组,其唯一目的是用零初始化一个列表,然后将该数组丢弃。这可能看起来效率不高。但是,正如我们将看到的,它实际上非常有效!

    这是一个不创建中间数组的方法:

    int n = 100;
    
    var list = new List<float>(n);
    
    for (int i = 0; i < n; ++i)
        list.Add(0f);
    

    或者,您可以使用Enumerable.Repeat() 提供0f "n" 次,如下所示:

    var list = new List<float>(n);
    list.AddRange(Enumerable.Repeat(0f, n));
    

    但这两种方法都比较慢!

    这是一个用于计时的小测试应用。

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    
    namespace Demo
    {
        public class Program
        {
            private static void Main()
            {
                var sw = new Stopwatch();
    
                int n = 1024*1024*16;
                int count = 10;
                int dummy = 0;
    
                for (int trial = 0; trial < 4; ++trial)
                {
                    sw.Restart();
    
                    for (int i = 0; i < count; ++i)
                        dummy += method1(n).Count;
    
                    Console.WriteLine("Enumerable.Repeat() took " + sw.Elapsed);
                    sw.Restart();
    
                    for (int i = 0; i < count; ++i)
                        dummy += method2(n).Count;
    
                    Console.WriteLine("list.Add() took " + sw.Elapsed);
                    sw.Restart();
    
                    for (int i = 0; i < count; ++i)
                        dummy += method3(n).Count;
    
                    Console.WriteLine("(new float[n]) took " + sw.Elapsed);
    
                    Console.WriteLine("\n");
                }
            }
    
            private static List<float> method1(int n)
            {
                var list = new List<float>(n);
                list.AddRange(Enumerable.Repeat(0f, n));
                return list;
            }
    
            private static List<float> method2(int n)
            {
                var list = new List<float>(n);
    
                for (int i = 0; i < n; ++i)
                    list.Add(0f);
    
                return list;
            }
    
            private static List<float> method3(int n)
            {
                return new List<float>(new float[n]);
            }
        }
    }
    

    这是我的 RELEASE 构建结果:

    Enumerable.Repeat() took 00:00:02.9508207
    list.Add() took 00:00:01.1986594
    (new float[n]) took 00:00:00.5318123
    

    事实证明,创建中间数组要快得多。但是,请注意,此测试代码存在缺陷,因为它没有考虑分配中间数组引起的垃圾收集开销(很难正确计时)。

    最后,有一种非常邪恶的方式,您可以使用反射来优化它。但这很脆弱,将来可能无法正常工作,并且应该永远在生产代码中使用。

    我在这里只是出于好奇:

    private static List<float> method4(int n)
    {
        var list = new List<float>(n);
        list.GetType().GetField("_size", BindingFlags.NonPublic | BindingFlags.Instance).SetValue(list, n);
        return list;
    }
    

    这样做可以将时间减少到不到十分之一秒,而下一个最快的方法需要半秒。但不要这样做。

    【讨论】:

    • 以这种方式创建数组并不是低效的:new float[1000]List&lt;T&gt; 构造函数将检查传递的IEnumerable&lt;T&gt; 是否可以强制转换为集合。然后它使用CopyToreferencesource.microsoft.com/#mscorlib/system/collections/…
    • @TimSchmelter 在堆上创建一个数组,将其初始化为全零,将该数组复制到另一个数组,然后让垃圾收集器从堆中回收原始数组是非常低效的。内存分配相对昂贵。
    • 点了,它可能比构造函数更有效。至少如果列表非常大或由于 GC 问题必须在短时间内经常创建。但是Enumerable.Repeat 不是一个好的选择,因为列表不知道大小,因此必须枚举序列,并且可能经常调整内部数组的大小。
    • 关于Enumerable.Repeat() 的好点!只是为了兴趣,我正在做一些时间安排。 :)
    • @TimSchmelter 事实证明你是对的——使用中间数组的 OP 代码似乎是最快的。我相应地改变了我的答案!我还采纳了您对 Enumerable.Range() 的建议,但差别不大。
    猜你喜欢
    • 2021-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-26
    • 1970-01-01
    • 2018-06-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多