收益率 - 内存优化

时间:2015-03-20 18:00:47

标签: c# memory memory-management iterator yield-return

还有一个关于yield return

的问题

所以我需要远程执行不同的SQL脚本。脚本在TFS中,因此我自动从TFS获取它们,并且该过程遍历读取其内容的所有文件并将内容发送到远程SQL服务器。

到目前为止,这个过程完美无瑕。但是现在一些脚本将包含bulk inserts,将脚本的大小增加到500,000 MB或更多。

所以我构建了代码“思考”,我在内存中读取文件的内容,但现在我有了第二个想法。

这就是我所拥有的(简化):

    public IEnumerable<SqlScriptSummary> Find(string scriptsPath)
    {
        if (!Directory.Exists(scriptsPath))
        {
            throw new DirectoryNotFoundException(scriptsPath);
        }

        var path = new DirectoryInfo(scriptsPath);

        return path.EnumerateFiles("*.sql", SearchOption.TopDirectoryOnly)
            .Select(x =>
            {
                var script = new SqlScriptSummary
                {
                    Name = x.Name,
                    FullName = x.FullName,
                    Content = File.ReadAllText(x.FullName, Encoding.Default)
                };

                return script;
            });
    }

....

    public void ExecuteScripts(string scriptsPath)
    {
        foreach (var script in Find(scriptsPath))
        {
            _scriptRunner.Run(script.Content);
        }
    }

我的理解是EnumerateFiles每次都会yield return每个文件,这就是让我“思考”的原因我一次只在内存中加载一个文件

可是...

一旦我正在迭代它们,在ExecuteScripts方法中, script foreach 变量会发生什么>超出范围后循环?这是处置?还是留在记忆中?

  • 如果它仍然在内存中意味着即使我使用迭代器并在内部使用yield return时我遍历所有这些它们仍然在内存中吗?所以最后只是使用 ToList 只是懒惰执行就是这样吗?

  • 如果script变量在超出范围时处理掉,那么我认为我会没事的

我如何重新设计代码以优化内存消耗,例如强制将脚本内容一次加载到内存中

其他问题:

  • 如何测试(单元/集成测试)我在内存中一次只加载一个脚本?

  • 如何测试(单元/集成测试)每个脚本是否已从内存中释放?

1 个答案:

答案 0 :(得分:5)

  

一旦我对它们进行迭代,在foreach循环超出范围后使用的脚本变量会发生什么?这是处置?还是留在记忆中?

如果您的意思是ExecuteScripts方法 - 除了SqlScriptSummary实施IDisposable之外,没有什么可以处理的,这似乎不太可能。但是,这里有两个不同的东西:

  • script循环后foreach变量超出范围,无法充当GC根
  • script变量引用的每个对象都有资格进行垃圾回收,而没有其他任何引用它...在下一次迭代中包括script

所以是的,基本上应该绝对没问题。您一次只能加载一个文件,而且我无法看到为什么一次只能在内存中存储多个文件的内容,就对象而言GC无法收集。 (GC本身很懒,所以一次不能在内存中完全一个脚本,但你不必担心那边事情,因为您的代码确保它不会一次保持对多个脚本的实时引用。)

您可以测试一次只加载一个脚本的方法是尝试使用大型脚本的大目录(实际上并没有做任何事情)。如果你可以处理的脚本多于你的内存,那你就没问题了:)