在C#中用零初始化一个浮点数列表

5

我想要通过0.0初始化一个长度为N的对象列表。我的想法是这样的:

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

有更好(更有效)的方法来做这件事吗?


1
那种方法有什么低效的地方吗? - Tim Schmelter
我不知道,我想问问专业人士这是否是正确的方法。 初始化后,有没有办法将列表设置为零?(在使用列表后) - axcelenator
如何将列表设置为零? - axcelenator
1
0.0是float的默认值,因此您不需要执行任何其他初始化步骤。 - Brett Wolfington
如果您想要效率,请将其保留为 float[] - John Alexiou
3个回答

7

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

这里有一种不创建中间数组的方法:

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;
}

这样做可以将时间缩短到不到十分之一秒,相比下一个最快的方法要快一半。但是不要这样做。


@TimSchmelter 在堆上创建一个数组,将其初始化为所有零,将该数组复制到另一个数组,然后让垃圾收集器从堆中回收原始数组是相当低效的。内存分配相对昂贵。 - Matthew Watson
1
明白了,使用构造函数可能比较高效。尤其是当列表非常大或者需要在短时间内频繁创建列表以解决垃圾回收问题时。但是Enumerable.Repeat并不是一个好的替代方案,因为这样列表就无法知道大小,所以必须枚举序列并经常调整内部数组的大小。 - Tim Schmelter
1
Enumerable.Repeat()这个的确说得好!为了好玩,我正在做一些计时。:) - Matthew Watson
我的问题是关于Enumerable.Range的,但同样适用于Enumerable.Repeat - Tim Schmelter
1
@TimSchmelter 无论如何,你是对的 - 使用中间数组的 OP 代码似乎是最快的。我相应地更改了我的答案!我还采纳了你的 Enumerable.Range() 建议,但它似乎没有太大的差别。 - Matthew Watson
显示剩余2条评论

0
为什么不这样做:
var itemsWithZeros = new float[length];

0

这是什么问题

float[] A = new float[N];

或者

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

请注意,试图微观管理编译器并不是优化。从最干净的代码开始,让它做你想要的事情,然后让编译器自己处理。 编辑1 使用List<float>的解决方案会产生一个空列表,只有内部的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 的列表。 - Matthew Watson
哦,是的。收到。 - John Alexiou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接