VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > 简明python教程 >
  • LINQ 性能分析(2)

where 子句中的条件,将比较整型字段 PageCount 改为比较字符串字段 Tit1e

var result = (from book in books
                where book.Title.StartsWith("l")
                select book).ToList();

按照同样方式修改其他的测试代码,并再次运行 20 次。其结果将如下表所示:

方法                  平均时间(毫秒)            最小时间(毫秒)            最大时间(毫秒)
foreach               144.3                       136                         177
for                   134.55                      125                         156
List<T>.FindAll       136.45                      131                         161
LINQ                  148.4                       136                         193

测试代码如下:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using LinqInAction.LinqBooks.Common;

static class Demo
{
    public static void Main()
    {
        var books = BooksForPerformance();

        Console.WriteLine("{0,-20}{1,-20}{2,-20}{3,-20}", "方法", "平均时间(毫秒)", "最小时间(毫秒)", "最大时间(毫秒)");

        var time = 20;
        var result = Test(Foreach, books, time);
        Console.WriteLine($"{"foreach",-22}{result.avg,-28}{result.min,-28}{result.max,-28}");

        result = Test(For, books, time);
        Console.WriteLine($"{"for",-22}{result.avg,-28}{result.min,-28}{result.max,-28}");

        result = Test(FindAll, books, time);
        Console.WriteLine($"{"List<T>.FindAll",-22}{result.avg,-28}{result.min,-28}{result.max,-28}");

        result = Test(Linq, books, time);
        Console.WriteLine($"{"LINQ",-22}{result.avg,-28}{result.min,-28}{result.max,-28}");



        Console.ReadKey();
    }

    private static List<Book> BooksForPerformance()
    {
        var rndBooks = new Random(123);
        var rndPublishers = new Random(123);
        var publisherCount = SampleData.Publishers.Count();

        var result = new List<Book>();
        for (int i = 0; i < 1000000; i++)
        {
            var publisher = SampleData.Publishers.Skip(rndPublishers.Next(publisherCount)).First();
            var pageCount = rndBooks.Next(1000);
            result.Add(new Book
            {
                Title = pageCount.ToString(),
                PageCount = pageCount,
                Publisher = publisher
            });
        }

        return result;
    }

    /// <summary>
    /// 第一种方法
    /// </summary>
    /// <returns></returns>
    static void Foreach(List<Book> books)
    {
        var result = new List<Book>();
        foreach (var book in books)
        {
            if (book.Title.StartsWith("l"))
            {
                result.Add(book);
            }
        }
    }

    /// <summary>
    /// 第二种方法
    /// </summary>
    static void For(List<Book> books)
    {
        var result = new List<Book>();
        for (int i = 0; i < books.Count; i++)
        {
            var book = books[i];
            if (book.Title.StartsWith("l"))
            {
                result.Add(book);
            }
        }
    }

    /// <summary>
    /// 第三种方法
    /// </summary>
    static void FindAll(List<Book> books)
    {
        var result = books.FindAll(book => book.Title.StartsWith("l"));
    }

    /// <summary>
    /// 第四种方法
    /// </summary>
    static void Linq(List<Book> books)
    {
        var result = (from book in books
                      where book.Title.StartsWith("l")
                      select book).ToList();
    }


    /// <summary>
    /// 测试
    /// </summary>
    /// <param name="action"></param>
    /// <param name="books"></param>
    /// <param name="time"></param>
    /// <returns></returns>
    static (double avg, long max, long min) Test(Action<List<Book>> action, List<Book> books, int time)
    {
        List<long> times = new List<long>();
        Stopwatch stopwatch = new Stopwatch();

        for (int i = 0; i < time; i++)
        {
            stopwatch.Start();
            action(books);
            stopwatch.Stop();
            times.Add(stopwatch.ElapsedMilliseconds);
            stopwatch.Reset();
        }

        return (times.Average(), times.Max(), times.Min());
    }
}

LINQ 的做法比前面例子中比较整型值的版本要多花费大概 5 倍的时间。这是因为字符串操作要比数值操作更加耗时。不过最有趣的则是,这一次 LINQ 的做法只比最快的做法慢一点点。两次比较的结果清楚地说明,LINQ 所带来的一些性能上额外的开销并不一定成为程序效率上的瓶颈。

不过为什么两个测试会有如此的差异呢?当我们把 where 子句中的比较条件从整型改变为字符串之后,实际上就相应地增加了每一段代码的执行时间。这段额外的时间将应用于所有的测试代码上,不过 LINQ 所带来的性能开销则始终维持在一个相对恒定的水平上。因此可以这样认为,查询中执行的操作越少,相对而言 LINQ 所带来的性能开销则越大。

这并没有什么值得惊讶的——凡事都有利弊,LINQ 也不会只带来好处。LINQ 需要一些额外的工作,例如创建对象和对垃圾收集器的更高依赖等。这些额外的工作让 LINQ 的执行效率极大地依赖于所要执行的查询。有些时候效率可能只会降低 5%,而有些时候则可能降低 500%。

结论就是,不要害怕使用 LINQ,不过使用时要多加小心。对于一些简单而又频繁执行的操作,或许传统的方法更适合一些。对于简单的过滤或搜索操作,我们可以仍使用 List<T>和数组内建的支持,例如 FindAllForEachFindConvertAll 和 TrueForAll 等。当然,在任何 LINQ 将会造成巨大性能影响的地方,我们均可使用传统的 foreach 或 for 循环代替。而对于那些不是很频繁执行的查询来说,你可以放心地使用 LINQ to Objects。对于那些不是对时间非常敏感的操作而言,执行时间是 60 毫秒还是 10 毫秒并不会给程序的运行带来什么显著差异。别忘了 LINQ 能够在源代码级别为你带来多么好的可读性和可维护性!

性能和简洁:鱼和熊掌不可兼得吗

刚刚我们看到,LINQ 似乎在兼顾代码的性能和代码的简洁清晰方面给我们出了一道难题。我们再来看一个示例程序,用来或者证明,或者推翻这个理论。这次的测试将进行分组操作。下面代码中的 LINQ 查询将图书按照出版社分组,并将分组后的结果按照出版社名称进行排序。

var result = from book in books
                group book by book.Publisher.Name
    into publisherBooks
                orderby publisherBooks.Key
                select publisherBooks;

若是不使用 LINQ,那么用传统的方法也能实现同样的功能:

var result = new SortedDictionary<string, List<Book>>();
foreach (var book in books)
{
    if (!result.TryGetValue(book.Publisher.Name, out var publisherBooks))
    {
        publisherBooks = new List<Book>();
        result[book.Publisher.Name] = publisherBooks;
    }
    publisherBooks.Add(book);
}

运行 20 次的结果:

方法                  平均时间(毫秒)            最小时间(毫秒)            最大时间(毫秒)
LINQ                  61.85                       46                          124
Foreach               421.45                      391                         505

测试代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using LinqInAction.LinqBooks.Common;

static class Demo
{
    public static void Main()
    {
        var books = BooksForPerformance();

        Console.WriteLine("{0,-20}{1,-20}{2,-20}{3,-20}", "方法", "平均时间(毫秒)", "最小时间(毫秒)", "最大时间(毫秒)");

        var time = 20;
        var result = Test(Linq, books, time);
        Console.WriteLine($"{"LINQ",-22}{result.avg,-28}{result.min,-28}{result.max,-28}");

        result = Test(Foreach, books, time);
        Console.WriteLine($"{"Foreach",-22}{result.avg,-28}{result.min,-28}{result.max,-28}");

        Console.ReadKey();
    }

    private static List<Book> BooksForPerformance()
    {
        var rndBooks = new Random(123);
        var rndPublishers = new Random(123);
        var publisherCount = SampleData.Publishers.Count();

        var result = new List<Book>();
        for (int i = 0; i < 1000000; i++)
        {
            var publisher = SampleData.Publishers.Skip(rndPublishers.Next(publisherCount)).First();
            var pageCount = rndBooks.Next(1000);
            result.Add(new Book
            {
                Title = pageCount.ToString(),
                PageCount = pageCount,
                Publisher = publisher
            });
        }

        return result;
    }

    /// <summary>
    /// 第一种方法
    /// </summary>
    /// <returns></returns>
    static void Linq(List<Book> books)
    {
        var result = (from book in books
                      group book by book.Publisher.Name
            into publisherBooks
                      orderby publisherBooks.Key
                      select publisherBooks).ToList();
    }

    /// <summary>
    /// 第二种方法
    /// </summary>
    static void Foreach(List<Book> books)
    {
        var result = new SortedDictionary<string, List<Book>>();
        foreach (var book in books)
        {
            if (!result.TryGetValue(book.Publisher.Name, out var publisherBooks))
            {
                publisherBooks = new List<Book>();
                result[book.Publisher.Name] = publisherBooks;
            }
            publisherBooks.Add(book);
        }
    }

    /// <summary>
    /// 测试
    /// </summary>
    /// <param name="action"></param>
    /// <param name="books"></param>
    /// <param name="time"></param>
    /// <returns></returns>
    static (double avg, long max, long min) Test(Action<List<Book>> action, List<Book> books, int time)
    {
        List<long> times = new List<long>();
        Stopwatch stopwatch = new Stopwatch();

        for (int i = 0; i < time; i++)
        {
            stopwatch.Start();
            action(books);
            stopwatch.Stop();
            times.Add(stopwatch.ElapsedMilliseconds);
            stopwatch.Reset();
        }

        return (times.Average(), times.Max(), times.Min());
    }
}

毫无疑问,传统方法的代码更长也更复杂。虽然并不是太难以理解,不过若是对功能有更进一步的需求,那么可以想象这段代码将会越来越长,越来越复杂。而 LINQ 的版本则能够始终保持简单!

上述两段代码中最主要的差别在于其使用了完全不同的两种理念。LINQ 版本使用的是声明式的方法,而传统的版本则通过一系列的命令实现。在 LINQ 出现之前,C# 中的代码都是命令式的,因为语言本身就是如此。命令式的代码详细地给出了执行某些操作所需要的完整步骤。而 LINQ 的声明式方法则仅仅描述了我们所期望得到的结果,对于具体的实现过程并不在意。与详细描述实现步骤不同的是,LINQ 代码则更像是对结果的直接定义。这才是二者最核心的不同之处!

前面曾经说过,你应该已经信服于 LINQ 所带来的种种便利。那么这个新的示例程序又将要证明些什么呢?答案就是,若测试一下这两种方法的执行效率,你会看到 LINQ 版本要快于传统的代码!

当然,你可能会对产生这样的结果存有疑惑,不过我们将把调查研究的工作留给你自己。这里我们要说的是:若你希望在传统代码中得到与 LINQ 同样的执行效率,可能需要继续编写更加复杂的代码。

从内存占用以及插入时间等角度考虑,SortedDictionary 是一个比较低效的数据结构。此外,我们还在每一次循环中使用了 TryGetValue。而 LINQ 运算符则能够更有效地处理
这类场景。当然,这个非 LINQ 版本的代码也存在着性能提升的空间,不过同时也会带来更高的复杂性。

原文链接:https://www.vinanysoft.com/c-sharp/linq-performance-analysis/


相关教程