- Home /
Measuring script recompilation times
Hi ! As a quite common scenario, i'm working on a project with an increasing number of scripts. After the last few days, I noticed a sensible increase in script recompilation times: it takes almost 20 seconds instead of what used to be, like 3.
I've been reading here and there about how C# or building my code into a DLL compiles much faster, but, rather than speeding it up, i'm trying to understand why this happens, ,so I'd like to narrow down what could just be some awful code.
I'd like to write an helper script, to measure individual scripts recompiling times and find the bad guy.
I'm reading AssetDatabase docs, but doesn't seam the right place for what i'm trying to do.
//something like this
function OnRecompilation()
{
var time_break = Time.realtimeSinceStartup;
var measured_script = every script triggered to be compiled ? how ? :)
AssetDatabase.Refresh(); //obviously not, but you get the idea. do you ? :)
print(measured_script + " Loaded in " + (Time.realtimeSinceStartup-time_break));
}
Of course, given the growing number of scripts, including a "self compilation time measuring" function to all of them, is not an option.
Also, Is there anything else beyond scripts, (Substances in Resources folder) affecting what i may wrongly call "recompilation times" (the small lower right circle) ? Moving substances away from that folder didn't help, but at the moment i'm clueless.
Thanks for your time and attention :)
Good question. I've always lurked in them dlls to find out how to trigger a manual compilation - I didn't go into that much depth. But really if you want to know anything 'internal' in Unity, you need to decompile it's dlls and take a look for your self. I use ILSpy. For starters, you could take a look at the UnityEditor.Scripting.Compilers
namespace
Hi vexe, and thanks for dropping by. I'm not sure this has to do with my initial scope, since i'm not interested in unity internal workflow, and probably, decompiling it's dlls is probably way too far from just measuring $$anonymous$$Y script compilation times.
Well if you could have access to these stuff you could manually compile your scripts and thus easily measure things. After all this is what unity does to compile your stuff... Decompiling might sound intimidating but it's literally:
Download and install ILSpy.
Drag/drop the UnityEditor.dll located in the Editor/Data/$$anonymous$$anaged folder in your disk to ILSpy
Browse through, lurk around till you find what you're looking for.
Thanks ! Actually i'm not new or intimidated by decompiling, but I think I would just feel lost in searching for the right functions.
UnityEditor.Scripting.Compilers would be the place to search. But I don't know what to search. for example, i don't know how or where unity stores and loops the scripts triggered for recompilation.
Don't get me wrong, I'd hate to appear lazy, but i don't think lurking into unity internal classes and reverse engineer it's inner workings it's the best way to shorten my compilation time by 10 seconds. Too much information is not better than no information at all
Decompilation never failed me. I've learned 'so much' from it! A lot of the times I need to know how something actually works, I just take a look. It might sound like there's too much, but ILSpy really helps, like for example you could right click a method and see who's calling it, where it's being used, etc. See children/parents of a type, who instantiates it, etc. A lot of info. True, it takes a bit of time to get what you want but if you're persistent you'll eventually find it, or a lead to it at least... $$anonymous$$nowledge comes at a cost, the cost is usually effort.
I'm not advising this as a way to shorten your compilation times, I'm replying to this statement:
"I'd like to write an helper script, to measure individual scripts recompiling times and find the bad guy."
Answer by vexe · Jun 20, 2014 at 07:42 AM
Alright so if you really want to speed your compilation you need to forget about UnityScript/JS/Boo and use C#, it's tons of times faster.
Now here's how you can manual compile stuff so you could measure things. First let's start with C#, there's two ways, first via a static method, second is via the StartCompiler
instance method. In both cases you have to provide:
Absolute paths to your source file(s) (With their extensions like .cs .js etc)
(Optional) Absolute paths to any non .NET reference that you used (if any) (see later) (msdn)
(Optional) Any defines that you used (msdn)
Absolute output file path (with the .dll extension as well)
Now create an editor file (put it in your Editor folder) called "ManualCompile.cs" (or anything)
using UnityEngine;
using UnityEditor;
using System.Linq;
using System;
using System.Reflection;
using System.Collections.Generic;
public static class ManualCompile
{
[MenuItem("Tools/Compile Via Static Method")]
public static void CompileCSStatic()
{
}
[MenuItem("Tools/Compile Via MonoIsland")]
public static void CompileCSViaIsland()
{
}
}
Now let's write these methods:
public static void CompileCSStatic()
{
// Get a reference to the UnityEditor.dll - could be done via any UnityEditor type, not just Editor. So typeof(SerializedProperty).Assembly also works...
var editorAsm = typeof(Editor).Assembly;
// Get the type object for our mono cs compiler
// For some reason doing a editorAsm.GetType("Whatever") always returns null so I get all the types and filter the type I'm interested in
var compilerType = editorAsm.GetTypes().FirstOrDefault(t => t.Name == "MonoCSharpCompiler");
// Get a MethodInfo reference to our static Compile method
var compileMethod = compilerType.GetMethod("Compile", BindingFlags.Static | BindingFlags.Public);
// Now we set the sources, refs, defs and output
var sources = new[] { @"absolute path to a source file, ex: C:\Users\vexe\Desktop\Test\Assets\Editor\ManualCompile.cs" };
var references = new string[]
{
typeof(GameObject).Assembly.Location, // or C:\Program Files (x86)\Unity\Editor\Data\Managed\UnityEngine.dll
editorAsm.Location
//, add more references if needed - no need to add .NET references like System, etc
};
var defines = new string[] { };
var output = @"C:\Users\vexe\Desktop\Test\Assets\output.dll";
// Invoking the compile method will return a string[] containing all the compilation errors (if any)
var errors = compileMethod.Invoke(null, new object[] { sources, references, defines, output });
foreach (var e in (errors as IEnumerable<string>))
Debug.LogError(e);
// We import and refresh
AssetDatabase.ImportAsset("Assets/output.dll");
AssetDatabase.Refresh();
}
And that's it!
Now I have to mention the other way (via MonoIsland
) because it seems that the UnityScript
compiler doesn't have a static Compile
method so we can't just change the compiler type and use the same code above, we have to do it the island way, so let's see...
First, I should mention that the static compile method above, creates an island internally so it's just a wrapper for the method that we'll write next. Now what is an island? It's just a struct that holds references to the source files, references, defines, output file, build target and a library profile:
internal struct MonoIsland
{
public readonly BuildTarget _target;
public readonly string _classlib_profile;
public readonly string[] _files;
public readonly string[] _references;
public readonly string[] _defines;
public readonly string _output;
public MonoIsland(BuildTarget target, string classlib_profile, string[] files, string[] references, string[] defines, string output)
{
this._target = target;
this._classlib_profile = classlib_profile;
this._files = files;
this._references = references;
this._defines = defines;
this._output = output;
}
}
Now what is BuildTarget
? - It's just an enum in the UnityEditor namespace. What is _classlib_prfile
? - I'm not a 100% sure, but reading Unity's code (specifically the GetProfileDirectory
method in the internal class MonoInstallationFinder
in the UnityEditor.Utils
namespace) I found out that all the profiles are located in EditorApplication.applicationPath + @"\Data\Mono\lib\mono"
(`EditorApplication.applicationPath` == (in my computer) @"C:\Program Files (x86)\Unity\Editor") - So I used "2.0" - Judging from the number, this could be the equivalent .NET framework that Unity's Mono uses (somebody correct me if I'm wrong)
So now basically the only difference, is that we have to create a MonoIsland, and pass it to the StartCompile
method - StartCompile
returns a Program
reference (an internal class in UnityEditor.Utils
) from which we can take the output messages (errors, etc). So here we go:
public static void CompileCSViaIsland()
{
var editorAsm = typeof(Editor).Assembly;
var editorTypes = editorAsm.GetTypes();
var target = BuildTarget.StandaloneWindows64;
var profile = "2.0";
var sources = new[]
{
@"C:\Users\vexe\Desktop\Test\Assets\Editor\ManualCompile.cs"
//, add more source files...
};
var references = new string[]
{
typeof(GameObject).Assembly.Location,
editorAsm.Location
};
var defines = new string[] { };
var output = @"C:\Users\vexe\Desktop\Test\Assets\output.dll";
var islandType = editorTypes.FirstOrDefault(t => t.Name == "MonoIsland");
var islandInstance = Activator.CreateInstance(islandType, target, profile, sources, references, defines, output);
var compilerType = editorTypes.FirstOrDefault(t => t.Name == "MonoCSharpCompiler");
var compilerInstance = Activator.CreateInstance(compilerType, islandInstance);
// For some reason I got an AmbiguousMatchException thrown when I used compilerType.GetMethod("StartCompiler, BF.Instance | BF.NonPublic);
// I don't know why, since there's only one StartCompiler method so there should be no ambiguity...
// So I used a more explicit overload of GetMethod and specified that I want the StartCompiler that takes no arguments...
var startCompiler = compilerType.GetMethod("StartCompiler", BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
// Like we said earlier, StartCompiler returns a Program instance
var program = startCompiler.Invoke(compilerInstance, null);
var errors = program.GetType().GetMethod("GetErrorOutput", BindingFlags.Instance | BindingFlags.Public).Invoke(program, null);
foreach (var e in (errors as IEnumerable<string>))
Debug.LogError(e);
AssetDatabase.ImportAsset("Assets/output.dll");
AssetDatabase.Refresh();
}
Now I haven't tried it, but I'm willing to bet that all you have to do to compile UnityScripts, is just change the compiler type name, so instead of "MonoCSharpCompiler" you use "UnityScriptCompiler"
I will write a more nicified/friendly/easy-to-use/portable versions of the two methods above later so you can pass a whole directory to compile, much better.
Now you can measure your stuff, just write the source files paths, use System.Diagnostics.Stopwatch
and compile. - Let me know how it goes.
EDIT:
Actually you can do your compilation from the command line, reading from MonoScriptCompilerBase
(in UnityEditor.Scripting.Compilers
) StartCompiler
method you can see that the compiler is actually executed from the profile folders I mentioned above, so if you go to the "Editor/Data/Mono/lib/mono/2.0" the compilers executables are there, somewhere...
EDIT:
Here's a better-written version - Notice I'm not detecting the references when I compile a directory - So you have to manually add the references your self. Remember, you don't need to add .NET references like System, etc but only custom/user references, like Unity dlls or MyRuntimeExtensions.dll
or whatever... You might want to put the stopwatch in a dif place, or put more than one, up to you.
EDIT:
Forgot to mention something, the second answer here covers it.
wow, a lot to chew on :) thanks for your effort, i really hope i can make profit of it!
Vexe, thank you for your time, but this is really above my actual skills. I'm not familiar with C#, with editor scripts, so couldn't understand or follow the logic of what you posted so far, I can't even understand how to call these functions i put in my Editor folder. and seeing @"C:\Users\vexe\Desktop\Test\Assets\Editor\$$anonymous$$anualCompile.cs" //, add more source files...
makes me think i should manually add every script path in my project in there. which is as mentioned, definitively unfeasable. I sincerely thank you for your efforts, your findings may surely help who's landing on the same problem. $$anonymous$$y wrong for assu$$anonymous$$g there would have been a solution not out of my league.
No problem man. I believe the best way to learn is to bite more than you could chew. Don't be afraid to learn new stuff, if you want a really good source to 'drastically' improve your techniques arsenal, check out Jamie $$anonymous$$ing, really a beast! I learned all my reflection, LINQ, etc stuff from him!
And, no you're not gonna write the paths to 'all' the scripts you want to compile, ins$$anonymous$$d you should use Directory.GetFiles(path)
so you compile all the files underneath a directory. I'm writing a better version of all this, should be much easier to use. I might make an editor window for it too... There's something a bit tricky though when compiling a directory, is fetching the references. Since you have more than one file, I don't know how to go about it yet but I'll see what I can do.
Your answer
![](https://koobas.hobune.stream/wayback/20220613151936im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
Check if public field set in editor at compilation 2 Answers
Assigning class instance to a Gameobject 0 Answers
Compiling Scripts Popup Appears Every Time You Switch to Unity 0 Answers
How can I get more information about this error: "The classes in the module cannot be loaded"? 1 Answer
Isn't there a "do not compile" folder? 6 Answers