Startup Performance Tuning

By design MugenMvvm is scanning all assemblies in order to match viewmodel->view and caching all needed types. Also by design MugenMvvm bindings works through reflection.

Performance of application startup as far as binding perfromace can be significantly improved by using IBootstrapCodeBuilder interface:

  • remove assembly scanning in release builds;
  • MugenMvvm bindings will work though compiled expressions instead of reflection access;
  • platform linker can be applied (Android, iOS).

Let's look at algorithm of usage IBootstrapCodeBuilder interface. In order to make this mechanism work you have to follow the next steps:

  1. Implement IBootstrapCodeBuilder interface.
  2. Register the implementation in module. This will generate piece of code that allows all of features be applied.
  3. Override the module loading procedure in order to use generated code instead of default behaviour.
  4. Pass generated code into your MugenMvvmApplicaiton instance.

Let's look at example of usage IBootstrapCodeBuilder interface. As our application we will take our HelloValidation app. Clone it on your computer and open the solution.

19201920

Let's implement IBootstrapCodeBuilder interface:

using System;
using System.Collections.Generic;
using System.IO;
using MugenMvvmToolkit.Infrastructure;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Interfaces.Models;

namespace HelloValidation
{
    public class BootstrapCodeBuilder : IBootstrapCodeBuilder
    {
        private static Dictionary<string, List<string>> _staticTagToCode;
        private static Dictionary<string, List<string>> _tagToCode;

        void IBootstrapCodeBuilder.AppendStatic(string tag, string code)
        {
            lock (this)
            {
                if (_staticTagToCode == null)
                    _staticTagToCode = new Dictionary<string, List<string>>();
                List<string> value;
                if (!_staticTagToCode.TryGetValue(tag, out value))
                {
                    value = new List<string>();
                    _staticTagToCode[tag] = value;
                }
                value.Add(code);
                DumpCode();
            }
        }

        void IBootstrapCodeBuilder.Append(string tag, string code)
        {
            lock (this)
            {
                if (_tagToCode == null)
                    _tagToCode = new Dictionary<string, List<string>>();
                List<string> value;
                if (!_tagToCode.TryGetValue(tag, out value))
                {
                    value = new List<string>();
                    _tagToCode[tag] = value;
                }
                value.Add(code);
                DumpCode();
            }
        }

        private static void DumpCode()
        {
            var list = new List<string>();
            var mainMethod = new List<string>();
            mainMethod.Add($"public static void PrecompiledBoot({typeof(IModuleContext).FullName} context)");
            mainMethod.Add("{");
            if (_staticTagToCode != null)
                foreach (var builder in _staticTagToCode)
                {
                    var methodName = builder.Key + "StaticGenerated()";
                    mainMethod.Add(methodName + ";");
                    list.Add(Environment.NewLine);
                    list.Add("private static void " + methodName);
                    list.Add("{");
                    if (builder.Key == nameof(ExpressionReflectionManager))
                        builder.Value.Sort();
                    list.AddRange(builder.Value);
                    list.Add("}");
                }
            if (_tagToCode != null)
                foreach (var builder in _tagToCode)
                {
                    mainMethod.Add(builder.Key + "Generated(context);");
                    list.Add(Environment.NewLine);
                    list.Add("private static void " + builder.Key 
                             + $"Generated({typeof(IModuleContext).FullName} context)");
                    list.Add("{");
                    if (builder.Key == nameof(ExpressionReflectionManager))
                        builder.Value.Sort();
                    list.AddRange(builder.Value);
                    list.Add("}");
                }
            mainMethod.Add("}");
            list.AddRange(mainMethod);
            var path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
            var filename = Path.Combine(path, "BootstrapCodeBuilder.cs");
            File.WriteAllLines(filename, list);
        }
    }
}

and add it in "HelloValidation" android project:

19201920

Now we have to register this class as BootstrapCodeBuilder. Let's add new module in android project and register our BootstrapCodeBuilder class:

using MugenMvvmToolkit;
using MugenMvvmToolkit.Models;
using MugenMvvmToolkit.Modules;

namespace HelloValidation
{
    public class Module : ModuleBase
    {
        #region Constructors

        public Module()
            : base(false, LoadMode.All, InitializationModulePriority)
        {
        }

        #endregion

        #region Overrides of ModuleBase

        protected override bool LoadInternal()
        {
#if DEBUG
            ServiceProvider.BootstrapCodeBuilder = new BootstrapCodeBuilder();
#endif
            return true;
        }

        protected override void UnloadInternal()
        {
        }

        #endregion
    }
}
19201920

📘

BootstrapCodeBuilder and Release Builds

Note that we used DEBUG macro to register BootstrapCodeBuilder only in debug builds. By design BootstrapCodeBuilder should be called only once before application release (e.g. in Play Store, App Store) and definitely not intended for use in release builds.

Now run your android app. As was written in DumpCode method of BootstrapCodeBuilder class generated class BootstrapCodeBuilder.cs will be saved in personal dir with name "BootstrapCodeBuilder.cs".

var path = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
var filename = Path.Combine(path, "BootstrapCodeBuilder.cs");
File.WriteAllLines(filename, list);

In our case the path is "/data/data/HelloValidation.HelloValidation/files/BootstrapCodeBuilder.cs".

19191919

In our case the path is "/data/data/HelloValidation.HelloValidation/files/BootstrapCodeBuilder.cs".

Let's copy this file from android device on pc using adb and then include generated code into project:

adb pull /data/data/HelloValidation.HelloValidation/files/BootstrapCodeBuilder.cs  %USERPROFILE%\Desktop\
963963

Let's add "GeneratedBootstrapCode.cs" file into android project and include generated code into it:

19201920

Include generated code into android project.

Now we have to point out MugenMvvm engine that we want it use generated code.
Open "App.cs" in "Core" project and modify "App.cs" as follows:

using System;
using System.Collections.Generic;
using System.Reflection;
using Core.ViewModels;
using MugenMvvmToolkit;
using MugenMvvmToolkit.Interfaces.Models;

namespace Core
{
    public class App : MvvmApplication
    {
        private readonly Action<IModuleContext> _loadModulesDelegate;

        public App(Action<IModuleContext> loadModulesDelegate = null)
        {
            _loadModulesDelegate = loadModulesDelegate;
        }

        protected override void LoadModules(IList<Assembly> assemblies)
        {
            if (_loadModulesDelegate == null)
                base.LoadModules(assemblies);
            else
                _loadModulesDelegate(CreateModuleContext(Empty.Array<Assembly>()));
        }

        #region Overrides of MvvmApplication

        public override Type GetStartViewModelType()
        {
            return typeof(MainViewModel);
        }

        #endregion
    }
}

Here we added new constructor and override LoadModules method in order to use generated code.
Now, in android project modify Setup.cs as follows:

public class Setup : AndroidBootstrapperBase
{
  #region Overrides of AndroidBootstrapperBase

  protected override IIocContainer CreateIocContainer()
  {
    return new AutofacContainer();
  }

  protected override IMvvmApplication CreateApplication()
  {
    return new Core.App(GeneratedBootstrapCode.PrecompiledBoot);
  }

  #endregion
}

In line 12 we passed GeneratedBootstrapCode.PrecompiledBoot as delegate in App constructor.
The tuning of startup performance is done. Now our app will use all features of optimized boot.

📘

Generated Code and Linker

Note that this approach fully supports linker on each platform.

📘

Generated Code and Platforms

Despite the fact we considered only android app, similar mechanism is supported by all other platforms (iOS, UWP, Xamarin.Forms).