Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/324.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# [优化此]:减慢LINQ到对象查询_C#_Linq_Performance_Optimization_Linq To Objects - Fatal编程技术网

C# [优化此]:减慢LINQ到对象查询

C# [优化此]:减慢LINQ到对象查询,c#,linq,performance,optimization,linq-to-objects,C#,Linq,Performance,Optimization,Linq To Objects,我有一个问题困扰着我;它被封装为一个新的查询操作符,我制作了两个版本,试图看看哪一个性能更好。两者的表现都很糟糕 第一次尝试;陈述式风格 public static IEnumerable<IEnumerable<α>> Section<α>(this IEnumerable<α> source, int length) { return source.Any() ? source.Take(length).Cons(sou

我有一个问题困扰着我;它被封装为一个新的查询操作符,我制作了两个版本,试图看看哪一个性能更好。两者的表现都很糟糕

第一次尝试;陈述式风格

public static IEnumerable<IEnumerable<α>> Section<α>(this IEnumerable<α> source, int length)
{
    return source.Any()
        ? source.Take(length).Cons(source.Skip(length).Section(length))
        : Enumerable.Empty<IEnumerable<α>>();
}
公共静态IEnumerable部分(此IEnumerable源代码,int-length)
{
返回source.Any()
?源、取(长度)、反(源、跳(长度)、段(长度))
:Enumerable.Empty();
}
第二次尝试:命令式“收益回报”风格

公共静态IEnumerable部分(此IEnumerable源代码,int-length)
{
var fst=源。获取(长度);
var rst=源。跳过(长度);
收益率;
if(rst.Any())
foreach(第一节中的变截面(长度))
收益率回归段;
}
事实上,第二次尝试在可读性、合成性和速度方面都更差


有没有关于如何优化这个的线索?

只要有可能,我会尝试在操作符中只对源进行一次迭代。如果源代码类似于
Reverse()
操作符的结果,则调用
Any
Take
Skip
可能会导致很多糟糕的性能


操作员想做什么还不完全清楚,但如果您不需要多次阅读源代码就可以做到,这可能会很有帮助,尽管这在很大程度上取决于输入的内容。

如果我正确理解您的问题,您正在尝试构建一个惰性的枚举器实现,该枚举器将较大的项集合拆分为较小的可枚举项集合

例如,一个一百万个数字的序列可以分成“部分”,每个部分只产生100个数字,而你希望所有的数字都是惰性的,即在产生之前不要收集100个项目到一个列表中

首先,您的尝试将在集合上重复多次,这是不好的,因此会出现性能问题

如果你想建立一个纯懒的实现,你应该考虑以下问题:

  • 您只想在基础集合上迭代一次
  • 您应该返回重复使用基础枚举数的枚举数
  • 您需要处理返回的部分没有完全枚举(例如,调用代码只需要这100项中的前50项)

编辑:在讨论我过于简单的解决方案之前,这里有一些注意事项:

  • 您无法保存每个节以备以后使用,即无法执行以下操作:
    collection.Sequence(10).ToArray()
    以获取节数组
  • 不能对每个部分进行多次枚举,因为这样做会更改隐藏的底层数据结构
基本上:我的解决方案不是通用的。如果需要的话,您应该使用批处理的注释,我会犹豫是否将代码放入类库,它必须在需要的地方是本地的,或者重命名为明确警告您有关它的问题的内容


这是一个过于简单的实现,我很确定这里存在bug,所以您可能想看看如何为EdgeCase实现大量的单元测试:

using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication20
{
    class SectionEnumerable<T> : IEnumerable<T>
    {
        private readonly IEnumerator<T> _Enumerator;

        public SectionEnumerable(IEnumerator<T> enumerator, int sectionSize)
        {
            _Enumerator = enumerator;
            Left = sectionSize;
        }

        public IEnumerator<T> GetEnumerator()
        {
            while (Left > 0)
            {
                Left--;
                yield return _Enumerator.Current;
                if (Left > 0)
                    if (!_Enumerator.MoveNext())
                        break;
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public int Left { get; private set; }
    }

    static class SequenceExtensions
    {
        public static IEnumerable<IEnumerable<T>> Section<T>(this IEnumerable<T> collection, int sectionSize)
        {
            if (collection == null)
                throw new ArgumentNullException("collection");
            if (sectionSize < 1)
                throw new ArgumentOutOfRangeException("sectionSize");

            using (IEnumerator<T> enumerator = collection.GetEnumerator())
            {
                while (enumerator.MoveNext())
                {
                    SectionEnumerable<T> enumerable = new SectionEnumerable<T>(enumerator, sectionSize);
                    yield return enumerable;
                    for (int index = 0; index < enumerable.Left; index++)
                        if (!enumerator.MoveNext())
                            yield break;
                }
            }
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var sequence = Enumerable.Range(0, 100);
            var sections = sequence.Section(10);
            foreach (var section in sections)
            {
                Console.WriteLine(
                    String.Join(", ",
                    section.Take(5).ToArray().Select(i => i.ToString()).ToArray()));
            }
            Console.ReadLine();
        }
    }
}
您应该进行单元测试的内容:

  • 空输入集合不生成节
  • 一个元素数量刚好合适的集合只生成一个节
  • 包含多个节大小元素(即10、20、30等,节大小为5或10的元素数)的集合不会在所有预期的元素之后生成空节
  • 它实际上是惰性的,如果在前10个元素的部分上进行枚举,但只在第二个部分的前5个元素上进行枚举,则只在基础集合的前15个元素上进行枚举

这会更快吗?应该是这样,因为它只需要对源序列进行一次迭代

public static IEnumerable<IEnumerable<T>> Section<T>(
    this IEnumerable<T> source, int length)
{
    return source
        .Select((x, i) => new { Value = x, Group = i / length })
        .GroupBy(x => x.Group, y => y.Value);
}
公共静态IEnumerable节(
此IEnumerable源(整数长度)
{
返回源
.Select((x,i)=>new{Value=x,Group=i/length})
.GroupBy(x=>x.Group,y=>y.Value);
}

还有一种不使用linq的方法,比第二种方法快得多:

 public static IEnumerable<IEnumerable<a>> Section<a>(this IEnumerable<a> source, int length)
        {


            var enumerator = source.GetEnumerator();
            var continueLoop = true;
            do
            {
                var list = new List<a>();
                var index = 0;
                for (int i = 0; i < length; i++)
                {
                    if (enumerator.MoveNext())
                    {
                        list.Add(enumerator.Current);
                        index++;
                    }
                    else
                    {
                        continueLoop = false;
                        break;
                    }
                }
                if (list.Count > 0)
                {
                    yield return list;
                }
            } while (continueLoop);


        }
公共静态IEnumerable部分(此IEnumerable源代码,int-length)
{
var枚举器=source.GetEnumerator();
var continueLoop=true;
做
{
var list=新列表();
var指数=0;
for(int i=0;i0)
{
收益回报表;
}
}while(continueLoop);
}

我怀疑您遇到的问题与以下事实有关:枚举最终结果至少是一个O(n^2)操作,可能更糟;我脑子里还没想清楚

为什么呢?好吧,假设你有[1,2,3,4,5,6],你把它分解成你认为的{{1,2},{3,4},{5,6}

你不是这么做的。事实上,你已经把它分成{取前两个,取前两个并丢弃它们,然后取下下两个,取前两个并丢弃,然后取下两个并丢弃它们,然后取下第三个两个}

注意每一步是如何重新计算结果的?这是因为数组可能在调用枚举之间发生变化。LINQ的设计宗旨是让您随时了解最新的结果;您编写的查询
public static IEnumerable<IEnumerable<T>> Section<T>(
    this IEnumerable<T> source, int length)
{
    return source
        .Select((x, i) => new { Value = x, Group = i / length })
        .GroupBy(x => x.Group, y => y.Value);
}
 public static IEnumerable<IEnumerable<a>> Section<a>(this IEnumerable<a> source, int length)
        {


            var enumerator = source.GetEnumerator();
            var continueLoop = true;
            do
            {
                var list = new List<a>();
                var index = 0;
                for (int i = 0; i < length; i++)
                {
                    if (enumerator.MoveNext())
                    {
                        list.Add(enumerator.Current);
                        index++;
                    }
                    else
                    {
                        continueLoop = false;
                        break;
                    }
                }
                if (list.Count > 0)
                {
                    yield return list;
                }
            } while (continueLoop);


        }
public static IEnumerable<α> Take<α>(this IEnumerator<α> iterator, int count)
{
    for (var i = 0; i < count && iterator.MoveNext(); i++)
        yield return iterator.Current;
}

public static IEnumerable<IEnumerable<α>> Section<α>(this IEnumerator<α> iterator, int length)
{
    var sct = Enumerable.Empty<α>();
    do
    {
        sct = iterator.Take(length).ToArray();
        if (sct.Any())
            yield return sct;
    }
    while (sct.Any());
}
public static IEnumerable<IEnumerable<α>> Section<α>(this IEnumerable<α> source, int length)
{
    using (var iterator = source.GetEnumerator())
        foreach (var e in iterator.Section(length))
            yield return e;
}
public static class IEnumerableExtensions
{
    public static IEnumerable<List<T>> InSetsOf<T>(this IEnumerable<T> source, int max)
    {
        List<T> toReturn = new List<T>();
        foreach(var item in source)
        {
                toReturn.Add(item);
                if (toReturn.Count == max)
                {
                        yield return toReturn;
                        toReturn = new List<T>();
                }
        }
        if (toReturn.Any())
        {
                yield return toReturn;
        }
    }
}
[TestFixture]
public class When_asked_to_return_items_in_sets
{
    [Test]
    public void Should_return_the_correct_number_of_sets_if_the_input_contains_a_multiple_of_the_setSize()
    {
        List<string> input = "abcdefghij".Select(x => x.ToString()).ToList();
        var result = input.InSetsOf(5);
        result.Count().ShouldBeEqualTo(2);
        result.First().Count.ShouldBeEqualTo(5);
        result.Last().Count.ShouldBeEqualTo(5);
    }

    [Test]
    public void Should_separate_the_input_into_sets_of_size_requested()
    {
        List<string> input = "abcdefghijklm".Select(x => x.ToString()).ToList();
        var result = input.InSetsOf(5);
        result.Count().ShouldBeEqualTo(3);
        result.First().Count.ShouldBeEqualTo(5);
        result.Last().Count.ShouldBeEqualTo(3);
    }
}