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();
Timothy
  • 469
  • 5
  • 8
Craig Johnston
  • 7,467
  • 16
  • 40
  • 47
  • 1
    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. – Shiv Kumar Mar 04 '11 at 07:12
  • I intend to, but just checking here if anyone had tried it already. – Craig Johnston Mar 04 '11 at 07:13
  • 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. – Craig Johnston Mar 04 '11 at 07:19
  • NGEN is difficult for deployment. – Craig Johnston Mar 04 '11 at 07:20
  • 1
    @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. – Jon Skeet Mar 04 '11 at 07:27
  • 1
    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. – Marc Gravell Mar 04 '11 at 07:42
  • @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++. – Craig Johnston Mar 04 '11 at 08:11
  • @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. – Marc Gravell Mar 04 '11 at 08:28
  • @Marc: but PrepareMethod must be going through the actual method, in which it will encounter instantiations of the generic type. – Craig Johnston Mar 04 '11 at 08:34
  • 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. – Marc Gravell Mar 04 '11 at 08:36
  • 1
    Personally, I wouldn't touch this code with a long pole... sorry... – Marc Gravell Mar 04 '11 at 08:36
  • @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? – Craig Johnston Mar 04 '11 at 08:56
  • @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. – Marc Gravell Mar 04 '11 at 09:06
  • @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? – Craig Johnston Mar 04 '11 at 09:12
  • 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 example – Marc Gravell Mar 04 '11 at 09:17

2 Answers2

11

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.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 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. – Craig Johnston Mar 04 '11 at 07:22
  • 4
    @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. – Jon Skeet Mar 04 '11 at 07:26
  • 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? – Ani Mar 04 '11 at 07:50
  • @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 :) – Jon Skeet Mar 04 '11 at 07:53
  • 1
    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. – Motti Mar 21 '12 at 09:18
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);
    }
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900