【问题标题】:Optimizing a Recursive Function for Very Large Lists .Net为非常大的列表优化递归函数 .Net
【发布时间】:2009-09-24 00:19:52
【问题描述】:

我构建了一个应用程序,用于模拟公司每月可以在不同“模式”下生产的产品数量。此模拟用于帮助找到运行一个月的最佳模式系列,以最好地满足该月的预计销售预测。该应用程序一直运行良好,直到最近该工厂被修改为在其他模式下运行。现在可以在 16 种模式下运行。对于有 22 个工作日的一个月,这会产生 9,364,199,760 种可能的组合。这比过去仅产生 1,560,780 种可能组合的 8 种模式有所增加。运行此应用程序的 PC 较旧,无法处理在引发内存不足异常之前的计算次数。事实上,整个应用程序不能支持超过 15 种模式,因为它使用整数来跟踪模式的数量,并且超过了整数的上限。抛开这个问题,我需要尽我所能降低应用程序的内存利用率并优化它以尽可能高效地运行,即使它无法实现 16 种模式的既定目标。我正在考虑将数据写入磁盘而不是将列表存储在内存中,但在我承担这个开销之前,我想了解人们对该方法的看法,看看那里是否有任何优化空间。

编辑 根据少数人的建议,考虑一些更学术的东西,然后仅仅计算每个可能的答案,下面列出了如何选择最佳运行(模式组合)的简要说明。 目前,计算机确定工厂在该月的工作日数内可以运行的所有可能方式。例如,最多 2 个工作日的 3 种模式将导致 (1,1)、(1,2)、(1,3)、(2,2) 的组合(其中数字代表选择的模式), (2,3), (3,3) 对于每种模式,产品以不同的生产率生产,例如在模式 1 中,产品 x 可能以每小时 50 单位的速度生产,而产品 y 以每小时 30 单位的速度生产,而产品z 每小时生产 0 个单位。然后将每个组合乘以工作时间和生产率。选择产生与该月每种产品的预测值最接近的数字的运行。但是,由于工厂在某些月份没有达到产品的预测值,因此该算法会提高下个月产品的优先级,以确保该产品在年底达到预测值。由于仓库空间紧张,产品也不要过度生产很重要。

谢谢

private List<List<int>> _modeIterations = new List<List<int>>();

private void CalculateCombinations(int modes, int workDays, string combinationValues)
    {
        List<int> _tempList = new List<int>();

        if (modes == 1)
        {
            combinationValues += Convert.ToString(workDays);
            string[] _combinations = combinationValues.Split(',');

            foreach (string _number in _combinations)
            {
                _tempList.Add(Convert.ToInt32(_number));
            }
            _modeIterations.Add(_tempList);
        }
        else
        {
            for (int i = workDays + 1; --i >= 0; )
            {
                CalculateCombinations(modes - 1, workDays - i, combinationValues + i + ",");
            }
        }
    }

【问题讨论】:

  • 你为什么要一次生成它们?你不能生成其中的一些,处理它们然后扔掉吗?然后生成其他的,处理这些等等。我还想知道需要什么样的算法来生成数百万个组合才能通过蛮力找出最好的组合。肯定有一些更聪明的方法来解决这段代码帮助你解决的任何问题?
  • 我不明白这个问题:您是说您的工厂有 22 个工作日,并且在每个工作日它可以以不同的模式运行(具有不同的输出)。您想找到尽可能接近本月预计总产出的模式组合吗?您可以每天切换模式,还是每周仅切换一次,或其他任何方式?
  • 另外,你是怎么得出数字 1,560,780 和 9,364,199,760 的?
  • 当只有几百万需要考虑的时候,让 PC 插上一两分钟的时间来蛮力通过它似乎并没有那么糟糕,你是对的,这不再实用。也许我可以每个月分手,这样一次只查看几百万个组合,这是一个很好的建议。至于计算它的新算法,如果我能做到这一点,我就不会是现在的我:)。
  • 每天只有一种模式。 (模式 + 天数 - 1)! / ((天)!(modes-1)!

标签: c# .net optimization


【解决方案1】:

这种优化问题是困难,但非常充分研究。您可能应该阅读有关它的文献,而不是试图重新发明轮子。你要找的关键词是“运筹学”和“组合优化问题”。

在优化问题的研究中众所周知,当问题变大时,找到问题的最优解几乎总是在计算上不可行,正如您自己发现的那样。然而,找到保证在最优解的一定百分比范围内的解通常是可行的。您可能应该专注于寻找近似解决方案。毕竟,您的销售目标已经只是有根据的猜测,因此找到最佳解决方案已经是不可能的;你没有得到完整的信息。)

我会先阅读关于背包问题的维基百科页面:

http://en.wikipedia.org/wiki/Knapsack_problem

这就是“我有一大堆不同价值和不同重量的物品,我的背包可以携带 50 磅,在达到我的体重目标的同时,我可以携带的最大价值是多少?”

这不完全是您的问题,但显然它是相关的——您有一定数量的“价值”要最大化,并且有有限数量的插槽可以将该价值打包。如果您可以开始了解人们如何为背包问题找到接近最优的解决方案,您就可以将其应用于您的具体问题。

【讨论】:

  • 你说的完全正确;我需要重新审视这种方法。我会阅读你建议的领域。也许关于启发式的问题在我的未来。
【解决方案2】:

您可以在生成排列后立即对其进行处理,而不是先将它们全部收集到一个列表中:

public delegate void Processor(List<int> args);

private void CalculateCombinations(int modes, int workDays, string combinationValues, Processor processor)
{
    if (modes == 1)
    {
        List<int> _tempList = new List<int>();
        combinationValues += Convert.ToString(workDays);
        string[] _combinations = combinationValues.Split(',');

        foreach (string _number in _combinations)
        {
            _tempList.Add(Convert.ToInt32(_number));
        }
        processor.Invoke(_tempList);
    }
    else
    {
        for (int i = workDays + 1; --i >= 0; )
        {
            CalculateCombinations(modes - 1, workDays - i, combinationValues + i + ",", processor);
        }
    }
}

我在这里假设你目前的工作模式是这样的

CalculateCombinations(initial_value_1, initial_value_2, initial_value_3);

foreach( List<int> list in _modeIterations ) {

    ... process the list ...

}

使用直接处理方法,这将是

private void ProcessPermutation(List<int> args) 
{
    ... process ...
}

...其他地方...

CalculateCombinations(initial_value_1, initial_value_2, initial_value_3, ProcessPermutation);

我还建议您尽可能早地修剪搜索树;如果您已经知道,参数的某些组合永远不会产生可以处理的东西,那么您应该在生成过程中捕获它们,并尽可能避免递归。

在新版本的 C# 中,使用迭代器 (?) 函数生成组合可能有助于保留代码的原始结构。我还没有真正使用过这个功能(yield),所以我不能评论它。

【讨论】:

  • +1,绝对:如果可以在生成的那一刻判断当前组合是优于还是劣于另一个组合,您应该这样做并在存储之前丢弃(或删除)劣质组合它。或者,如果您想以 X 组合结束,只需查看当前组合是否不如您存储的当前顶部 X
  • +1,这很好地解决了我提出的问题。它确实解决了我面临的内存问题。但是,我没有将其标记为答案,因为正如 Eric 和 Jorge 下面所指出的,这种蛮力解决方案总是有限制的。就短期而言,这让我可以继续处理手头的问题,感谢您抽出宝贵时间。
【解决方案3】:

问题更多在于蛮力方法,而不是代码本身。蛮力可能是解决问题的唯一方法,但我对此表示怀疑。例如,国际象棋是蛮力无法解决的,但计算机可以很好地使用启发式算法来丢弃不太有希望的方法并专注于好的方法。也许你应该采取类似的方法。

另一方面,我们需要知道每个“模式”是如何评估的,以便提出任何启发式方法。在您的代码中,您只计算所有可能的组合,无论如何,如果模式上升到 32...即使您将其存储在磁盘上,这些组合也不会扩展。

【讨论】:

  • 这是一个有趣的方法,我更新了我的问题以反映模式的评估方式。
【解决方案4】:
if (modes == 1)
{
    List<int> _tempList = new List<int>();
    combinationValues += Convert.ToString(workDays);
    string[] _combinations = combinationValues.Split(',');

    foreach (string _number in _combinations)
    {
        _tempList.Add(Convert.ToInt32(_number));
    }
    processor.Invoke(_tempList);
}

这段代码中的所有内容都会一遍又一遍地执行,因此该代码中的任何行都不应在不释放内存的情况下使用内存。避免内存疯狂的最明显的地方是在处理时将combinationValues 写入磁盘(即使用FileStream,而不是string)。我认为一般来说,像你在这里做的那样进行字符串连接是不好的,因为每个连接都会导致记忆悲伤。至少使用一个字符串生成器(参见 back to basics ,它在 C 方面讨论了相同的问题)。不过,可能还有其他地方有问题。找出内存不足错误原因的最简单方法可能是使用memory profiler(来自download.microsoft.com 的Download Link)。

顺便说一句,我对这样的代码的倾向是拥有一个全局 List 对象,它是 Clear()ed,而不是有一个反复创建的临时对象。

【讨论】:

    【解决方案5】:

    我会用我自己的类替换 List 对象,该类使用预分配的数组来保存整数。我现在对此不太确定,但我相信 List 中的每个整数都被装箱,这意味着比使用简单的整数数组使用更多的内存。

    编辑:另一方面,我似乎弄错了:Which one is more efficient : List<int> or int[]

    【讨论】:

    • 不真实。 C# 中的泛型足够好,不会对值类型进行装箱。您可能会想到 .NET 1.1 ArrayList,或者您可能会想到 Java 泛型。 (至少它们在 1.4.2 附近是如何工作的)在这两种情况下,整数确实是装箱的。
    • 是的,如果您阅读我对帖子的编辑,您会发现我已经承认了这一点。
    猜你喜欢
    • 2010-10-20
    • 1970-01-01
    • 2017-07-23
    • 1970-01-01
    • 2017-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多