重新执行的巨大差异

本文关键字:巨大 执行 新执行 | 更新日期: 2023-09-27 18:35:58

我写了一个小的 C# 应用程序,它为一本书编制索引,并在索引上执行布尔文本检索算法。文章末尾的类展示了两者的实现,构建索引并在其上执行算法。

代码通过以下方式通过 GUI 按钮调用:

    private void Execute_Click(object sender, EventArgs e)
    {
        Stopwatch s;
        String output = "-----------------------'r'n";
        String sr = algoChoice.SelectedItem != null ? algoChoice.SelectedItem.ToString() : "";
        switch(sr){
            case "Naive search":
                output += "Naive search'r'n";
                algo = NaiveSearch.GetInstance();
                break;
            case "Boolean retrieval":
                output += "boolean retrieval'r'n";
                algo = BooleanRetrieval.GetInstance();
                break;
            default:
                outputTextbox.Text = outputTextbox.Text + "Choose retrieval-algorithm!'r'n";
                return;
        }
        output += algo.BuildIndex("../../DocumentCollection/PilzFuehrer.txt") + "'r'n";
        postIndexMemory = GC.GetTotalMemory(true);
        s = Stopwatch.StartNew();
        output += algo.Start("../../DocumentCollection/PilzFuehrer.txt", new String[] { "Pilz", "blau", "giftig", "Pilze" });
        s.Stop();
        postQueryMemory = GC.GetTotalMemory(true);
        output +=  "'r'nTime elapsed:" + s.ElapsedTicks/(double)Stopwatch.Frequency + "'r'n";
        outputTextbox.Text = output + outputTextbox.Text;
    }

Start(...) 的第一次执行运行大约 700μs,每次重新运行只需要 <10μs。该应用程序是使用 Visual Studio 2010 和默认的"调试"生成配置编译的。

我做了很多实验来找到原因,包括分析和不同的实现,但效果总是保持不变。

如果有人能给我一些新的想法,我会尝试甚至解释,我会很兴奋。

    class BooleanRetrieval:RetrievalAlgorithm
    {
        protected static RetrievalAlgorithm theInstance;
        List<String> documentCollection;
        Dictionary<String, BitArray> index;
        private BooleanRetrieval()
            : base("BooleanRetrieval")
        {
        }
        public override String BuildIndex(string filepath)
        {
            documentCollection = new List<string>();
            index = new Dictionary<string, BitArray>();
            documentCollection.Add(filepath);
            for(int i=0; i<documentCollection.Count; ++i)
            {
                StreamReader input = new StreamReader(documentCollection[i]);
                var text = Regex.Split(input.ReadToEnd(), @"'W+").Distinct().ToArray();
                foreach (String wordToIndex in text)
                {
                    if (!index.ContainsKey(wordToIndex))
                    {
                        index.Add(wordToIndex, new BitArray(documentCollection.Count, false));
                    }
                    index[wordToIndex][i] = true;
                }
            }
            return "Index " + index.Keys.Count + "words.";            
        }
        public override String Start(String filepath, String[] search)
        {
            BitArray tempDecision = new BitArray(documentCollection.Count, true);
            List<String> res = new List<string>();
            foreach(String searchWord in search)
            {
                if (!index.ContainsKey(searchWord))
                    return "No documents found!";
                tempDecision.And(index[searchWord]);
            }

            for (int i = 0; i < tempDecision.Count; ++i )
            {
                if (tempDecision[i] == true)
                {
                    res.Add(documentCollection[i]);
                }
            }
            return res.Count>0 ? res[0]: "Empty!";
        }
        public static RetrievalAlgorithm GetInstance()
        {
            Contract.Ensures(Contract.Result<RetrievalAlgorithm>() != null, "result is null.");
            if (theInstance == null)
                theInstance = new BooleanRetrieval();
            theInstance.Executions++;
            return theInstance;
        }
    }

重新执行的巨大差异

.Net 应用程序的冷/热启动通常受加载程序集的 JIT 时间和磁盘访问时间的影响。

对于执行大量磁盘 IO 的应用程序,如果数据足够小以适合磁盘的内存缓存,则由于缓存内容(也适用于程序集加载),首次访问磁盘上的数据将比重新运行相同数据时慢得多。

  • 任务的首次运行将受到程序集和数据的磁盘 IO 以及 JIT 时间的影响。
  • 在不重新启动应用程序的情况下第二次运行同一任务 - 只需从操作系统内存缓存中读取数据。
  • 第二次运行应用程序 - 再次从操作系统内存缓存和 JIT 读取程序集。