【问题标题】:Change foreach loop by for loop用for循环改变foreach循环
【发布时间】:2017-07-07 07:36:16
【问题描述】:

我正在创建一个指纹验证系统,在该系统中我必须使用数据库中的记录来匹配指纹。我已经使用了foreach 循环,但仅 350 条记录就需要将近 40 秒。我想加快速度。我希望我的 foreach 循环转换为 for 循环,但我在初始化 for 循环时遇到了一些困难。这是我的代码。

foreach (KeyValuePair<decimal, MemoryStream> tt in profileDic)
{
    this.Invoke(new Function(delegate()
    {
        textBox1.Text += "\n" + tt.Key.ToString();
    }));
    temp = new DPFP.Template(tt.Value);

    Verificator = new DPFP.Verification.Verification();
    featuresForVerification = ExtractFeatures(Sample, DPFP.Processing.DataPurpose.Verification);
    if (featuresForVerification != null)
    {
        DPFP.Verification.Verification.Result result = new DPFP.Verification.Verification.Result();
        Verificator.Verify(featuresForVerification, temp, ref result);

        #region UI Envoke
        this.Invoke(new Function(delegate()
        {
            if (result.Verified)
            {
                MessageBox.Show("FAR " + result.FARAchieved + "\n" + tt.Key.ToString());
            }
            else
            {
                //MessageBox.Show("No Match");
            }
        }));
        #endregion
    }
    this.Invoke(new Function(delegate()
    {
        progressBar1.Value += 1;
    }));
    Thread.CurrentThread.Priority = ThreadPriority.Highest;
}

我对第一行 foreach (KeyValuePair&lt;decimal, MemoryStream&gt; tt in profileDic) 感到困惑。有人可以告诉我如何使用 for 循环遍历 Dictionary 对象profileDic 中的每个项目。我不确定在使用for 循环时如何获得KeyValuePair&lt;decimal, MemoryStream&gt; tt in profileDic

【问题讨论】:

  • 是什么让您认为for 循环会提高速度?你有没有运行分析器来找出哪个部分实际上很慢?
  • 除了@JamesThorpe,您可能还想检查您的数据库,如果您的查询没有跟上速度,它不会有很大改进。
  • Verificator = new DPFP.Verification.Verification();;这条线有效吗?如果是这样,Verificator 是一个对象,而不是一个类,所以应该称为verificator。您是否需要在每次迭代时重新初始化该对象,还是允许您重用同一个对象,多次调用 Verify 而不会影响结果?
  • “你能告诉我哪个代码需要更多时间吗?” 不,但是一个好的分析器可以。以 Ants 或 dotTrace 为例。
  • 性能分析初学者指南(在 Visual Studio 中)msdn.microsoft.com/en-us/library/ms182372.aspx

标签: c# performance for-loop foreach performance-testing


【解决方案1】:

我必须匹配 [条目与列表]。我用foreach循环来做 因此,但仅 350 条记录需要将近 40 秒。我要加速 它了。我希望我的 foreach 循环转换为 for for 循环 [...]

在这种情况下,最好退后一步,想想我们在这里所做的一切。性能优化通常来自两个层面:算法和工作流程。

本质上,这里要解决的问题是在可能很大的记录集中找到一个条目。速度慢可能有两个原因:

  • 列表非常大,迭代需要很长时间
  • 列表可能没有那么大,但该例程经常被调用

列表很大

如果我们记得我们对所谓的Big-O notation 的了解并考虑一下,我们可能很快会发现数组搜索需要O(n),而例如哈希集搜索在正常情况下只需要O(1),只有在最坏的情况下,我们才会再次下降到O(n)。还不错!

巧合(并借助上面链接的备忘单),我们发现Dictionary&lt;K,V&gt; 或数据库索引或多或少是我们想要的:字典基本上是“拉皮条”哈希集,并且数据库索引通常是 B 树,它使用Θ(log(n)) 执行。我们应该使用字典还是数据库表的最终决定主要取决于我们所讨论的数据量。

作为一个实际示例,我最近在我的表上有一段代码,它以相同的线性方式遍历列表。该代码在另外两个嵌套循环中执行此操作。该算法需要 4 个小时才能完成。在战略要地介绍两部词典后,我把它缩短到不到一分钟。我把它留给有趣的读者来计算百分比。

因此,真正要问的问题不是“forforeach 快吗?” (否)但我们应该问:“我怎样才能重新组织我的数据结构并优化所涉及的算法以使其执行?

代码经常被调用

这是另一个相关的问题。在某些情况下,无法真正优化数据结构,否则会导致代码更改太多。但是,当我们仔细查看分析器告诉我们的内容时,我们可能会发现代价高昂的例程在 15 秒内被调用了 800.000 次,而且仅这些调用对总时间的贡献就相当大。

如果我们仔细观察,我们可能会发现我们使用非常有限的一组输入数据调用例程,因此基本上大部分调用可能只是通过缓存昂贵操作的结果而被省略。上周我刚遇到这样一个案例,我能够将数据库调用量减少到原始数量的 5%。可以想象这对整体性能有什么影响。

因此,在第二种情况下,我们应该问自己一个稍微不同的问题:“我们为什么要这样做?我们如何更改逻辑工作流程以避免大多数此类调用?是否有完全不同的方法?达到同样的效果?”。

摘要 (TL;DR)

每种性能优化都有两种基本方法:

  • 算法或“低级”:快速排序还是冒泡排序?树、列表还是 HashSet?
  • 工作流和逻辑:为什么我们必须调用这个特别昂贵的例程 500 万次?

【讨论】:

    【解决方案2】:

    与其将其从 For 循环更改为 Foreach 循环,您实际上可能想要添加 Exit For 或 Break;在 c# 中,一旦你已经找到了结果。

    【讨论】:

      【解决方案3】:

      您使用的是什么生物识别系统?这种工作应该在生物识别设备中完成。但是如果你真的需要直接在数据库中找人,你不应该使用C#集合,而应该使用数据库本身。

      每个指纹都有其细节。指纹有独特的特点。有一些算法可以将其转换为可存储的数据,例如在数据库中。这可能看起来像 md5 哈希。

      接下来,当你在数据库中有细节的记录时,你可以向数据库询问这个值。

      它应该是这样工作的:你得到细节(或可以直接存储在数据库中的完整数据),然后向数据库询问这个值,比如:

      SELECT *
      FROM users
      WHERE fingerprint = :your_data
      

      数据库操作比以任何方式遍历任何集合都要快得多。

      【讨论】:

      • 我正在使用数字角色指纹设备
      【解决方案4】:

      要回答您提出的问题:要用 for 循环替换 foreach 循环,请替换:

      foreach (KeyValuePair<decimal, MemoryStream> tt in profileDic) 
      {
          //...
      }
      

      for (var i=0; i < profileDic.Count, i++) 
      {
          KeyValuePair<decimal, MemoryStream> tt = profileDic.ElementAt(i);
          //...
      }
      

      要使用它,您还需要在代码中包含 using System.Linq; 语句。 也就是说,这假设字典中元素的顺序不会改变;这是无法保证的(除非您使用的是SortedDictionaryOrderedDictionary)。 因此,更好的方法是:

      [decimal[]]keys = profileDic.Keys;
      for (var i=0; i < keys.Count, i++) 
      {
          KeyValuePair<decimal, MemoryStream> tt = profileDic[keys[i]];
          //...
      }
      

      但这会增加更多的开销/可能会使for 循环的时间超过foreach 循环的时间/是一种微优化,无法解决您的实际性能问题。


      对于每个 cmets,循环可能不是您的问题,而是该循环中发生了什么(如果在这部分代码中)。

      我们对您的代码知之甚少,无法对其发表评论,因此您最好自己调查一下;例如使用此处描述的性能分析技术:https://msdn.microsoft.com/en-us/library/ms182372.aspx

      我已经重构了您的代码以使其更具可读性(即通过将 UI 更新拉入它们自己的方法中,这样它们就不会弄乱主线程)。

      我还移动了那些看起来不需要在循环之外的每次迭代中更新的操作...但是在不知道您的代码的情况下,这纯粹是猜测/所以不能保证。

      最后,我删除了您的代码,以在每次迭代结束时更改当前线程的优先级。玩弄线程优先级并不是修复慢代码的好方法;只有在某些情况下它是合适的,并且看到这里的上下文,我 99% 以上确定这不是其中一种情况。

      //...
      featuresForVerification = ExtractFeatures(Sample, DPFP.Processing.DataPurpose.Verification); //since this appears unaffected by our profileDic values, let's initialise once
      if (featuresForVerification != null)
      {
          DPFP.Verification.Verification verificator = new DPFP.Verification.Verification();
          foreach (KeyValuePair<decimal, MemoryStream> tt in profileDic)
          {
              string key = tt.Key.ToString(); //we use this a lot, so let's only convert it to string once, then reuse that
              UIReportCurrentKey(key);
              temp = new DPFP.Template(tt.Value); 
              DPFP.Verification.Verification.Result result = new DPFP.Verification.Verification.Result();
              verificator.Verify(featuresForVerification, temp, ref result);
              UIReportMatch(result, key);
              //if a match were found, would we want to keep comparing, or exit on first match?  If the latter, add code to record which record matched, then use break to exit the loop
              UIIncremementProgressBar();
          }
      } else {
          throw new NoFeaturesToVerifyException("The verfication tool was not given any features to verify");
          //alternatively set progress bar to complete / whatever other UI actions you have /
          //whatever you want to happen if no features were selected for comparison
      }
      
      
      //...
      
      #region UI Updaters
      /*
          I don't know much about winforms updates; have a feeling these can be made more efficient too, 
          but for the moment just shoving the code into its own functions to make the main method less
          cluttered with UI logic.
      */
      
      // Adds the key of the item currently being processed to the UI textbox
      private void UIReportCurrentKey(string key) 
      {
          this.Invoke(new Function(delegate()
          {
              textBox1.Text += "\n" + key;
          }));
      }
      private void UIReportMatch(DPFP.Verification.Verification.Result result, string key) 
      {
          if (result.Verified) 
          {
              this.Invoke(new Function(delegate()
              {
                      MessageBox.Show("FAR " + result.FARAchieved + "\n" + key);
              }));
          } 
          /* 
          else 
          {
              this.Invoke(new Function(delegate()
              {
                      MessageBox.Show("No Match");
              }));        
          } 
          */
      }
      private void UIIncremementProgressBar() 
      {
          this.Invoke(new Function(delegate()
          {
              progressBar1.Value++;
          }));
      }
      
      #endregion UI Updaters
      
      #region Custom Exceptions
      public class NoFeaturesToVerifyException: ApplicationException 
      { //Choose base class appropriately
          public NoFeaturesToVerifyException(): base() {}
          public NoFeaturesToVerifyException(string message): base(message) {}
          //...
      }
      #endregion Custom Exceptions
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-04-04
        • 2018-08-20
        • 2014-04-19
        • 1970-01-01
        • 1970-01-01
        • 2018-01-14
        • 2012-10-02
        • 1970-01-01
        相关资源
        最近更新 更多