编译C#脚本很慢

本文关键字:脚本 编译 | 更新日期: 2023-09-27 18:00:55

我有一个应用程序,它使用C#编写的"脚本",在启动时使用C#CodeDomProvider编译。加载几十个,甚至超过170个脚本都不是什么大问题,我编译它们,然后将程序集保存到缓存文件夹中,以便下次启动。对于170个脚本,这个初始编译大约需要1.5分钟。

然而,当我尝试加载>1000个脚本时,编译所有脚本需要一个多小时。我添加了一个Stopwatch,了解到每个脚本的加载时间都比以前长一点,在170个文件的情况下,它从第一个的150ms增加到最后一个的650ms,每个文件都在一点一点地增加。

我知道,通常情况下,通过将脚本组合到一个大文件中,我可以大幅减少加载时间,但出于几个原因,我更喜欢单独编译它们:/这使重新加载它们变得简单快捷,当发生变化时,我不必担心重新编译整个脚本文件夹,我可以很容易地为编译进度提供进度条,等等。

现在我的问题是,这里有什么问题?为什么每个文件的编译时间会随着时间的推移而增加?我能做点什么吗?

编辑

根据评论中的要求,我将尝试提供更多信息。

正如我所说的,我正在使用C#CodeDomProvider编译每个脚本,基本上是循环中的以下内容,用于我当前171个脚本文件中的每一个,每个文件都包含一个或多个类,我在创建程序集后实例化这些类:

var provider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("CSharp"); ;
var parameters = new System.CodeDom.Compiler.CompilerParameters();
parameters.ReferencedAssemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic).Select(a => a.Location).ToArray());
parameters.GenerateExecutable = false;
parameters.GenerateInMemory = false;
parameters.OutputAssembly = tmpFileName + ".compiled";
parameters.TreatWarningsAsErrors = false;
parameters.WarningLevel = 0;
var results = provider.CompileAssemblyFromFile(parameters, tmpFileName);
asm = results.CompiledAssembly;

我使用CS Script得到了相同的结果,它做了类似的事情。

asm = CSScript.LoadWithConfig(tmpFileName, null, debug, CSScript.GlobalSettings, "/warnaserror- /warn:0");

然后将创建的程序集保存到缓存文件夹中。当脚本文件比程序集旧时,将使用保存的程序集,因此我不必再次编译它。

当你坐在它前面,看着进度条越来越慢时,它会随着时间的推移变得越来越慢,这一点变得很明显,所以我围绕上面的代码StartNew, Stop, Elapsed添加了一个简单的Stopwatch调用,没什么特别的。结果反映了我在进度条上看到的内容,每增加一个文件,编译时间就会增加。

timer.Restart();
// compile as shown above
timer.Stop();
Console.WriteLine(asm.Location + ": " + timer.Elapsed);

当然,根据文件的大小和复杂程度,预计文件之间会有一些细微的差异,但脚本的大小和复杂性通常都或多或少相同,而且随着时间的推移,我看到了不断的增加。

Temp'tmpDDD7.tmp.compiled: 00:00:00.1389177
Temp'tmpDE74.tmp.compiled: 00:00:00.1327150
...
Temp'tmpE156.tmp.compiled: 00:00:00.1719746
Temp'tmpE213.tmp.compiled: 00:00:00.1431011
...
Temp'tmpF05C.tmp.compiled: 00:00:00.1696297
Temp'tmpF118.tmp.compiled: 00:00:00.1739564
...
Temp'tmpF7D5.tmp.compiled: 00:00:00.1824292
Temp'tmpF891.tmp.compiled: 00:00:00.1819889
...
Temp'tmp29F1.tmp.compiled: 00:00:00.2912163
Temp'tmp2B2B.tmp.compiled: 00:00:00.2909096
...
Temp'tmp362F.tmp.compiled: 00:00:00.3161408
Temp'tmp3773.tmp.compiled: 00:00:00.3170768
...
Temp'tmpA4C9.tmp.compiled: 00:00:00.5457990
Temp'tmpA6FC.tmp.compiled: 00:00:00.5460514

编译C#脚本很慢

事实上,这可能是导致这种行为的原因。事实上很有可能是。

通过将CSScript.ShareHostRefAssemblys设置为true,可以轻松更改行为。

尽管在这种情况下,您需要注意所有引用其他脚本的脚本。一种方法是为所有"可共享"脚本提供所需的程序集文件名(在Load(string scriptFile, string assemblyFile...)中(,然后使用这些文件名作为加载的所有脚本的输入。

//Assembly Load(string scriptFile, string assemblyFile, bool debugBuild, params string[] refAssemblies)
CSScript.Load("common_scriptA.cs", "asmA.dll", false);
CSScript.Load("common_scriptB.cs", "asmB.dll", false);
CSScript.Load("common_scriptC.cs", "asmC.dll", false);
CSScript.Load("normal_scriptA.cs", null, false, "asmC.dll", "asmB.dll", "asmC.dll");
CSScript.Load("normal_scriptB.cs", null, false, "asmC.dll", "asmB.dll", "asmC.dll");

编译变得越来越慢的原因是,每个创建的程序集都被添加为对以下所有脚本文件的引用。第一个文件可能有10个引用,第二个是11个,第三个是12个,依此类推。引用越多,编译所需的时间就越长。这就是下一行所做的,也是CS Script默认情况下所做的。

parameters.ReferencedAssemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies().Where(a => !a.IsDynamic).Select(a => a.Location).ToArray());

不幸的是,我希望脚本能够相互引用,所以我现在必须想出一个引用系统。但至少我知道问题出在哪里。也许我会尝试自动化它,记住在哪个程序集中可以找到什么类型,然后自动引用它们。