优化字符串连接的聚合

时间:2008-12-09 23:12:49

标签: c# optimization linq-to-objects

更新 - 对于那些有思想心态的人,你可以假设,无论函数传递给它,Aggregate仍会产生正常结果,包括在优化的情况下。

我编写了这个程序,用逗号分隔从0到19999的长整数字串。

using System;
using System.Linq;
using System.Diagnostics;

namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            const int size = 20000;

            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();
            Enumerable.Range(0, size).Select(n => n.ToString()).Aggregate((a, b) => a + ", " + b);
            stopwatch.Stop();

            Console.WriteLine(stopwatch.ElapsedMilliseconds + "ms");
        }
    }
}

当我运行时,它说:

5116ms

超过五秒钟,太可怕了。当然这是因为每次循环都会复制整个字符串。

但是,如果评论中指出一个非常小的变化怎么办?

using System;
using System.Linq;
using System.Diagnostics;

namespace ConsoleApplication5
{
    using MakeAggregateGoFaster;  // <---- inserted this

    class Program
    {
        static void Main(string[] args)
        {
            const int size = 20000;

            Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();
            Enumerable.Range(0, size).Select(n => n.ToString()).Aggregate((a, b) => a + ", " + b);
            stopwatch.Stop();

            Console.WriteLine(stopwatch.ElapsedMilliseconds + "ms");
        }
    }
}

现在,当我运行它时,它说:

42ms

快了100多倍。

问题

MakeAggregateGoFaster命名空间中有什么?

更新2: Wrote up my answer here

5 个答案:

答案 0 :(得分:43)

为什么不使用其他形式的聚合?

Enumerable.Range(0, size ).Aggregate(new StringBuilder(),
        (a, b) => a.Append(", " + b.ToString()),
        (a) => a.Remove(0,2).ToString());

您可以为种子指定任何类型,在第一个lambda函数中执行所需的任何格式化或自定义调用,然后在第二个lambda函数中自定义输出类型。内置功能已经提供了您所需的灵活性。我的跑步从1444ms到6ms。

答案 1 :(得分:15)

您在命名空间中使用自己的扩展方法“覆盖”System.Linq.Aggregate MakeAggregateGoFaster。

也许专注于IEnumerable<string>并使用StringBuilder?

也许使用Expression<Func<string, string, string>>而不是Func<string, string, string>,这样它可以分析表达式树并编译一些使用StringBuilder而不是直接调用函数的代码?

猜猜。

答案 2 :(得分:5)

没有回答这个问题,但我认为这里的标准模式是使用StringBuilder或string.Join:

string.join(", ",Enumerable.Range(0, size).Select(n => n.ToString()).ToArray())

答案 3 :(得分:4)

我问这是否是一个谜题的原因是因为只要满足所述问题的字母,就允许谜题在不同程度上牺牲稳健性。考虑到这一点,这里有:

解决方案1(立即运行,问题无法验证):

public static string Aggregate(this IEnumerable<string> l, Func<string, string, string> f) {
     return "";
}

解决方案2(运行速度与问题一样快,但完全忽略代理):

public static string Aggregate(this IEnumerable<string> l, Func<string, string, string> f) {
    StringBuilder sb = new StringBuilder();
    foreach (string item in l)
        sb.Append(", ").Append(item);
    return sb.Remove(0,2).ToString();
}

答案 4 :(得分:3)

那么,这完全取决于MageAggregateGoFaster命名空间中的代码现在不是吗?

此命名空间不是.NET运行时的一部分,因此您已链接了一些自定义代码。

就我个人而言,我认为识别字符串连接或类似内容,构建列表或类似内容,然后分配一个大的StringBuilder并使用Append。

一个肮脏的解决方案是:

namespace MakeAggregateGoFaster
{
    public static class Extensions
    {
        public static String Aggregate(this IEnumerable<String> source, Func<String, String, String> fn)
        {
            StringBuilder sb = new StringBuilder();
            foreach (String s in source)
            {
                if (sb.Length > 0)
                    sb.Append(", ");
                sb.Append(s);
            }

            return sb.ToString();
        }
    }
}

脏,因为这段代码在执行您所说的程序体验时,根本不使用函数委托。但是,它会在我的计算机上将执行时间从大约2800ms降低到11ms,并且仍会产生相同的结果。

现在,下一次,也许你应该问一个真正的问题,而不仅仅是看看我是多么聪明类型的胸部殴打?