Would this be an effective way to improve cold-start delays in .NET?
Asked Answered
E

2

12

The following code (by Vitaliy Liptchinsky) goes through all types in an assembly and calls PrepareMethod on all methods. Would this improve cold-start delays?

    Thread jitter = new Thread(() =>
    {
      foreach (var type in Assembly.Load("MyHavyAssembly, Version=1.8.2008.8," + 
               " Culture=neutral, PublicKeyToken=8744b20f8da049e3").GetTypes())
      {
        foreach (var method in type.GetMethods(BindingFlags.DeclaredOnly | 
                            BindingFlags.NonPublic | 
                            BindingFlags.Public | BindingFlags.Instance | 
                            BindingFlags.Static))
        {
            if (method.IsAbstract || method.ContainsGenericParameters)
                    continue;
            System.Runtime.CompilerServices.RuntimeHelpers.PrepareMethod(method.MethodHandle);
        }
      }
    });
    jitter.Priority = ThreadPriority.Lowest;
    jitter.Start();
Emulsion answered 4/3, 2011 at 7:5 Comment(15)
Why don't you try and see for yourself? Seeing that a lot of work is being done at start up, I can't see how this will improve the start up speed. In fact it will slow it down even more. Remember the jitter only complies methods just in time and all not at once. And in fact one of the reasons it does this is so as to NOT slow down the application at start up. Of course the jitter's implementation could change at any time to include things like runtime optimization etc. If you don't care for this then simply ngen your assemblies for the platform in question.Thicken
I intend to, but just checking here if anyone had tried it already.Emulsion
What I mean by 'cold-start delays' is any delay that is caused by the JIT of methods. Many of my functions and forms have an appreciable delay the first time they are run, thereafter they are fine. I would prefer to have the user wait during a splash screen, than wait all the way through the running of the app.Emulsion
NGEN is difficult for deployment.Emulsion
@Craig: Why not kick the thread off but not have it at the splash screen? That way it can be JITting the methods for Form2 while the users are still looking at Form1, if you see what I mean.Challah
Note also that this doesn't guarantee anything anyway... for example, generic methods (or methods in generic types) have JIT per value-type plus once for all references, which this doesn't account for.Geophilous
@Marc: it's not clear what PrepareMethod actually does. If it is compiling then surely it would compile whatever types are actually used for the generic types, just like in a non-JIT language such as C++.Emulsion
@Craig - GetMethods returns the declarations only; it isn't going to scour your entire code-base looking for calls to it, to find all the T possibly involved. And even if it did, T can be provided entirely at runtime.Geophilous
@Marc: but PrepareMethod must be going through the actual method, in which it will encounter instantiations of the generic type.Emulsion
yes but JIT of an individual method doesn't mean that every method it calls get JITted; this code sequentially and in isolation JITs every method it can find - but that is all.Geophilous
Personally, I wouldn't touch this code with a long pole... sorry...Geophilous
@Marc: you were right, PrepareMethod cannot handle generic types - I've modified the above code to ignore these methods. By the way, if NGEN is not option due to deployment issues, what would you recommend as a way to pre-JIT assemblies?Emulsion
@Craig I wouldn't; generally it is an unnecessary expense... however, having some mechanism to execute some of your core logic on a dry run might be enough - maybe add a "for real" flag on any blocks you really want to JIT and just call them? but don't do it while the splash is running; a background thread that doesn't stop the user would be preferable. But in most cases this is going to do a lot more harm than good. Honestly.Geophilous
@Marc: the only way to avoid a cold-start of a particular form in my app is to show the form with size = (0,0) during the splash - subsequent showing of the form is then quick. What are my options?Emulsion
In the case of a form, I expect the delay is actually "fusion", not JIT. Rather than focusing on JIT, you can pre-load assemblies recursively... 2 secs, I'll write an exampleGeophilous
C
10

It won't make startup any faster - after all, it's doing work earlier than it normally would rather than later. In fact could will slow down startup slightly, as you'll have another thread doing work while your app is trying to start.

It means that by the time that thread has finished, you shouldn't see the normal (tiny) JIT delay when you call a method for the first time. (Of course there are also constructors, but you could include them if you want.)

Additionally it probably means more memory will be used earlier, and there may be some locking involved in the JIT working on methods which means your "main" thread may need to wait occasionally.

Personally I wouldn't use it "for real" unless I had some very good evidence to suggest it's actually helping. By all means test it and try to get such evidence. If you don't mind startup time, but you want a swift response time when you come to actually work with the application, it may help.

Challah answered 4/3, 2011 at 7:14 Comment(5)
I intend to use it on the main thread during a splash screen. I would prefer the user to wait during startup, rather than during the running of the app.Emulsion
@Craig: Have you canvassed your users about this? I'd much rather have several basically-imperceptible pauses spread through the lifetime of the application than have to wait at startup - especially as I'd be waiting for methods to be JIT compiled which I may never end up executing.Challah
What do you mean by "[...] there are also properties"? The query would return the getter and/or setter of each property, wouldn't it? Is there some other aspect to the JITter with respect to properties that you are referring to?Henleigh
@Ani: It does indeed. Will edit. For some reason I thought it would require an extra binding flag to discover methods backing properties. It doesn't see constructors though :)Challah
Actually it may very well improve startup time, when launching an application a lot of time goes to the IO (loading the assemblies, resources and native DLLs from disk) and the CPU is not fully utilized (for many apps). This is especially true for multi-core machines (have you seen a single-core machine recently?). Since the code sample uses a separate thread with low priority I don't see how it can reduce performance.Halfdan
G
0

As discussed in comments, it might be sufficient just to preload the assemblies:

    static void PreloadAssemblies()
    {
        int count = -1;
        Debug.WriteLine("Loading assemblies...");
        List<string> done = new List<string>(); // important...
        Queue<AssemblyName> queue = new Queue<AssemblyName>();
        queue.Enqueue(Assembly.GetEntryAssembly().GetName());
        while (queue.Count > 0)
        {
            AssemblyName an = queue.Dequeue();
            if (done.Contains(an.FullName)) continue;
            done.Add(an.FullName);
            try
            {
                Assembly loaded = Assembly.Load(an);
                count++;
                foreach (AssemblyName next in loaded.GetReferencedAssemblies())
                {
                    queue.Enqueue(next);
                }
            }
            catch { } // not a problem
        }
        Debug.WriteLine("Assemblies loaded: " + count);
    }
Geophilous answered 4/3, 2011 at 9:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.