Go Back

Speeding up synergy.net WPF application load time

I have a Sales Order Processing synergy.net WPF appplication which currently takes around 30 seconds to get to the menu when I run it on my 4 process, 32GB ram, SSD disk development machine.  It takes longer on the client's production server.

Of this, 10 seconds is loading IG components and themes and my application DLLs.  10 seconds is spent loading Microsoft.Xaml.Behaviors.dll  (which allows me to use arrow keys and ENTER key when entering directly into IG data grids).  I display a splash sccreen while this happens.  Then my application starts, clears the splash screen and waits another 10 second before the menu is displayed.

The DLLs and the EXE just sit in a folder and I start teh application via a BAT file.   Would installing the application improve on this start up behaviour ?  I don't know what "installing an application" actually means.

During the 10 seconds of getting to the menu I see via debug that it works through all the visual states and the view models they reference and use, even though the user is never going to use them all in a given session.  These are equivalent to programs -  customer maintenance, product maintenance, order entry, etc.  Is there a way to only load these on demand, ie when navigating to a visual state rather than doing it all up front at the start ? 

Even though I Iam using synergy.net, I'm sure the same principles apply in C#, so happy for help from that front too
 

18 Answers
0   | Posted by Gordon Ireland to Synergy .NET on 8/18/2020 1:09 PM
Ace Olszowka
Did you install these DLL's into the GAC? Or if you cannot do that did you at least NGEN (https://docs.microsoft.com/en-us/dotnet/framework/tools/ngen-exe-native-image-generator) them? That will improve startup time, but WPF does a lot of stuff at runtime, it very well maybe that it is just that slow.

When stuff gets registered into the GAC it is implicitly NGEN'ed which is why historically desktop developers didn't see much of a hit in startup, but in the brave new world of XCOPY Deploy you gotta manually do it.

8/20/2020 9:45 PM   0  
Gordon Ireland
I am quite a simple soul  I just drop this folder with its DLLs and EXE on the server and have a BAT file which sets logicals and runs the EXE

I have menu.exe which references 10 projects which each have their own DLLs, plus there are a heap of other DLLs via Nuget and from Infragistics

The DLLs I produce change daily, but the others are very static.

There are 126 files in my software folder.

Where would I start with NGEN or using the GAC ?

Is there a prcoess I would run  everytime I wanted to do an install ?   

What do I actually install on site ?


My software  has a version number and each version is installed in a different folder per version, with the BAT file pointing to the latest.  This allows me to install new software without getting users out of the system.  The application checks if the running version is still compatible with the latest install and forces the users to exit and load the latest.  Would that have a bearing ?


.folder screenshot

8/21/2020 9:36 AM   0  
Steve Ives
You should consider building an installer for the product and have it do the NGEN for you during install. You may be surprised how easy it is to do. If you want to know more, here's a video from the 2016 conference that might help get you up and running quickly.

https://www.youtube.com/watch?v=usOh3NQO9Ms

8/27/2020 5:25 PM   0  
Gordon Ireland
I created a Wix installer and it has had a small impact - it now takes around 48 seconds to load instead of 55.

It is built for 64 bit and installed in ProgramFiles - it uses the synPDF 64 bit DLLs, so I keep the rest of the application as 64 bit

So my assemblies are now NGEN'd.

Would putting the DLLs in GAC help ?  If so, is this something Wix can do ?

What is the benefit of using GAC ?

 

9/1/2020 6:34 PM   0  
Gordon Ireland
Has anyone actually installed their DLLs/EXEs in GAC ?

When I google this the advice seems to be this can open a whole can of worms and it is best avoided, but maybe that's if you want to share components between different applications.

I just have a single application I would want to do this with  

I now have a WIX intstaller for my application and I NGEN the DLLs adn EXEs but that doesn't make a lot of difference
 

9/4/2020 8:45 AM   0  
Gordon Ireland
I found the main reason why my application was so slow at loading and took around 30 seconds off the time.    Of the remaining 20 seconds, 10 seconds is spent loading Microsoft.xaml.Behaviors which gives be control of ENTER adn arrow keys when keying directly into a data grid.   I have emailed the team who look after it to see if they have any suggestions.

My issue was thath I was setting the user's selected Infragistics theme in the base view model which every application VM extends.  It only takes 1/2 to 1 second to set this, but when this was multipled by 30 or more view models it was noticeable.

The IG theme itself only needs set once, but I still need to do thing like decide if they need light or dark icons and set the title colour, border colour and window background colour which aren't controlled by the IG theme.

SO I still call my setTheme() method, but within that I check  a static public property to see if it has been set already

 Any advice on speeding up the Microsoft.xaml.Behaviors.dll welcome - it is already included in my WIX installer and NGENd


 

9/7/2020 4:24 PM   0  
Steve Ives
Glad to hear you were able to build a WiX installer, but sorry to hear that NGEN didn't resolve your issue. Sounds like you're on the right track by contacting the MS dev team for the offending assembly.
One  other thing you could try is to use the Fusion Log Viewer utility, which gives you some visibility of which assemblies are being loaded and how long each takes. Maybe the issue is not with the actual Microsoft.xaml.Behaviors assembly, but one of it's dependencies? If so, this log may give you some visibility into that.
You simply enable logging, then run the app. And don't forget to disable logging again. This is what I see if I enable logging then run CodeGen, just by way of an example of a .NET app. As you can see there are time stamps next to each loaded assembly. Only down to the nearest second, but if you're looking for a missing 10 seconds maybe that will be granular enough. If not, maybe try looking in the actual log file on disk
User-added image
 

9/8/2020 5:45 PM   0  
Ace Olszowka
FWIW they've open sourced WPF https://github.com/dotnet/wpf

However as per their README.md:

.NET Framework issues

Issues with .NET Framework, including WPF, should be filed on VS developer community, or Product Support. They should not be filed on this repo.

Anything reported to VS Developer Community at some point ends up on their GitHub page, so basically you get the run around.

I haven't had much luck with them (See https://github.com/dotnet/wpf/issues/460 originally opened up here: https://developercommunity.visualstudio.com/content/problem/503048/wpf-application-always-rebuilds-in-visual-studio.html) but its worth a shot.

 


9/8/2020 5:55 PM   0  
Gordon Ireland
Oh, so I'm not the only one loses   3 or 4 minutes of my life every time I try to run my WPF app in VS even if it hasn't changed at all ?

Usually - sometimes it knows just to run it. Most times it wants to rebuild it

9/9/2020 8:17 AM   0  
Gordon Ireland
I ran Fusion Log Viewer

I don't know if the time shown is start time or end time.  And I was confused that the times were not shown in time sequence

If I assume these are end times, and they are loaded in the orer shown in the VS output window, and that the VS output window shows when the DLL is actually loaded, then it looks like my project DLLs take a second or 2 each. 
The Microsft.Xaml.Behaviors takes 1 or 2 seconds, but the next DLL does not show as loaded until 10 to 12 seconds later.

That DLL is a project DLL which is one of my simplest and smallest.
But incas etheye was something odd in it, I removed it from the solution and  found that the next project DLL in the start up sequence was taking 10 to 12 seconds before it said it was loaded, even though it had loaded instantly before I removed a project.

SO I am not sure exactly what stage the loading of DLLs i sat when the Fusion log shows them and teh VS output window shows them.


Fusion Log Viewer
I did notice something strange with my project DLLs which have UI in them, and with most but not all of the Infragistics DLLs.   The actual DLLs load fine, but it tries to load 2 other resource DLLs for each, and these fail.
So if my project DLL is SalesOrders.dll, it will try to load SalesOrders.resources.DLL   (language =en.GB) and SalesOders.resources.DLL (Language = en.) before successfully loading SalesOrders.DLL.  All this happens in teh same second in the log, so it can't be adding much of an overhead.

Microsft.Xaml.Behaviors.DLL
The only odd thing I noticed was that every other Microsoft or System DLL is loaded from GAC, except this.  SHould I put this in GAC, and if so, how?


 

9/9/2020 11:20 AM   0  
Gordon Ireland
In case you are wondering where Microsoft.Xaml.Behaviors.DLL came from, it in installed as a Nuget package, latest version, which is 8 months old
 

9/9/2020 2:55 PM   0  
Ace Olszowka
> SHould I put this in GAC, and if so, how?

Historically the rule of thumb has been never put something into the GAC unless you own it.

Third party assemblies tend to be a grey area, in general most Microsoft shipped assemblies are already registered in the GAC.

In this particular case it looks like Microsoft has actually open sourced Microsoft.Xaml.Behaviors (https://github.com/microsoft/XamlBehaviorsWpf). Meaning that this would most likely be treated as a third party dependency (hence the grey area).

There are a couple of rules to putting something in the GAC:

1. It should be strong named (https://docs.microsoft.com/en-us/dotnet/standard/assembly/strong-named)
2. It should follow best practices for assembly versioning (https://docs.microsoft.com/en-us/dotnet/standard/assembly/versioning)

Both of these pieces are critical to having the GAC work as designed. To understand why, you need to understand what the GAC was designed to solve.

You probably have been around windows long enough to know of DLL Hell (https://en.wikipedia.org/wiki/DLL_Hell), but if not the basic idea is that by default LoadLibrary and its kin utilize the Dynamic-Link Library Search Order (https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order). Historically Windows, when asked to load a dynamic link library (DLL) would probe several well know locations, first starting with the directory from which the program is executing and then searching the PATH environment variable.

Poor programming practices historically meant that developers would shove their binaries into a location that was guaranteed to be in the default Path (usually C:\Windows or C:\Windows\System32) you can see this behavior in Synergex itself for example with the usage of the OpenSSL Libraries here https://www.synergex.com/docs/versions/v111/index.htm#icg/icgChap6Opensslrequirements.htm#Windows, historical versions of this document had you placing these DLL's in one of these common locations.

The problem with this is many times fold, in the worst case scenario this is a security issue because if I have control of the path I can exploit the security primitive http://cwe.mitre.org/data/definitions/426.html, for example CONNECTDIR in some versions of Synergy are vulnerable (see Case #099490). In the more common case however people will willy nilly override existing DLL's with either newer or older versions, allowing their applications to work, but others to break due to incompatibilities.

The GAC set out to solve this problem by allowing multiple versions of a DLL to exist on the same system, and then have a program (in this case the Fusion Loader https://stackoverflow.com/questions/9961162/what-is-fusion-in-net-assembly), determine based on a bit of information which assembly to load.

Defense against DLL Hell comes from two properties:

1. The Strong Name - this is a crytograhically secure signing mechanism that allows your assembly to have a unique name
2. The Assembly Version - Breaking changes should increment this number

In practice however this fell apart due to the poor quality of programmers available in industry who failed to recognize why this was put in place. For example:

1. The Strong Name - many projects (especially open sourced ones) simply posted their Strong Name key files, such that anyone could sign any binary with this strong name. In a lot of cases this was done for pragmatic reasons as you need the ability to recreate the binary to drop it on your system.
2. The Assembly Version - Many developers  did not understand the exact rules for proper versioning and either didn't provide a version, or provided a version so useless that the system broke.

The version in particular is nasty, this is because the GAC is a reference counted storage system. To give a concrete example:
  • Assume I have an Assembly: MyCoolAssembly.dll
  • I have strong named this assembly
  • I have assigned version 1.0.0
  • I have registered it in the GAC
Now assume that I have discovered a bug in MyCoolAssembly.dll, have fixed the bug, but I did not bump the version.

If I try to register this *new* MyCoolAssembly.dll in a GAC that has the previous version of MyCoolAssembly.dll with the same version I WILL NOT get the new version of MyCoolAssembly.dll, rather the existing (*old* with bug) version of MyCoolAssembly.dll will have its reference count increased by 1.

The logic of this was that if everyone followed the rules you could save disk space because if the strong name and version were identical you were telling Fusion that the assembly was in fact identical and should not be replaced.

In practice this is not what most developers wanted to have happen (there are a ton of reasons for this and I can explain if you want, the short is: when you bump a version you have to recompile EVERYONE that depended upon it -OR- create Asssembly Binding redirects to point to the new version, which was error prone and pretty much impossible for other libraries).

So lets bring this back to your question:

> SHould I put this in GAC, and if so, how?

Chances are no, mostly because you do not own this assembly, and therefore cannot make the guarantees above. However I can tell you in practice for us, because we have a lot of control over the boxes we will often times register these in the GAC, especially if there is a performance gain.

I would recommend to you to try it, and see if it helps any, then base your decision off of that, realizing the issues I set forth above are very real and must be accounted for.

As far as how to put it in the GAC, you should be putting it in via an Installer, this is the only supported way to do this. There is an unsupported way, using developer tools (specifically gacutil
https://docs.microsoft.com/en-us/dotnet/framework/tools/gacutil-exe-gac-tool) that you should never use in production. The reason is that gacutil will happily remove reference counts from assemblies which it did not put there. For example an MSI increasing reference count. You can leave a machine's GAC in a bad state if you are not careful.


 

9/9/2020 2:56 PM   0  
Gordon Ireland
Thanks Ace for your very detailed comprehensive answer.  I really appreciate the time taken to explain this so clearly

I do have a Wix installer for the software
DO I just need to add a line to this to install the DLL ?

This is currently defined as a component as below :

        <!-- Install Microsoft.Xaml.Behaviors DLL -->
            <Component Id="Microsoft.Xaml.Behaviors.dll" Guid="*" Win64="yes" Directory="INSTALLFOLDER">
                <File Id="Microsoft.Xaml.Behaviors.dll" KeyPath="yes" Source="C:\Projects\TRS\TRSMenu-Live\TRSMenu\bin\x64\Release\Microsoft.Xaml.Behaviors.dll" Checksum="yes">
                    <netfx:NativeImage Id="Microsoft.Xaml.Behaviors.dll" Platform="64bit" Priority="0" AppBaseDirectory="INSTALLFOLDER"/>
                </File>
            </Component>

9/9/2020 3:03 PM   0  
Ace Olszowka
It has been a very long time since I have written WiX (that was given to another group at CU), but that does not look correct to me, here is a snippet (with our GUID removed):
 
<Component Id="ComputersUnlimited.Manager.Roadnet.Contracts.dll" Directory="GAC" Guid="{XXXXXX-XXXX-XXX-XXX-XXXXX}">
  <File Id="ComputersUnlimited.Manager.Roadnet.Contracts.dll" KeyPath="yes" Source="$(env.TIMSSource)\GAC\ComputersUnlimited.Manager.Roadnet.Contracts.dll" Assembly=".net">
    <netfx:NativeImage Id="ComputersUnlimited.Manager.Roadnet.Contracts.dll" Platform="all" Priority="3" />
  </File>
</Component>

You appear to be missing the GAC as the directory for this to be installed to. the rest looks pretty close (if you are attempting to push something into the GAC).

Furthermore it looks like you are not specifying your GUID's while this will work for one off's this will make it impossible for you to generate an MSP (https://docs.microsoft.com/en-us/windows/win32/msi/patch-packages) because you are letting WiX Generate the GUID for the component in a non-deterministic manner which is not good.

I am going to take this as an aside to again advocate that Synergex should either produce MSP's or full blown MSI's for when they ship hot fixes.
 

9/9/2020 3:10 PM   0  
Gordon Ireland
Thanks Ace.

My installer does not put things in GAC.  Thats the bit I need to try.

I am using automatic GUIDs as I am creating a new application each time, rathert than updating the existing one.

This allows users to run TRSMenu 107 while I install TRSMenu 108 and then change the BAT file they use to start the application to reference 108 instead of 107.

So no-one needs to log out of the application while I install a new version, and they automatically get teh latest vesrion everytime they start up.

I have ways of forcing them to log out.

So i am not interested in the GUIDs

9/9/2020 3:28 PM   0  
Gordon Ireland
How do you define GAC in your Wix file ?

INSTALLFOLDER, where everything else goes, is defined as 

    <Fragment>
        <Directory Id="TARGETDIR" Name="SourceDir">
            <Directory Id="ProgramFiles64Folder">
                <Directory Id="MANUFACTURERFOLDER" Name="!(bind.property.Manufacturer)">
                    <Directory Id="INSTALLFOLDER" Name="!(bind.property.ProductName)" />
                </Directory>
            </Directory>
        </Directory>
    </Fragment>


 

9/9/2020 3:56 PM   0  
Ace Olszowka
<Fragment>
  <DirectoryRef Id="INSTALLDIR">
    <Directory Id="GAC" Name="GAC">
    </Directory>
  </DirectoryRef>
</Fragment>

 

9/9/2020 4:02 PM   0  
Gordon Ireland
Thanks Ace!

My installer now installs Microsoft.Xaml.Behaviors.DLL in GAC.

It is not installed as a separate DLL in my application folder after install, and everything runs fine.

Note sur if it has speeded things up much, but on the faster production server it now takes 15 seconds to load with the splash screen, and 3 to 5 seconds for my application to get to a menu, and it has to initialise all the VMs and work out security for around 30 separate menu options.

That used to take 45 seconds before optimising the setting of IG theme, NGEN'ing, installing and throwing the big DLL into GAC
 

9/9/2020 6:50 PM   0  
Please log in to comment or answer this question.