【问题标题】:Switch inside loops impacting performance?切换内部循环会影响性能?
【发布时间】:2013-02-28 15:31:40
【问题描述】:

我处于循环数据并根据设置以特定方式对其进行格式化的场景中,我担心我认为最好的风格可能会妨碍性能。

代码基本模式如下

enum setting {single, multiple, foo, bar};
Data data = getData(Connection conn, int id);
setting blah = data.getSetting();
foreach (Item item in data)
{
   switch(blah)
   {
      case blah.single:
        processDataSingle(item blah);
        break;
      ...
   }
}

我担心数据中可能有数千甚至数万个项目。我想知道是否将开关置于循环中可能会重复评估它可能会导致一些严重的性能问题。我知道我可以将switch 放在循环之前,但是每个case 都包含它,这似乎不太可读,因为基本功能保持不变的情况不太明显。

【问题讨论】:

  • 你能发布完整的开关吗?如果我们知道代码的确切功能,我们将能够建议最佳解决方案
  • 你做过性能测试吗?制作2个解决方案,使用大量数据> 1000,并计时:)
  • 有时根据blah 拆分工作负载并(同时)处理现在统一的“分片”是有效的。虽然无法从发布的代码中分辨出来
  • 您是否分析过代码并确定这确实是您的瓶颈?
  • 这段代码没有意义。看起来这是一个完全落后的问题方法,但由于我们并不真正知道问题是什么(太抽象),所以没有什么可以做的......

标签: c# performance coding-style


【解决方案1】:

您可以设置一个委托/动作一次,然后每次在循环中调用它:

Data data = getData(Connection conn, int id);
setting blah = data.getSetting();
Action<Item> doThis;
switch (blah)
{
  case blah.single:
      doThis = i => processSingleData(i blah);
      break;
  ...
}
foreach (Item item in data)
{
    doThis(item);
}

基本上,将每个“案例”的主体放在Action 中,在循环外的switch 中选择Action,然后在循环中调用Action

【讨论】:

  • 哦,我喜欢这个主意。它似乎兼具了我一直在寻找的可读性和更高的性能。谢谢!
【解决方案2】:

您可以创建一个方法来保持可读性,然后将数据传递给该方法:

void processAllData(IEnumerable<Item> data,  setting blah)
{
    switch(blah)
    {
      case blah.single:
        foreach (Item item in data)
        {

        }
    }
    // next case, next loop ...
}

那么它只是一个单行:

processAllData(data, blah);

这种方法是可读的,因为它封装了复杂性,简洁,因为你只看到你必须看到的,高效,因为你可以优化案例。

【讨论】:

  • 这不是把问题从一个地方转移到另一个地方吗?
  • @Sconibulus:它增加了可读性但不影响性能。如果代码变得越来越复杂,你应该重构它。最简单的方法是提取方法。这隐藏了复杂性并允许重用它。编辑:但也许你想将它与罗林斯的方法结合起来;)
【解决方案3】:

通过这种方式使用 Action 委托,您可以大量分解代码

enum setting {single, multiple, foo, bar};
Data data = getData(Connection conn, int id);

var processAll = new Action<Action<item>>(action =>
                    {
                        foreach(var item in data)                           
                            action(item);
                    });

setting blah = data.getSetting();

switch(blah)
{
    case blah.single:
       processAll(item => processDataSingle(item, blah));
       break;
       ...
}

【讨论】:

    【解决方案4】:

    如果您谈论可能运行数万次或更多次的比较,它肯定有可能影响性能。您在此处编写的代码中可能出现的另一个问题是,如果您需要添加到枚举中会发生什么。然后您需要打开此代码并对其进行调整以处理违反Open/Closed Principle 的情况。

    IMO,同时解决这两个问题的最佳方法是使用工厂模式来解决这个问题(请参阅帖子 herehere 以获得有关启动该问题的一些建议)。您需要做的就是拥有一个接口,其实现将调用您想要在上面的 switch 代码中调用的方法。创建一个工厂并让它根据传入的枚举选择哪个实现返回到您的代码(在循环之前)。此时,您的循环需要做的就是调用该接口方法,该方法将完全按照您的意愿执行。

    之后,任何未来的功能添加只需要您创建该接口的另一个实现,并相应地调整枚举。没有麻烦,没有大惊小怪。

    【讨论】:

    • 枚举实际上是我创建的一个私有的东西,用于改进通过设置字段下拉值的含义的识别,因此无论如何都必须打开代码。当我重构这个和类似的过程时,我会记住这一点。
    【解决方案5】:

    像这样将开关置于循环中几乎肯定会慢一些。是否重要无法判断 - 使用秒表查看。

    【讨论】:

      【解决方案6】:

      如果 switch 语句中的值彼此接近,编译器将生成一个查找表而不是 N 个 if 语句。它提高了性能,但很难说编译器何时会决定这样做。
      相反,您可以创建一个Dictionary&lt;switchType,Delegate&gt;,用成对的值操作填充它,然后选择适当的操作大约需要O(1),因为字典是一个哈希表。
      dictionary[value].Invoke()

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-06-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-12-31
        相关资源
        最近更新 更多