{"id":2175,"date":"2014-11-15T22:44:14","date_gmt":"2014-11-16T03:44:14","guid":{"rendered":"https:\/\/www.tmurgent.com\/TMBlog\/?p=2175"},"modified":"2015-09-23T18:03:08","modified_gmt":"2015-09-23T22:03:08","slug":"app-v-and-net-performance","status":"publish","type":"post","link":"https:\/\/www.tmurgent.com\/TmBlog\/?p=2175","title":{"rendered":"App-V and .Net Performance"},"content":{"rendered":"<p><img decoding=\"async\" src=\"https:\/\/www.tmurgent.com\/TMBlog\/wp-content\/uploads\/2014\/12\/Performance.PNG\" alt=\"\" align=\"left\" \/><span style=\"color: #993300;\"><b>NOTE:<\/b> This post contains some great information, but has been updated<\/span> <a href=\"https:\/\/www.tmurgent.com\/TmBlog\/?p=2350\">in this post.<\/a><\/p>\n<p>This post is about the performance impacts of combining Microsoft App-V 5 sequenced virtual applications with applications that use Microsoft .Net.\u00a0 I wish this story ended with good news, or at least reasonable actions that you could take to improve performance, but it isn&#8217;t so.\u00a0 Instead it is mostly informational, however, in the end there is a technique mentioned that might be useful for a really poorly performing app. In the most typical cases\u00a0the trick\u00a0is probably too much work for the performance gains that you might receive, but maybe it can help you if you are stuck with user&#8217;s complaining about the runtime performance.\u00a0 So here is the story (so far).<\/p>\n<h2><strong>Background on .Net<\/strong><\/h2>\n<p>Like Java, Microsoft .Net programs are constructed with the concept of\u00a0&#8220;write once, run anywhere&#8221;.\u00a0 Except\u00a0anywhere means places where you have the supporting runtime.\u00a0 For Java, this means the appropriate Java Runtime (which can be placed about everywhere), and with .Net it means the appropriate .Net Framework (more limited currently, but now it might be coming to other OSs).\u00a0 But while Java may run on more CPU architectures and OS platforms,\u00a0.Net tends to have much fewer version incompatibilities.\u00a0Both programing languages achieve processor independence by compiling into a processor independent language.\u00a0\u00a0 This is called MSIL (Microsoft Independent Language)\u00a0in &#8220;.Net-speak&#8221;.<\/p>\n<p>The processor independent language is compiled into binary form by a &#8220;just-in-time&#8221; compiler, known as JITing. Both Java and .Net use JIT compilation to improve speed over interpreted languages like the older Visual Basic (the non-managed variety)\u00a0or Perl languages. While binary code will always be faster than interpretation, the overhead of JIT compiling every time the program is run is a drag on performance. Microsoft supports three forms of performance improvements for .Net programs\u00a0that the developer of the app can take advantage of.<\/p>\n<ol>\n<li><strong>Strong Named Assemblies<\/strong>. When a .Net dll is added to a system, if it was built with a StrongName it can load faster.\u00a0 A StrongName is basically a weak public digital signature that doesn&#8217;t verify authenticity but verifies that the file hasn&#8217;t been messed with.\u00a0 When dlls built with StrongNames are stored in the Global Assembly Cache, signature verification occurs only once (when placed into the cache).\u00a0 When the dll is loaded by a program into memory, the OS can perform a quick hash check that the\u00a0file hasn&#8217;t been modified since installation into the cache.\u00a0 For large dlls where you might not need all of the functionality this will improve performance by a small amount.\u00a0 To gain this performance, the StrongNamed dll must be added to the .Net Global Assembly Cache (one of the following folders under C:\\Windows\\assembly: GAC, CAG_32, GAC_64, GAC_MSIL).\u00a0 The dll placed in the Global Assembly Cache is not compiled, so gains are limited. Some .Net dlls are built without a StrongName, and these will never be added to the Global Assembly Cache.\u00a0 Adding this into the cache is typically a post-installation step (sometimes performed by the installer).<\/li>\n<li><strong>Native Compilation (post-install).<\/strong> When a .Net dll or exe is installed on a system, NGEN can be used to create a &#8220;Native Image&#8221; copy via compilation.\u00a0 This compiled copy is specific to a CPU architecture.\u00a0 Back when I attended the &#8220;Hailstorm&#8221; event in Redmond (when .Net was originally announced more than a dozen years ago), compilation to a CPU was very processor specific.\u00a0 But these days, my sources inside Microsoft inform me that we don&#8217;t need to worry about AMD versus Intel, or Xeon versus Core I*.\u00a0 Compilation is really to the bit-ness of the CPU architecture (32 versus 64 bit) and major design.\u00a0 Usually, these native compilations occur as part of the application installer as a post-installation event.\u00a0 Often, the installer designer forgets about this, and the program is just JITed all of the time.\u00a0 Post-install compilation is performed using the ngen.exe program that is part of the framework.\u00a0 The compilation may be performed synchronously, or placed into a queue for asynchronous compilation by the NGEN service whenever the system is idle for a period of time.\u00a0 When compiled, an executable named <em>foo.exe<\/em> or <em>bar.dll<\/em> will have a compiled copy placed in the windows\\assembly subfolder for the major .Net version (2 or 4, currently).\u00a0 These files are distinguished by being called <em>foo.ni.exe<\/em> or <em>bar.ni.dll<\/em>.\u00a0 These files will almost always be larger than the original file, but usually perform much faster as JIT compilation is no longer\u00a0needed at runtime.\u00a0 The system loader automatically detects if there is a Native Image available whenever the original component is loaded into memory.<\/li>\n<li><strong>Native Image Compilation (by the developer).<\/strong>\u00a0 This is a new option available to developers in Visual Studio 2014 preview for applications being developed for the Windows Store.\u00a0 Because the compilation is being performed at build time, the optimizations available are far greater.\u00a0 Among other things, this compilation pulls in the portions of framework dlls directly into the native image (sort of like embedding mfc dlls into a Win32 executable), making these Native Images independent of the .Net Framework itself.\u00a0 Of course on the flip side, these Native Images are processor specific. The result is a real exe, so special handling by the loader is not required.<\/li>\n<\/ol>\n<p>.Net compilation can improve performance significantly.\u00a0 There are no hard numbers on this, as it really depends on the program.\u00a0 Some simple tests I have run using post-installation compilation into Native Image on small programs saw a 15 to 20% improvement in launch time.\u00a0 <em>So\u00a0the potential performance gains of .Net Native Image compilations are significant enough to be interested in.<\/em><\/p>\n<h2><strong>Which brings us to the .Net and App-V discussion.<\/strong><\/h2>\n<p>When we install and capture programs in the sequencer, post-installation activity of type 1 and 2 are a possibility and may get captured.\u00a0 If captured, we are essentially redistributing these components to the clients.\u00a0 I used to think that this would break things, but never saw that happen.\u00a0 And now with the latest information from Microsoft, it seems not to be the case and redistribution in an App-V sense should be OK.<\/p>\n<p>To confirm when a native image is in use, I use ProcessExplorer to examine what is actually loaded into the process memory. Using the lower pane display to show loaded modules, you can see that both <em>AppV_Manage.exe<\/em>\u00a0 and <em>Appv_Manage.ni.exe <\/em>are loaded in the process shown in the image below:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.tmurgent.com\/TMBlog\/wp-content\/uploads\/2014\/12\/ProcExplorerNI.PNG\" alt=\"Process Explorer showing a process using a Native Image\" \/><\/p>\n<p>Note: The above capture is with AppV_Manage version 3.8 where I realized that my installer wasn&#8217;t creating the native image either.\u00a0 You will now also see the compression library has a native image also.<\/p>\n<p>In tests not using App-V, I have proven the ability to redistribute an &#8220;AnyCPU&#8221; executable that was natively compiled on an x86 system.\u00a0 Outside of App-V, I redistributed the native image to both an x86 system and an x64 system.\u00a0 The x86 target system used the Native Image (as seen in ProcessExplorer) and saw the expected performance gains.\u00a0 The x64 system did not use the Native Image compiled for 32-bit as the &#8220;AnyCPU&#8221; executable ran as 64-bit on that system, thus the performance was the same as if not compiled.\u00a0 But it wasn&#8217;t any for the worse!\u00a0 So the bottom line is that capturing the native image and redistributing might make things better and don&#8217;t seem to make things worse.<\/p>\n<p>But it turns out that we don&#8217;t often capture native images in our App-V packages.\u00a0 I recently added support to the AppV_Manage Analyzer\u00a0(version 3.8) to detect the number of modules in the Global Assembly Caches and the Native Image caches.\u00a0 An example display is shown below:<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/www.tmurgent.com\/TMBlog\/wp-content\/uploads\/2014\/12\/StatsNI.PNG\" alt=\"AppV_Manage Analyzer showing a package containing a Native Image\" \/><\/p>\n<h2>Why don&#8217;t typically we capture Native Images in our App-V packages?<\/h2>\n<p>There at least a couple of\u00a0reasons:<\/p>\n<ol>\n<li>The installer didn&#8217;t request the compilation. Depending on the installer, this may require the addition of a custom action.<\/li>\n<li>The installer requested asynchronous compilation.\u00a0 This makes the installer seem faster, but unless you let the sequencer sit around a while after the installation is complete it won&#8217;t happen in time.\u00a0 Pretty much, unless you are interrupted or go get some coffee or a smoke, the compilation won&#8217;t happen in time before you end monitoring.<\/li>\n<\/ol>\n<p>So I was thinking that I&#8217;d like to force the compilation.\u00a0 Either case can be handled by using ngen.exe which comes with the framework.\u00a0 You might have two copies, one for v2 and one for v4.\u00a0 When in doubt, use the v4 copy (it handles .Net 2 apps also).\u00a0 To solve the second issue, you only need to run the command:<\/p>\n<pre><strong>C:\\Windows\\Microsoft.Net\\Framework\\v4.0.30319\\ngen.exe ExecuteQueuedItems<\/strong><\/pre>\n<p>On a 64-bit system, you should also run the command in the Framework64 folder.<\/p>\n<p>To solve the first issue, you identify the major exe programs and run:<\/p>\n<pre style=\"width: 566px; height: 14px;\"><strong>C:\\Windows\\Microsoft.Net\\Framework\\v4.0.30319\\ngen.exe Install \"C:\\...pathto...exe\"<\/strong><\/pre>\n<p>If you compile the exe, it automatically compiles the dependent dlls, so typically you do not need to compile the dll files directly.\u00a0 Do these things while in monitoring mode, and the global and native image caches are automatically captured.<\/p>\n<h2>But does it do any good to do this in your packages?<\/h2>\n<p>Unfortunately, NO.<strong><br \/>\n<\/strong><br \/>\nCurrently (as of App-V 5.0 SP2 with Hotfix 5 on a Windows 7\/8.1 system with .Net 4 through 4.5.2), the loader cannot detect the native images that are inside the App-V package.\u00a0Nothing is broken, it is just that the VFS caches for .Net are not used.\u00a0 Some simple testing indicates that it is not a timing issue.\u00a0 There is an undocumented index file which might play a role in this, but I think it is a deeper issue.<\/p>\n<h2>Potential Workaround?<\/h2>\n<p>In theory, if you had a really badly performing virtual application, you could use a Publish-Package script to force an ngen compilation against the major exe(s).\u00a0 You would have to create the package using the forced load for the streaming configuration, and then run this script outside of the virtual environment using the ProgramData reference to the exe.\u00a0 If you had a really horrible app, it might be worth a try.\u00a0 Keep in mind that you might not be able to\u00a0have multiple versions of the same app with improved performance with different Native Images if done this way &#8211; it depends on the app.<\/p>\n<p>I am reporting this into Microsoft for consideration.\u00a0 So the story could have a happy ending one day after all.\u00a0 Maybe.<\/p>\n<h2>Addendum<\/h2>\n<p>I should mention that while I only tested Microsoft App-V 5.0 SP2 (with Hotfix 5) in preparation of this article, it probably applies to any technology used to virtualize or layer applications.\u00a0 Ultimately, I believe any filter driver, or user-mode dll injection,\u00a0that adds application components to the OS will fail to deliver the native components in a way that the OS will use as intended.\u00a0 This includes other application virtualization tools (like Symantec or VMWare ThinApp), and application layering solutions (like UniDesk, and VMWare AppVolumes, but maybe not FsLogix which should work as is).\u00a0 At least with App-V I know that we can work around it, but I&#8217;m guessing that due to their architectures, some of the others cannot.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>NOTE: This post contains some great information, but has been updated in this post. This post is about the performance impacts of combining Microsoft App-V 5 sequenced virtual applications with applications that use Microsoft .Net.\u00a0 I wish this story ended with good news, or at least reasonable actions that you could take to improve performance,&hellip; <a class=\"more-link\" href=\"https:\/\/www.tmurgent.com\/TmBlog\/?p=2175\">Continue reading <span class=\"screen-reader-text\">App-V and .Net Performance<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_exactmetrics_skip_tracking":false,"_exactmetrics_sitenote_active":false,"_exactmetrics_sitenote_note":"","_exactmetrics_sitenote_category":0,"footnotes":""},"categories":[47,49,48],"tags":[40,31,41,28],"class_list":["post-2175","post","type-post","status-publish","format-standard","hentry","category-appv5","category-performance","category-sequencing","tag-net","tag-appv5","tag-ngen","tag-performance","entry"],"_links":{"self":[{"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=\/wp\/v2\/posts\/2175","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2175"}],"version-history":[{"count":37,"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=\/wp\/v2\/posts\/2175\/revisions"}],"predecessor-version":[{"id":2356,"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=\/wp\/v2\/posts\/2175\/revisions\/2356"}],"wp:attachment":[{"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2175"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2175"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tmurgent.com\/TmBlog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2175"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}