首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ToList慢性能与foreach慢性能

ToList慢性能与foreach慢性能
EN

Stack Overflow用户
提问于 2012-12-26 00:12:49
回答 3查看 4.2K关注 0票数 2

我正在建立程序,使用3个表(工人,任务,TaskStep)的DataBase和我有一个方法,获得日期,并为任务的特定工人和特定的一天三个步骤建立报告。

数据库结构如下:

MySQL 5.2

Worker表列:

代码语言:javascript
复制
workerID(VARCHAR(45)),
name(VARCHAR(45)),
age(int),
...

Tasks表列:

代码语言:javascript
复制
TaskID(VARCHAR(45)),
description(VARCHAR(45)),
date(DATE),
...

TaskSteps表列:

代码语言:javascript
复制
TaskStepID(VARCHAR(45)),
description(VARCHAR(45)),
date(DATE),
...

不对任何表编制索引

问题是它非常非常慢!(~ 20秒)

代码如下:

代码语言:javascript
复制
using WorkerDailyReport = Dictionary<task, IEnumerable<taskStep>>;

private void Buildreport(DateTime date)
{
    var report = new WorkerDailyReport();    

    // Load from DB
    var sw = new Stopwatch();
    sw.Start();

    var startOfDay  = date.Date;
    var endOfDay    = startOfDay.AddDays(1);
    var db          = new WorkEntities();

    const string    workerID   = "80900855";

    IEnumerable<task> _tasks = db.task
                    .Where(ta =>    ta.date     >= startOfDay   &&
                                    ta.date     <  endOfDay     &&
                                    ta.workerID == workerID)
                    .ToList();

    sw.Stop();
    Console.WriteLine("Load From DB time - " + sw.Elapsed + 
                      ", Count - "           + _tasks.Count());   

    // Build the report
    sw.Restart();

    foreach (var t in _tasks)
    {
        var ts = db.taskStep.Where(s => s.taskID == task.taskID);

        report.Add(t, ts);
    }

    sw.Stop();
    Console.WriteLine("Build report time - " + sw.Elapsed);

    // Do somthing with the report
    foreach (var t in report)
    {
        sw.Restart();

        foreach (var subNode in t.Value)
        {
            // Do somthing..
        }

        Console.WriteLine("Do somthing time - " + sw.Elapsed + 
                          ", Count - " + t.Value.Count());
    }
}

如你所见,我在每个部分都放入了StopWatch,以检查是什么花了这么长时间,结果如下:

1)

如果我像上面那样运行代码:

控制台:

代码语言:javascript
复制
Load From DB time - 00:00:00.0013774, Count - 577

Build report time - 00:00:03.6305722

Do somthing time - 00:00:07.7573754, Count - 21

Do somthing time - 00:00:08.2811928, Count - 11

Do somthing time - 00:00:07.8715531, Count - 14

Do somthing time - 00:00:08.0430597, Count - 0

Do somthing time - 00:00:07.7867790, Count - 9

Do somthing time - 00:00:07.3485209, Count - 39

.........

内部的foreach运行大约需要7-9次!秒运行不超过40个记录。

2)

如果我只更改了一件事,那么当我从数据库加载工作任务时,在第一个查询之后添加.ToList()就会改变所有的事情。

控制台:

代码语言:javascript
复制
Load From DB time - 00:00:04.3568445, Count - 577

Build report time - 00:00:00.0018535

Do somthing time - 00:00:00.0191099, Count - 21

Do somthing time - 00:00:00.0144895, Count - 11

Do somthing time - 00:00:00.0150208, Count - 14

Do somthing time - 00:00:00.0179021, Count - 0

Do somthing time - 00:00:00.0151372, Count - 9

Do somthing time - 00:00:00.0155703, Count - 39

.........

现在从DataBase加载需要更多的时间,4+秒。但构建报告的时间约为1ms,每个内部的报告时间约为10ms

第一种方法是不可能的(577* ~8秒),第二种方法也很慢,我看不到y。

知道这里发生了什么吗?

1)为什么ToList()这么慢?

2)为什么没有ToList(),内部foreach和构建报告会变慢?

我怎么才能让它更快呢?

thnx。

EN

回答 3

Stack Overflow用户

发布于 2012-12-26 00:52:22

如果您不使用.ToList(),C#直到您第一次需要从数据库获取数据时才从数据库加载数据,这是因为实体框架中延迟加载。

而且在内部for-each循环的每一步中,你的程序都会从数据库中请求一个查询,这是非常慢的。

但是,当您使用.ToList()时,您会立即运行查询,并首先获取所有记录,这是很慢的。然后,在内部for-each循环中,您的程序将所有记录都保存在内存中。

对不起,我的英语说得不够流利。

票数 2
EN

Stack Overflow用户

发布于 2012-12-26 09:57:54

LINQ ToList()总是立即计算序列-在您的情况下,SQL查询数据库。

在第一个实例中,您得到了Load From DB time - 00:00:00.0013774, Count - 577 -这是很快的,因为您没有运行SQL query。但是,查询运行得晚了一点--这就是为什么你得到了Build report time - 00:00:03.6305722 (慢)。

在第二个实例中,添加ToList(),立即强制计算查询(执行SQL),这就是为什么您会得到以下时间:

  • Load From DB time - 00:00:04.3568445, Count - 577 -对数据库(slow)
  • Build report time - 00:00:00.0018535的SQL查询-对已在内存中的数据进行操作(fast)

有趣的是,返回577个条目的查询花费了超过3秒的时间。这可能是因为其中一个表上缺少索引而导致的。

请注意,如果您的表上没有索引,数据库系统需要执行full table scan来查找满足以下条件的所有项:

代码语言:javascript
复制
.Where(ta => ta.date >= startOfDay &&
             ta.date < endOfDay &&
             ta.workerID == workerID)

随着Tasks表中项目数的不断增加,您的查询将花费越来越长的时间。

因此,我强烈建议在Tasks.dateTasks.workerId列上创建索引。这应该会缩短初始查询时间(假设您的数据库连接速度很快,即您没有连接到部署在海洋上的数据库)。

顺便说一句,不要在所有表列上创建索引(只在查询条件中使用的表列上)。这可能会减慢插入操作并增加数据库大小。

不幸的是,我不能给你更多关于Do somthing time ...的建议,因为你没有提供代码。但如果你采用同样的建议,我相信你也会得到一些改进。

票数 0
EN

Stack Overflow用户

发布于 2012-12-26 13:56:33

为了提高性能,您应该使用一个查询从所有表中获取数据:

代码语言:javascript
复制
var _joinedTasks = db.task.Where(ta =>    ta.date     >= startOfDay   &&
                                    ta.date     <  endOfDay     &&
                                    ta.workerID == workerID)
                    .Join(db.taskStep, t => t.taskID, ts=>ts.taskID, (t, ts) => new {t, ts})
                    .GroupBy(g => g.t, v=>v.ts).AsEnumerable();

然后您可以将其添加到字典中:

代码语言:javascript
复制
var report = _joinedTasks.ToDictionary(g=>g.Key);

并随心所欲地使用此报告。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/14032208

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档