.NET 2.0 Performance Guidelines - What's New in 2.0

From Guidance Share

Jump to: navigation, search

Contents

Use TryParse Method to Avoid Unnecessary Exceptions

What to Do

Use TryParse Method instead of Parse Method for converting string input to a valid .Net data type. For example, use Int32.TryParse method before converting a string Input to integer data type.

Why

The Parse method will throw exception - ArgumentNullexception or FormatException or OverflowException, if the string representation cannot be converted to the respective data type.

Unnecessary Throwing Exceptions and Handling the same such as above has a negative impact on the performance of the application. The TryParse method does not throw an exception if the conversion fails instead it returns false, and hence saves exception handling related performance hit.

When

If it is required to convert a string representation of a data type to a valid .Net data type, use TryParse method instead of calling the Parse method to avoid unnecessary exception.

How

The following code snippet illustrates how to use TryParse method :

...
Int32 intResult;
if (Int32.TryParse(strData, intResult))
{
   // process intResult result
}
else
{
  //error handling
}
...

Problem Example

Consider a Windows Forms application for creating an Invoice. The application takes user inputs for multiple items as product name, quantity, price per unit and date of purchase. The user provides these inputs in the text boxes. The user can enter multiple items in an invoice at a given time and then finally submit the data for automatic billing calculation. The application internally needs to convert the string input data to integer (assume for simplicity). If the user enters invalid data in the text box, the system will throw an exception. This has adverse impact on performance of the application.

...
private Int32 ConvertToInt(string strData)
{
       try
       {
             return Int32.Parse(strData);
       }
       catch (exception ex)
       {
             return 0; //some default value 
       }
}
... 


Solution Example

Consider a Windows Forms application for creating an Invoice. The application takes user inputs for multiple items as product name, qunatity, price per unit and date of purchase. The user provides these inputs in the text boxes. The user can enter multiple items in an invoice at a given time and then finally submit the data for automatic billing calculation. The application internally needs to convert the string input data to integer (assume for simplicity). If the user enters invalid data in the text box, the system will not throw unnecessary exceptions and hence improves the application performance.

...
private Int32 ConvertToInt(string strData)
{
    Int32 intResult;
    if (Int32.TryParse(strData, intResult))
    {
        return intResult;
    }
     return o;  //some default value
}
... 

Additional Resources


Use Token Handle Resolution API to get the Metadata for Reflection

What to Do

Use new .Net 2.0 token handle resolution API RuntimeMethodHandle to get the Metadata of members while using Reflection.

Why

The RuntimeMethodHandle is a small, lightweight structure that defines the identity of a member. RuntimeMethodHandle is trimmed down version of MemberInfos, which provides the metadata for the methods and data without consuming .Net 2.0 back-end cache.

The .NET Framework also provides GetXxx type of API methods for e.g.. GetMethod, GetProperty, GetEvent to determine the metadata of given Type at runtime. There are two forms of these APIs, the non-plural, which return one MemberInfo (such as GetMethod), and the plural APIs (such as GetMethods).

The .Net framework implements a back-end cache for the MemberInfo metadata to improve the performance of GetXxx API. The caching policy is implemented irrespective of plural or non-plural GetXxx API call. Such eager caching policy degrades the performance on calls to or non-plural GetXxx API calls. RuntimeMethodHandle works approximately twice faster than compared to equivalent GetXxx API call, if the MemberInfo is not present in the back-end .Net cache.

When

If it is required to get the metadata of a given Type at runtime, use new .Net 2.0 token handle resolution API RuntimeMethodHandle for better performance then traditional GetXxx API calls.

How

The following code snippet shows how to get the RuntimeMethodHandle:

...
// Obtaining a Handle from an MemberInfo
RuntimeMethodHandle handle = typeof(D).GetMethod("MyMethod").MethodHandle;
...

The following code snippet shows how to get the MemeberInfo metadata from the handle:

...
// Resolving the Handle back to the MemberInfo
MethodBase mb = MethodInfo.GetMethodFromHandle(handle);
...

Problem Example

A Windows Forms based application needs to dynamically load the plug-in Assemblies and available Types. The application also needs to determine the metadata of a given Type (methods, members etc) at runtime to execute Reflection calls.

The plug-in exposes a Type CustomToolBar, which is derived from Type BaseToolBar. The CustomToolBar Type has 2 methods - PrepareCommand, ExecuteCommand. The BaseToolBar Type has 3 methods - Initialize, ExecuteCommand and CleanUp. To execute the ExecuteCommand method of type CustomToolBar at runtime, it gets the metadata of that method using GetXxx API as shown in the following code snippet.

Since .Net Framework implements eager caching policy, the call to get the metadata for a single ExecuteCommand method will also get the metadata of all the five methods of CustomToolBar and BaseToolBarTypes.

MethodInfo mi = typeof(CustomToolBar).GetMethod("ExecuteCommand");

The .Net framework implements a back-end cache for the MemberInfo metadata to improve the performance of GetXxx API. The implemented caching policy caches all members by default, irrespective of plural or non-plural API call. Such eager caching policy degrades the performance on calls to or non-plural GetXxx API calls.

Solution Example

A Windows Forms based application needs to dynamically load the plug-in Assemblies and available Types. The application also needs to determine the metadata of a given Type (methods, members etc) at runtime to execute Reflection calls.

The plug-in exposes a Type CustomToolBar, which is derived from Type BaseToolBar. The CustomToolBar Type has 2 methods - PrepareCommand, ExecuteCommand. The BaseToolBar Type has 3 methods - Initialize, ExecuteCommand and CleanUp. To execute the ExecuteCommand method of type CustomToolBar at runtime, it gets the metadata of that method using RuntimeMethodHandle is used, as shown in the following code snippet. This can improve the performance of the application. :

...
// Obtaining a Handle from an MemberInfo
RuntimeMethodHandle handle = typeof(CustomToolBar).GetMethod("ExecuteComand").MethodHandle;
// Resolving the Handle back to the MemberInfo
MethodBase mb = MethodInfo.GetMethodFromHandle(handle);
...

If the appropriate MemberInfo is already in the back-end .Net cache, the cost of going from a handle to a MemberInfo is about the same as using one of the GetXxx API call. But if the MemberInfo is not available in the cache RuntimeMethodHandle approximately twice faster then GetXxx API call.

Additional Resources

Use Generics To Eliminate Cost Of Boxing, Casting and Virtual calls

What to Do

Use Generics to eliminate cost of boxing, casting and virtual calls

Why

Generics feature can be used to improve the performance by avoiding runtime boxing, casting and virtual calls.

  • List<Object> class gives better performance over ArrayList
  • Current benchmark of a quick-sort of an array of one million integers shows the generic method is 3 times faster than the non-generic equivalent. This is because boxing of the values is avoided completely. The quick-sort of an array of one million string references with the generic method is 20 percent faster due to the absence of a need to perform type checking at run time.
  • Other benefits of using Generics are compile-time type checking, binary code reuse and clarity of the code.

When

Use Generics feature for defining a code (class, structure, interface, method, or delegate) which has to be used by different consumers with different types. Consider replacing a generic code (class, structure, interface, method, or delegate), which does implicit casting of any type to System.Object and forces the consuming code to cast between Object references to actual data types

  • Only if there are considerable amount of number (>500) to store consider having a special class, otherwise just use the default List<Object> class.
  • List<Object> gives better performance over ArrayList as List<Object> has a better internal implementation especially for enumeration.

How

Following are the steps for using generics for various types.

  • Define a generic class as follows.
public class List<T> 
  • Define methods and variables in generic class as follows.
public class List<T> 
{
  private T[] elements;
  private int count;   
  public void Add(T element)
  {
  	if (count == elements.Length) 
  	   Resize(count * 2);
  	elements[count++] = element;
  }   
  public T this[int index] 
  {      
       get { return elements[index]; }      
       set { elements[index] = value; }   
  }
}
  • Access generic class with required type as follows
List<int> intList = new List<int>();
intList.Add(1);
........
int i = intList[0]; 

Note The .NET Framework 2.0 provides a suite of generic collection classes in the class library. But applications can further benefit from generics by defining their own generic code

Problem Example

An Order Management Application stores the domain data Item, Price, etc in Cache using ArrayList. ArrayList accepts any type of data and implicitly casts into objects. Numeric data like Order number, customer id, etc are wrapped to object type from primitive types (boxing) while storing in the ArrayList. Consumer code has to explicitly cast the data from Object type to specific data type while retrieving from the ArrayList. Boxing and un-boxing requires lot of operations like memory allocation, memory copy & garbage collection which in turn reduces the performance of the application.

Example code snippet to add items to ArrayList or to get/set items from the ArrayList

ArrayList intList = new ArrayList();

//Cache data in to array 

intList.Add(45672);           // Argument is boxed
intList.Add(45673);           // Argument is boxed

//Retrieve data from cache
int orderId = (int)intList.Item[0];  // Explicit un-boxing & casting required

Solution Example

An Order Management Application stores the domain data Item, Price, etc in Cache. Using Generics feature avoids necessity of run time boxing & casting requirements and makes sure of compile time type checking

  • Implement the defining code for generic class. Generic class can be implemented only if required, else default List<T> class can be used
//Use  to allow consumer code to specify the required type

class OrderList{

//Consumer specific type array to hold the data
T[] elements;
int count;

//No implicit casting while adding to array
public void Add(T element) {

//Add data to array as an object
elements[count++] = element;
}

// Method to set or get data
public T this[int index] {
      
//Returns data as T type
     get { return elements[index]; }

//Set the data as T type
     set { elements[index] = value; }
  }
}
  • Initiate the class with specifying as int data type
OrderList intList = new OrderList();

//Cache data in to array
intList.Add(45672);           // No boxing required
intList.Add(45673);           // No boxing required

//Retrieve data from cache
int orderId = intList[0];  // Un-boxing & casting not required

Additional Resources


Use GC.AddMemoryPressure while consuming Unmanaged Objects through COM Interop

What to Do

Use .Net 2.0 CLR API GC.AddMemoryPressure and GC.RemoveMemoryPressure while consuming the Unmanaged Objects from Managed Code through COM Interop.

Why

The garbage collector cannot track the Memory allocated by Unmanaged Code, it only tracks the Memory allocated by Managed Code.

If there is a large amount of memory allocation (such as images or video data) in the unmanaged code, the GC will be able to see only the reference of unmanaged objects, but not the size of the memory occupied by the unmanaged object references. Since GC may be unaware of the large memory allocation within unmanaged code, the GC will not know that a collection should be executed, as it does not realize any "memory pressure" to cause a collection. The GC.AddMemoryPressure method can be used to inform GC about how much unmanaged memory a managed object will be referencing, when it consumes the Unmanaged Objects from Managed Code. The pro-active indication to GC improves the GC heuristics and collection algorithm, which improves memory management and performance.

When

If the Unmanaged Objects invoked from Managed Code allocates a large amount of unmanaged memory at runtime, the GC should be informed about the total memory consumed by the managed and unmanaged code, by invoking GC.AddMemoryPressure method, to improve memory management of GC.

How

Applying memory pressure is the technique using which the GC can be informed about the memory allocation that might be performed by the unmanaged code within a managed code wrapper.

The GC.AddMemoryPressure should be used to inform GC about probable memory allocation by the unmanaged code:

Bitmap (string path )
{
   _size = new FileInfo(path).Length;
   GC.AddMemoryPressure(_size);
   // other work
}

The GC.RemoveMemoryPressure should be used to inform GC to remove memory pressure while destroying the wrapper managed object which was consuming the unmanaged objects:

~Bitmap()
{
   GC.RemoveMemoryPressure(_size);
   // other clean up code
}

Note For every GC.AddMemeoryPressure call, there must be a matching GC.RemoveMemoryPressure call which will remove exactly same amount of memory pressure as added earlier. Failing to do so can adversely affect the performance of the system in applications that run for long periods of time.

Problem Example

A .Net 2.0 application uses unmanaged objects that allocate large amount of memory at runtime to load images and video data.

It actually uses unmanaged memory and occupies large amount of system memory. Since garbage collector can not track the Memory allocated by Unmanaged Code, it might so happen that the application might be running on low on memory without triggering garbage collection, which will degrade the performance of the application.

class Bitmap
{
  private long _size;
  Bitmap (string path )
  {
     _size = new FileInfo(path).Length;
      // other work
  }
  ~Bitmap()
  {
     // other work
  }
}

Solution Example

A .Net 2.0 application uses unmanaged objects that allocate large amount of memory at runtime to load images and video data.

Use of GC.AddMemoryPressure and GC.RemoveMemoryPressure informs about the memory allocation and deallocation that might be performed by the unmanaged code within a managed code wrapper. Hence GC will know to perform a collection when there is a memory pressure and thus will work efficiently.

class Bitmap
{
  private long _size;
  Bitmap (string path )
  {
     _size = new FileInfo(path).Length;
     GC.AddMemoryPressure(_size);
     // other work
  }
  ~Bitmap()
  {
     GC.RemoveMemoryPressure(_size);
     // other work
  }
}

Additional Resources


Use Promotable Transactions while Working with SQL Server 2005

What to Do

Use new .Net 2.0 System.Transactions API for controlling transactions in the managed code while working with SQL Server 2005

Why

System.Transactions API gives a flexibility to shift between local database server transactions and distributed database server transactions. Systems.Transactions API when used with SQL Server 2005, consumes Promotable Transactions feature by taking advantage of Lightweight Transactions Manager. It does not create a distributed transaction when not required that results in better performance.

Note If System.Transactions API is used to manage local transactions on SQL Server 2000, the local transactions automatically promoted to the distributed transactions managed by MSDTC. The SQL Server 2000 does not support Promotable Transactions so it degrades the performance.

When

If it is required to control transactions in the managed code while working with SQL Server 2005, use Systems.Transactions API for improving performance and flexibility.

This guideline should not be used when working with SQL Server 2000, as it degrades the performance.

How

Following are the steps for using "Promotable Transactions".

While using the System.Transaction API is to define Transaction Scope using TransactionScope Class, it defines the boundary for the required transactions.

...
using (TransactionScope scope = new TransactionScope())
{
   ...
   // Open Connection and Execute Statements
   ...
   scope.Complete();
}
...

Within Transaction Scope block use normal ADO.Net code for executing the statements using Connection, Command and Execute methods. If the transaction is successful, invoke TransactionScope.Complete method. If the transaction is unsuccessful, the transaction will be automatically Rollback as it will not execute TransactionScope.Complete in the program flow.

Problem Example

A web application for Online Shopping, provides a user interface to purchase items. Once the items are purchased, the item entry should be added for billing in the Billing database table on SQL server 2005. At the same time, the stock of the item should be reduced by number of units sold in the Item Quantity database table. The entire operation needs to be performed in single transaction to maintain data integrity.

The application follows traditional approach of using SqlTransaction API which enforces only local transactions. If the distributed database transactions are required, the code has to be changed and compiled again. This breaks the principle of flexibility and agility in the design. The following code illustrates the problem, which forces local transactions and compromises flexibility to change it to distributed transactions:

...
using (SqlConnection conn = new SqlConnection(dbConnStr))
{
    conn.Open();
    SqlCommand cmd = conn.CreateCommand();
    SqlTransaction trans;

    // Start a local transaction.
    trans = conn.BeginTransaction("NewTransaction");

    cmd.Connection = conn; 
    cmd.Transaction = transaction;

    try
    {
         cmd.CommandText = "Insert Statement...";
         cmd.ExecuteNonQuery();
         cmd.CommandText = "Update Statement...";
         cmd.ExecuteNonQuery();

         // Attempt to commit the transaction.
         trans.Commit();
     }
     catch (Exception ex)
     {
           // Attempt to roll back the transaction.
           try
           {
               trans.Rollback();
           }
           catch (Exception ex2)
           {
               // handle any errors that may have occurred             
           }
       }
}
...

Instead, if the new System.Transactions API is used, it supports distributed transactions also.

Solution Example

A web application for Online Shopping, provides a user interface to purchase items. Once the items are purchased, the item entry should be added for billing in the Billing database table on SQL server 2005. At the same time, the stock of the item should be reduced by number of units sold in the Item Quantity database table. The entire operation needs to be performed in single transaction to maintain data integrity. The new System.Transactions API is used to provide flexibility without compromise on performance. System.Transactions API has a feature of Promotable Transactions when used with SQL Server 2005. It determines the need of using distributed transactions or local transactions at runtime for better performance:

...
using (TransactionScope scope = new TransactionScope())
{
   using (SqlConnection conn = new SqlConnection(dbConnStr))
   {
       SqlCommand cmd1 = conn.CreateCommand();
       cmd1.CommandText = "Insert Statement....";
    
       SqlCommand cmd2 = conn.CreateCommand();
       cmd2.CommandText = "Update Statement....";

       conn.Open();
       cmd1.ExecuteNonQuery();

       cmd2.ExecuteNonQuery():
       conn.Close();
   }
    scope.Complete();
}
...

Additional Resources


Use HandleCollector API while Managing Expensive Unmanaged Resource Handles

What to Do

Use HandleCollector API while managing expensive Unmanaged Resource Handles from Managed Code using COM Interop.

Why

The HandleCollector API helps to optimize Garbage Collector efficiency while working with expensive unmanaged resource handles. The garbage collector cannot track the Memory allocated by Unmanaged Code, it can lead to un-optimized memory management by garbage collector. The HandleCollector can force garbage collection if the threshold number of handles are reached, thereby improves performance of the application.

When

If it is required to manage multiple Unmanaged Resource Handles in the application, it is recommended to use HandleCollector API to improve GC efficiency by ensuring the objects are destroyed seamlessly on-time.

How

Create an instance of HandleCollector by providing three parameters Handle Name (string), Initial Threshold (int) and Maximum Threshold (int). The initial threshold is the the point at which GC can start performing garbage collection. The maximum threshold is the point at which GC must perform garbage collection.

...
static readonly HandleCollector appGuiHandleCollector = new HandleCollector( “ApplicationGUIHandles”, 5, 30);
...

When application creates an expensive handle, application should increase the total handle count by invoking Add method:

...
static IntPtr CreateBrush() 
{
       IntPtr handle = CreateSolidBrush(...);
       appGuiHandleCollector.Add(); 
      return handle;
}
...

When application destroys an expensive handle, application should decrese the total handle count by invoking Remove method:

...
internal static void DeleteSolidBrush(IntPtr handle) 
{
       DeleteBrush(handle);
       appGuiHandleCollector.Remove();
}
...

Problem Example

A .Net 2.0 Windows Forms based application needs to provide lot of GUI features to facilitate Paint functionality. The code internally uses many unmanaged GUI handles to manage various Bitmaps. If the developer misses out to destroy the handle by calling appropriate Dispose method at appropriate time, the object remains in memory till the time garbage collection is performed. Also, if memory allocation is performed within unmanaged resource, the GC may not be even aware of it to force collection. At runtime, it might be required to force garbage collection to optimize GC memory management for unmanaged allocations.

Solution Example

A .Net 2.0 Windows Forms based application needs to provide lot of GUI features to facilitate Paint functionality. The code internally uses many unmanaged GUI handles to manage various Bitmaps. The application creates an instance of HandleCollector by providing Handle Name, Initial Threshold and Maximum Threshold. When application creates an expensive handle, it increases the total handle count by invoking the Add method. When handle count reaches to the maximum threshold limit, the GC will force garbage collection automatically. Also, if the handle is destroyed, it will automatically reduce the total handle count in the following code:

...
class UnmanagedHandles 
{ 
      // Create a new HandleCollector
        static readonly HandleCollector appHandleCollector = new HandleCollector("ApplicationUnmanagedHandles", 5, 30); 
      public UnmanagedHandles () 
      {
           // Increment Handle Count
            myExpensiveHandleCollector.Add();
      }
      ~ UnmanagedHandles () 
      { 
          // Decrement Handle Count
           myExpensiveHandleCollector.Remove();
      }
}
...

Additional Resources


Use ExcludeSchema Serialization Mode while Exchanging Typed DataSet Over Network

What to Do

Set the Typed DataSet property SchemaSerializationMode to SchemaSerializationMode.ExcludeSchema while transferring the typed DataSet over network for better performance.

Why

The serialization of typed DataSet can optimized by setting property value to SchemaSerializationMode.ExcludeSchema. When ExcludeSchema is used, the serialized payload does not contain schema information, tables, relations and constraints. This results in a smaller payload for network transfer, which provides better performance.

When

If it is required to send the typed DataSet over network, use ExcludeSchema Serialization Mode by setting the the typed DataSet property SchemaSerializationMode to SchemaSerializationMode.ExcludeSchema.

ExcludeSchema is supported only for a typed DataSet. ExcludeSchema should only be used in cases where the schema information of the underlying typed DataTables, DataRelations and Constraints does not get modified.

How

The typed DataSet has a new property called SchemaSerializationMode in .Net Framework 2.0. Set the SchemaSerializationMode property of typed DataSet to SchemaSerializationMode.ExcludeSchema before returning for network transfer as follows:

...
typedDataSet.EnforceConstraints = false;
typedDataSet.SchemaSerializtionMode = SchemaSerializationMode.ExcludeSchema;
...

Problem Example

A .NET 2.0 Windows Forms based Smart Client application for Order Management, gets the list of Sales Orders by a Web Service call. The Web Service internally makes a business logic call to get the list of Sales Order in a Typed DataSet. The Web Service returns the Typed DataSet to the smart client application. The code implementation does not exclude schema information, while serializing the DataSet and hence it has a larger size. The larger content takes more time to transfer across network and hence has a negative performance impact.

...
//WebService Method
public SalesOrdersTypedDataSet GetSalesDetailDataset() 
{
   ...
   SalesOrdersTypedDataSet salesOrdersDS = BusinessLogic.GetSalesOrders();
   ...       	
   return salesOrdersDS;
}
...

Solution Example

A .NET 2.0 Windows Forms based Smart Client application for Order Management, gets the list of Sales Orders by a Web Service call. The Web Service internally makes a business logic call to get the list of Sales Order in a Typed DataSet. The Web Service returns the Typed DataSet to the smart client applicaiton. The code implementation uses SchemaSerializationMode property of the Sales Order Typed DataSet to reduce the size of the serialized content. It gives performance benefit while transferring over network:

...
//WebService Method
public SalesOrdersTypedDataSet GetSalesOrdersDataset() 
{
   ...
   SalesOrdersTypedDataSet salesOrdersDS = BusinessLogic.GetSalesOrders();
   salesOrdersDS.EnforceConstraints = false;
   salesOrdersDS.SchemaSerializationMode = SchemaSerializationMode.ExcludeSchema;
   return salesOrdersDS;
}
... 

Additional Resources


Consider Using Hard Binding and String Freezing in Advanced NGEN Scenario

What to Do

Set System.Runtime.CompilerServices.DependencyAttribute on assemblies in order to hard bind them to .Net framework or non-.Net framework dependencies when creating a native image using NGEN. Use System.Runtime.CompilerServices.StringFreezingAttribute on assemblies in order to pre-allocate a string literal when creating a native image using NGEN.

Why Hard binding improves throughput performance by avoiding extra level of indirection while making cross assembly references. Hard binding and string freezing improves performance and reduces delay in application startup time by reducing private pages and the need of string fixups in the assembly

When When one assembly frequently calls another assembly, set the dependency attribute for the first assembly to hard bind the cross-assembly reference. When one assembly is frequently called by several assemblies in the application, set the default dependency attribute for the called assembly such that all assemblies hard bind to that assembly. When a particular string present in the NGEN’d assembly to be shared heavily, use string freezing CLR cannot unload any native image that has a frozen string. Therefore string freezing should be used only in cases where the native image that contains the frozen string is shared heavily. Using “Always” hint with dependency attribute causes NGEN to always load dependent assembly along with the parent assembly. Therefore use appropriate hint.

How

Use classes provided by System.Runtime.CompilerServices namespace for hard binding cross assembly references and freezing heavily shared strings

  • Set dependency attribute using DependencyAttribute, when one assembly frequently calls another assembly
[assembly: DependencyAttribute("AssemblyB", LoadHint.Always)] 
  • Set default dependency attributes using DefaultDependencyAttribute, when one assembly is frequently called by several assemblies in the application
[assembly: DefaultDependencyAttribute(LoadHint.Always)]
  • Apply the StringFreezingAttribute to an assembly when a string literal to be shared heavily
[assembly :StringFreezingAttribute()]

Problem Example

An Order management application frequently uses some utilities which are part of other assemblies. Also some classes of this application have string literals which are highly shared within the application. The issue is that the performance of the application is slow while accessing these cross assembly functionalities. Cross-assembly references in NGEN compiled code need to go through a jump slot that gets populated with the correct address at run time by executing a method pre-stub. This is the reason for slow performance. Additionally string literals in NGEN compiled code need fixups. That means the literals need to be wrapped in a string instance and the code then points to those instances. This can cause performance issues if the string is heavily shared.

class Classname
{
   //heavily shared strings
   …..
   //cross assembly references   
}

Solution Example

An Order management application frequently uses some utilities which are part of other assemblies. Also some classes of this application have string literals which are highly shared within the application. Hard binding cross assembly references can avoid extra level of indirection while making cross assembly references. Additionally Pre allocating such string literals in a separate segment during native image generation can avoid runtime fixups. Set the dependency attribute at the assembly level. Use the load hint parameter to inform if the assembly to be bound always or sometimes.

//hard bind external assemblies
[assembly: DependencyAttribute("AssemblyA", LoadHint.Always)]
[assembly: DependencyAttribute("AssemblyB", LoadHint.Sometimes)]

//freeze strings
[assembly :StringFreezingAttribute()]

class Classname
{
   //heavily shared strings
   …..
   //cross assembly references    
}

Additional Resources


Use Workstation GC for Managing Multiple Server Processes on a Single Server Machine

What to Do

Use Workstation GC with Concurrent GC OFF for managing multiple server processes on a single machine.

Why

The CLR 2.0 provides the following GC flavors currently:

Workstation GC with Concurrent GC OFF - There is only one GC thread running for garbage collection. All the the managed threads are stopped waiting for GC thread to finish its work.

Workstation GC with Concurrent GC ON - There is only one separate thread for garbage collection. Instead of stopping the managed threads completely till GC thread finishes its work, it just pauses the managed threads for short time, several times during the GC process. It helps the application to be more responsive as the managed threads are running concurrently with the GC thread.

Server GC - Creates one GC thread for each CPU per process. Each GC thread collects parts of the GC heap. The GC threads clean-up part of the memory allocated to them, till then the managed threads are suspended until the GC threads finishes its jobs.

If there are multiple server processes on the single machine, Workstation GC with Concurrent GC OFF can significantly cut down the number of GC threads on the machine, which improves performance by reducing the thread management and cycle wait time overhead.

When

The concurrent GC OFF should be used when there are multiple server processes running on a single machine.

The garbage collection of Gen0 and Gen1 references is fast but the garbage collection of Gen2 references is time consuming. So, if the application has to perform garbage collection of Gen2 type of references it is recommended to use Concurrent GC ON.

How

In the web.config file, use the following configuration to enable Workstation GC with Concurrent GC OFF:

...
<configuration>
  <runtime>
      <gcConcurrent enabled="false"/>
  </runtime>
</configuration>
... 

Problem Example

In an enterprise scenario a single server machine is hosting multiple Intranet ASP.NET applications for Human Resource Management. The applications are configured for the Server GC as per the GC best practice for Server Applications. The configuration settings looks as below.

Server GC creates one GC thread per CPU per process. If there are 10 ASP.Net application processes on the same machine, it will create 40 threads on a 4 CPU server machine. As the number of threads increases, it increases the thread cycle wait time and thread management overhead, which degrades the performance.

...
<configuration>
  <runtime>
      <gcServer enabled="true"/>
  </runtime>
</configuration>
...

Solution Example

In an enterprise scenario a single server machine is hosting multiple Intranet ASP.NET applications for Human Resource Management. Consider. The applications are configured to use the Workstation GC with Concurrent GC OFF, the configuration settings look as below.

The Workstation GC with Concurrent GC OFF configuration does not create multiple threads per CPU per process and thus does not have thread management issues and and thereby gives better performance.

...
<configuration>
  <runtime>
      <gcConcurrent enabled="false"/>
  </runtime>
</configuration>
...

Additional Resources


Load Assemblies As Domain Neutral In Multiple Domain Scenarios

What to Do

Load Assemblies as Domain Neutral in Multiple Domain Scenarios by specifying LoaderOptimization("LoaderOptimization.MultiDomainHost") or LoaderOptimization ("LoaderOptimization.MultiDomain") attribute on the Main method of the assembly

Why

If an assembly is loaded as domain bound, in a multidomain scenario, a new copy of the assembly is loaded on each domain used in the application. This has several bad performance characteristics. If there is a native image for the assembly, only the first AppDomain can use the native image. All other AppDomains will have to JIT-compile the code which can result in a significant CPU cost and the JIT-compiled code resides in private memory, so it cannot be shared with other processes or AppDomains.

Assemblies loaded as domain neutral are instead shared among all domain used in the application. The domain neutral assemblies will be jitted only once when shared across multiple application domains. The jitted code, and various runtime data structures like Method Tables, Method Descriptions etc will be shared across domains, which provide better performance in multiple application domain scenarios

When

If it is required to share the assembly in multiple application domains, use domain neutral assemblies for better performance.

How

An assembly can be loaded as domain-neutral by setting the LoaderOptimizationAttribute on the assembly's Main method. Following are various methods for marking assemblies domain neutral.

  • Set the MultiDomainHost option to LoaderOptimization attribute in order to mark the assembly as shared across multiple domains only when that assembly is loaded from GAC. For assemblies which are not loaded from the GAC will not be shared across multiple domains
...
[LoaderOptimization(LoaderOptimization.MultiDomainHost)]
static void Main(string[] args) {
  ...
}
...
  • Set the MultiDomain option to LoaderOptimization attribute in order to mark the assembly as shared across multiple domains.
...
[LoaderOptimization(LoaderOptimization.MultiDomain)]
static void Main(string[] args) {
  ...
}
...
  • For hosting scenarios, assemblies can be loaded as AppDomain neutral by setting the flags param to STARTUP_LOADER_OPTIMIZATION_MULTI_DOMAIN as mentioned below
HRESULT CorBindToRuntimeEx ( 
       LPWSTR pwszVersion,   
       LPWSTR pwszBuildFlavor, 
       DWORD flags,            
       REFCLSID rclsid,      
       REFIID riid,    
       LPVOID* ppv
);
  • AppDomain neutral can be controlled at the Domain level also by setting LoaderOptimization property to either LoaderOptimization.MultiDomain or LoaderOptimization.MultiDomainHost in the AppDomainSetup class and pass it to the AppDomain.CreateDomain API upon creation of the domain.

AppDomainSetup subSetup = new AppDomainSetup();

...
...
...
subSetup.LoaderOptimization = LoaderOptimization.MultiDomain;
return AppDomain.CreateDomain(rootSetup.ApplicationName +
                                         ".SubAppDomain", null, subSetup);

Problem Example

A web based application uses a set of un-trusted code to perform specific activity. This un-trusted code is placed in a separate AppDomain and AppDomain is sandboxed to have a limited set of permissions. The the AppDomain which is executing the un-trusted code also needs to access to a assembly of trusted code. The Main method of the assembly to be shared across AppDomain is as follows, this means as the shared assembly is not domain neutral and and cannot be shared across domains and hence the application is less performant.

...
static void Main(string[] args)
{
 ...
}
...

Solution Example

A web based application uses a set of un-trusted code to perform specific activity. This un-trusted code is placed in a separate AppDomain and AppDomain is sandboxed to have a limited set of permissions. The the AppDomain which is executing the un-trusted code also needs to access to a assembly of trusted code. The LoaderOptimizationAttribute value is set to “MultiDomain” on the entry point of shared assembly. This ensures that the shared assembly is domain neutral and thus can be shared across domains improving the performance of the application.

//All assemblies are domain neutral 

[LoaderOptimization(LoaderOptimization.MultiDomain)]
static void Main(string[] args) 
{
 ...
}
... 

Additional Resources


Choose An Appropriate Base Address For Optimum Performance From NGEN

What to Do

Choose an appropriate base addresses for the application's assemblies such that the NGen images do not collide with each other

Why If the base address is not explicitly specified, then all binaries by default will get the same base address (0x400000). Thus, at load time, EXE will be placed at this address and all of DLLs (precompiled binaries) will be have to be placed elsewhere in virtual memory (rebased), which causes pages to become private (non-shared) and degrade the performance of working set.

Setting appropriate base address helps to make sure that assemblies (and their native images) that are known to be loaded together do not collide with each other

Loading the executable image at its preferred base address helps to keep all references contained in the image valid. Furthermore, the image can be shared across multiple processes

If precompiled binaries get relocated, then they need address fixups and hence the corresponding memory pages need to be written to. Once the pages are written to they become non-shared and the working set degrades very quickly

When Set appropriate base addresses for assemblies before pre-compiling assemblies into native images using NGEN.

Note

  • Currently there is no automatic support for base address management. When creating an assembly, if the developer does not explicitly specify a preferred base address, the compiler typically picks a default value. This causes all the binaries in the application to end up with the same preferred load address

* NGen images are significantly larger than MSIL assemblies, typically 2.5 to 3 times the size of their corresponding MSIL assemblies in the case of x86. They are even larger for 64-bit systems. Base addresses must be calculated to allow for these larger sizes

How

The base address for a native image is set by specifying the base address for the assembly. Assembly base address property can be set in the Advanced Compiler Settings dialog box, accessible from the Compile page of the Project Designer as follows.

  • With a project selected in Solution Explorer, on the Project menu, click Properties.
  • Click the Compile tab.
  • Click the Advanced Compile Options button to open the Advanced Compiler Settings dialog box.
  • In the DLL base address field, enter the hexadecimal number to use as a base address, and then click OK. Assign base addresses to assemblies beginning from some predetermined starting point (for example, 0x10000000),

Make sure that the gap between two adjacent assemblies are at least two or three times the size of the MSIL assembly that will lie in that range and also provide some buffer to give some room for growth to the DLL . * Verify the actual load address by executing the following syntax on command prompt from the location where tasklist.exe is installed. Example - C:\SYSROOT\system32 in Windows XP. Output will be as follows

Image Name       PID     Session Name     Session#    Mem Usage
==============   ======  ==============   ========    ============
...
...
csrss.exe        920       			0      1,972 K
  • Verify the preferred base address using Microsoft COFF Binary File Dumper, Dumpbin.exe using the command prompt from the location where it is installed. Example - C:\Program Files\Microsoft Visual Studio 8\VC\bin
dumpbin /headers test.dll

The output will be as follows

75F70000 image base (75F70000 to 75F78FFF)

Note The range for a valid base address is from 0x1000000 to 0x80000000. The base address must be on a multiple of 64K (0x10000). <<Need validate this information>>

Problem Example

A deployment package for a suite of tools used by small businesses, has core tools like Inventory.exe, TaxWizards.exe, and Books.exe. In addition to these core tools, the package deploys a DLL which is shared by all applications in the suite, Payroll.dll, and a component from a third-party development toolkit called Chart.dll which could potentially be used by other applications on the user's system. All the assemblies have default base address (0x400000). Thus, at load time, the EXE is placed at default address and all of DLLs (precompiled binaries) are placed elsewhere in virtual memory (rebased), which causes pages to become private (non-shared) and degrade the performance of working set.

Solution Example

A deployment package for a suite of tools used by small businesses, has core tools like Inventory.exe, TaxWizards.exe, and Books.exe. In addition to these core tools, the package deploys a DLL which is shared by all applications in the suite, Payroll.dll, and a component from a third-party development toolkit called Chart.dll which could potentially be used by other applications on the user's system.

In order to gain optimum performance with NGEN, base address for each native image is set appropriately. The adjacent assemblies are separated by at least three times (four times in case of mixed mode assemblies) the size of first assembly along with some additional buffer. Thus there is no degradation of performance.

Additional Resources


Use ReadAllLines, ReadAllText, ReadAllBytes Methods for Simple Reading of an Entire File

What to Do

Use File.ReadAllText, File.ReadAllLines, or File.ReadAllBytes Methods, if it is required to retrieve and hold onto all of that data from a file

Why

File.ReadAllText, File.ReadAllLines, or File.ReadAllBytes Methods of System.IO.File allows to read entire data as a string (with ReadAllText method), string array (with ReadAllLines method) or byte array (with ReadAllBytes method), which can provide a better performance than traditional line by line reading. These methods also take care of closing file in regular as well as exceptional cases

When

If it is required to read entire content of a file at a time, then consider using File.ReadAllText, File.ReadAllLines, or File.ReadAllBytes Methods of System.IO.File. Different scenarios would be

  • Read entire data from a text file and set it to a string or a text control
  • Read entire data from a text file and set it to an array of strings
  • Read entire data from a binary file and set it to an array of bytes

When any of these methods are used, the entire data from file moves in to memory. Therefore it is not recommended to use in case where the application has to manipulate the data. Example - data transformation or parsing, in those cases reading one line of data at a time is the better option

How

Following shows usage of all the three methods

  • Use ReadAllText method to retrieve entire data to a string or to set the entire text to a control like Textbox.
//set contents of file into a string
string custInfo = File.ReadAllText(filename);
//set contents of file into textbox
textBox1.Text = File.ReadAllText(filename);
  • Use ReadAllLines method to retrieve entire data to an array of strings.
//set contents of file into a string
string[] custInfo = File.ReadAllLines(filename);
  • Use ReadAllBytes method to retrieve entire data from a binary file into an array of strings.
//set contents of file into a string
byte[] custInfo = File.ReadAllBytes(filename);

Problem Example

A windows based application used by a coupon processing agent for managing nightly batch processes to be executed for different manufacturers. The application executes batch jobs at night and saves transaction information by creating a separate flat file for each manufacturer. User can view transaction details on this windows based application. The application just reads the entire file and displays on a scrollable textbox. The issue with the current implementation is that if the file size is huge, reading the file line by line and displaying on the textbox takes more time. The current implementation code snippet is as shown below

…
//Get the data from the file using StreamReader
StringBuilder sb = new StringBuilder();
using (StreamReader sr = new StreamReader(filename)) 
{
   while (sr.Peek() >= 0)
   {
       sb.AppendLine(sr.ReadLine());
   }
}

//Set entire data into textbox
textBox1.Text = sb.ToString();
...

Solution Example

A windows based application used by a coupon processing agent for managing nightly batch processes to be executed for different manufacturers. The application executes batch jobs at night and saves transaction information by creating a separate flat file for each manufacturer. User can view transaction details on this windows based application. The application just reads the entire file and displays on a scrollable textbox. Therefore the application uses File.ReadAllText method, which reads entire data once instead of reading line by line and improves the performance. The code snippet for the solution example is as follows:

...
//Getting the data from the file using ReadAllText
textBox1.Text = File.ReadAllText(filename);
... 

Additional Resources


Use NGEN for Faster Application Startup and for Ability to Share Assemblies

What to Do

Use NGen (native image generation) for pre-compiling Microsoft intermediate language (MSIL) executables into machine code prior to execution time in order to reduce application startup time and to improve memory usage.

Why

NGen improves the warm startup time of applications by reusing pages of the NGen images that were brought in when the application had been running earlier.

NGen improves the cold startup time of applications by avoiding the necessity of accessing pages of MSIL at execution time.

NGen improves the overall memory usage and minimizes the total memory footprint of the system by allowing different processes to share the same NGen image corresponding to an assembly.

Important

  • NGen does not always improve the cold startup time of applications, since NGen images are larger than MSIL assemblies. The only way to determine whether cold startup time and working set will improve or degrade with NGen for specific scenarios is to actually measure them.
  • Native images require more hard disk space than MSIL assemblies and may require considerable time to generate

When

This guideline should be used in following scenarios

  • Scenarios like Terminal Services in which a large number of users might be logged in and running the same application at the same time
  • Scenarios like assemblies of libraries or other reusable components to be shared across multiple processes.

How

Use the NGen tool (Ngen.exe) to compile assemblies either synchronously or asynchronously with one of three priority levels. Make sure that all assemblies in the application are strong-named and installed into the Global Assembly Cache (GAC) prior to compilation in order to get the optimal performance from NGen

  • Open the command prompt
  • Execute the command for compiling the assembly with Ngen.exe from the location where Ngen.exe is installed. Example - C:\SYSROOT\Microsoft.NET\Framework\v2.0.50727. The syntax is as follows
ngen install [assemblyName | assemblyPath] [scenarios] [config] [/queue[:{1|2|3}]]
Example: ngen install Books.exe 

NGen automatically finds all the dependencies of the root and adds native images for those assemblies to the cache.

  • Use the following syntax to install assemblies present in the GAC.
ngen install "Chart, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ab5e43f526a3c46e, processorArchitecture=MSIL"	
  • Verify the cache for installed native images. Ngen display command displays all the root assemblies first, followed by a list of all native images on the computer. The syntax is as follows
ngen display

OR

ngen display Chart

OR

ngen display Chart.dll

The output will be as follows

NGEN Roots:
Accessibility, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3
C:\Program Files\Common Files\Microsoft Shared\MSEnv\PublicAssemblies\envdte.dll
..
..
..
Native Images:
Microsoft.CompactFramework.Design, Version=8.0.0.0, Culture=neutral, PublicKeyTken=b03f5f7f11d50a3a
System.Deployment, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d0a3a
..
..
..
..

Problem Example

A deployment package for a suite of tools used by small businesses, has core tools like Inventory.exe, TaxWizards.exe, and Books.exe. In addition to these core tools, the package deploys a DLL which is shared by all these applications (exe's) in the suite, Payroll.dll, and a component from a third-party development toolkit called Chart.dll which could potentially be used by other applications on the user's system. The methods of these dll’s are just-in-time (JIT) compiled, the generated machine code is tied to the process (exe) that created it and cannot be shared between processes. This causes the performance issue because every time JIT compiled code has to be generated before an application accessing this DLL. The application startup time is also a major issue as every time it has to recompile the code during startup because the machine code generated by the JIT compiler is thrown away once the process running that executable exits.


Solution Example

A deployment package for a suite of tools used by small businesses, has core tools like Inventory.exe, TaxWizards.exe, and Books.exe. In addition to these core tools, the package deploys a DLL which is shared by all applications in the suite, Payroll.dll, and a component from a third-party development toolkit called Chart.dll which could potentially be used by other applications on the user's system. Instead of just-in-time (JIT) compiled code, native image (NGEN) is used, which helps to share assemblies of dll’s across multiple processes and hence improves the performance. Startup time is improved from reusing pages of the NGen images that were brought in when the application had been running earlier.

The following code snippet illustrates the syntax for compiling assemblies using NGEN tool.

ngen queue pause
... (xcopy all files to application directory)
... (install Payroll.dll to GAC)
... (install Charts.dll to GAC)
... (add registry keys for application)
ngen install Books.exe /queue:1 
ngen install Inventory.exe /queue:2
ngen install TaxWizards.exe /queue:2
ngen update /queue
ngen executequeueditems 1
ngen queue continue

Additional Resources


Use NeutralResourcesLanguageAttribute Class to Avoid Extra Lookups for the Specified Culture

What to Do

Apply NeutralResourcesLanguageAttribute property to an assembly to inform the ResourceManager of the neutral culture of an assembly

Why

System.Resources currently has no concept of which resources are currently on the machine. Example - If culture is en-US (English in US), then resources are searched for in the order of en-US, en, and finally, the fallback (default) resources in the main assembly. In most cases the fallback resources will be in the culture for which the application is most frequently distributed. But even for that culture the extra lookups are performed anyway. The NeutralResourcesLanguageAttribute can be applied to an assembly (Main assembly) to inform the resources manager to avoid those lookups for the specified culture and also to inform the ResourceManager of the assembly to retrieve neutral resources using the resource fallback process. If NeutralResourcesLanguageAttribute is applied, when looking up resources in the same culture as the neutral resources language, the ResourceManager automatically uses the resources located in the main assembly, instead of searching for a satellite assembly. This will improve lookup performance for the resource loading, and can reduce working set

When

If the neutral resources of the application are in the same culture as that of the application will be running in, then consider setting NeutralResourcesLanguageAttribute at assembly to avoid additional lookups.

How

Apply NeutralResourcesLanguageAttribute property to an assembly to inform the resources engine to avoid unnecessary lookups for the specified culture and to retrieve neutral resources using the resource fallback process.

  • Set the default language for an assembly by setting NeutralResourcesLanguageAttribute to the assembly
// the neutral resources language is “en-US”
[assembly: NeutralResourcesLanguageAttribute("en-US ")] 
  • It is possible to pass a member of the UltimateResourceFallbackLocation enumeration to NeutralResourcesLanguageAttribute class to indicate the location from which to retrieve the fallback resources
// the neutral resources language is “en-US” and the neutral resources are located in a satellite assembly
[assembly: NeutralResourcesLanguageAttribute("en-US ",UltimateResourceFallbackLocation.Satellite)] 

// the neutral resources language is “en-US” and the neutral resources are located in the main assembly
[assembly: NeutralResourcesLanguageAttribute("en-US ",UltimateResourceFallbackLocation.MainAssembly
  • It is possible to create an instance of NeutralResourcesLanguageAttribute and get fallback information like location, culture, etc through the code
NeutralResourcesLanguageAttribute attr = new NeutralResourcesLanguageAttribute("en-US", 
     UltimateResourceFallbackLocation.Satellite); 
Console.WriteLine(“Fallback location = " + attr.Location.ToString() + ", 
     Fallback culture = " + attr.CultureName.ToString());

Problem Example

A web application for Employee leave management, has functionality for employee to apply leaves. As part of this functionality, user can view the list of holidays available for the year. This list is retrieved from fallback resources stored in the main assembly. The main assembly of the application has resources in US English and most of the users are US English users. But since the application has to do lookups for resources with US-en & en culture before looking into fallback resources, there is a time delay in displaying the holiday list for the user. Following is a code snippet for problem example.

...
//Create a resource manager. The GetExecutingAssembly() method
// gets appname.exe as an Assembly object. 
ResourceManager rm = new ResourceManager("appname", Assembly.GetExecutingAssembly());
// Obtain the en-US culture.
CultureInfo ci = new CultureInfo("en-US");
//Get day, year & Holiday from resource. Looks into en-US, en & then fallback
day  = rm.GetString("day", ci);
year = rm.GetString("year", ci);
holiday = rm.GetString("holiday", ci);
...

Solution Example

A web application for Employee leave management, has functionality for employee to apply leaves. As part of this functionality, user can view the list of holidays available for the year. This list is retrieved from fallback resources stored in the main assembly. The main assembly of the application has resources in US English and most of the users are US English users. Therefore NeutralResourcesLanguageAttribute is set with “en-US” as default language. UltimateResourceFallbackLocation.MainAssembly also set to inform that fallback resources should be retrieved from the main assembly. This helps to avoid unnecessary lookups during resource loading during runtime and improves performance.

//set the default language as en-US
//the neutral resources language is “en-US” and the neutral resources are located in the main assembly
[assembly: NeutralResourcesLanguageAttribute("en-US ",UltimateResourceFallbackLocation.MainAssembly)]


... 
//Create a resource manager. The GetExecutingAssembly() method
// gets appname.exe as an Assembly object. 
ResourceManager rm = new ResourceManager("appname", Assembly.GetExecutingAssembly());
// Obtain the en-US culture.
CultureInfo ci = new CultureInfo("en-US");
// get day, year & Holiday from the resource. Looks directly into fallback
day  = rm.GetString("day", ci);
year = rm.GetString("year", ci);
holiday = rm.GetString("holiday", ci);
...

Additional Resources


Set UseGlobalLock to False for Lock Free Tracing

What to Do

Set useGlobalLock property to false for lock free tracing. The global lock is not used if the value of useGlobalLock is false and the value of TraceListener.IsThreadSafe is true.

Why

In .Net Framework 1.1 tracing uses global lock internally to ensure the atomicity and order of the tracing operations. Internal lock is very expensive, which degrades the performance.

The .Net Framework 2.0 provides a facility by which a developer can explicitly specify if global lock has to be used or not. If the TraceListener is marked thread-safe and the useGlobalLock property is set to false, the global lock is not used, which provides better performance.

When

This guideline should be followed if the TraceListener is marked as thread-safe.

Note : The IsThreadSafe property is used to determine if the listener is thread-safe. Default value of the IsThreadSafe property for TraceListener class is false. The global lock is always used if the trace listener is not marked thread safe, regardless of the value of useGlobalLock.

How

In version 2.0, there's a new way to specify that you don't want to lock a particular listener—a new UseGlobalLock property on the Trace class. This property is set to true by default, reflecting the version 1.x behavior. But you can set it to false programmatically or in the config file as follows.

<configuration>
  <system.diagnostics>
     <trace useGlobalLock="false" />
  </system.diagnostics>
</configuration> 

Problem Example

A utility class library which facilitates tracing in Flat Files by exposing FlatFileTraceListener class. The utility class library is consumed by an application to perform tracing. The FlatFileTraceListener class in the utility library is not marked as thread-safe, so every trace operation will internally consume global lock, which is very expensive and has negative impact on the performance of tracing.

public class FlatFileTraceListener : TraceListener 
{          
  public FlatFileTraceListener ()
  {
     // initialize the listener
  }

  public override void TraceData(TraceEventCache eventCache, 
               string source, TraceEventType eventType, int id, object data) 
  {
      // implement trace method
  }
} 

Solution Example

A utility library which facilitates tracing in Flat Files by exposing FlatFileTraceListener class. The utility library is consumed by an application to perform tracing. The FlatFileTraceListener class is implemented in a thread-safe manner. The IsThreadSafe property of the trace listener class returns true.

public class FlatFileTraceListener : TraceListener 
{          
  public FlatFileTraceListener ()
  {
      // initialize the listener
  }
  
  public override bool IsThreadSafe 
  {
      get
      {
         return true;
       }

  }

  public override void TraceData(TraceEventCache eventCache, string source, 
                         TraceEventType eventType, int id, object data) 
  {
      // implement thread-safe trace method
  }
}

The application also specifies the useGlobalLock property as false to avoid global lock overhead, which improves performance.

<configuration>
  <system.diagnostics>
     <trace useGlobalLock="false" />
  </system.diagnostics>
</configuration> 

Additional Resources


Use UtcNow for DateTime Comparison and Calculations

What to Do

Use UtcNow property of DateTime instead of Now property for DateTime Comparison or Calculations.

Why

DateTime Comparison or Calculations using UtcNow property performs much faster than using Now property, because it performs cultural-neutral comparison or calculation.

When

This guideline should be followed when it is required to perform culture-neutral DateTime comparisons or calculations.

How

Use UtcNow property of DateTime class for comparison as follows:

...
utcTime = System.DateTime.UtcNow;
... 

Problem Example

A windows based application for Performance Testing, needs to invoke the performance test cases as created by the user and produce a duration statistics for each test case in a tree based hierarchy. The application has to retrieve the start time and end time to perform each test case in for loop. If the application uses Now property for the time calculation, it does the culture-sensitive calculation, which degrades the performance of the application:

...
for(i=0; i<numberTestCases; i++)
{
  ...
  startTime = System.DateTime.Now;
  ...
  //test case processing
  ...
  endTime = System.DateTime.Now;
  ...
  timeSpan = endTime - startTime;
}
...


Solution Example

A windows based application for Performance Testing, needs to invoke the performance test cases as created by the user and produce a duration statistics for each test case in a tree based hierarchy. The application has to retrieve the start time and end time to perform each test case in for loop. If the application uses UtcNow property for the time calculation, it does the culture-neutral calculation, which provides better performance:

...
for(i=0; i<numberTestCases; i++)
{	
  ...
  startTime = System.DateTime.UtcNow;
  ...
  //test case processing
  ...
  endTime = System.DateTime.UtcNow;
  ...
  timeSpan = endTime - startTime;
}
...


Additional Resources


Consider Using Precompiled Or Inline Regular Expressions

What to Do

Consider using Precompiled Or Inline Regular Expressions in the scenarios where the same set of regular expressions are used quite often in the applications.

  • Precompiled Regular Expressions can be created using RegexOptions.Compiled switch while defining the regular expression.
  • Inline Regular Expressions can be created using Regex.CompileToAssembly method.

Why

  • Interpreted Regular Expressions have the least start-up time, but has performance issues at runtime. If the regular expression is used rarely in the application, use Interpreted Regular Expressions.
  • Precompiled Regular Expressions take little more time at start-up, but gives better performance at runtime. Use Precompiled Regular Expressions for the regular expressions which are used most often in the application for better runtime performance. The precompiled regular expressions give 30 % better runtime performance then interpreted regular expressions.
  • Inline Regular Expressions creates separate regular expression assembly with comparable start-up time as interpreted regular expressions and also provide the performance benefit of precompiled regular expressions.

When

  • Select traditional interpreted Regular Expression if RegEx is used rarely in the application
  • Select Precompiled Regular Expression if there are limited set of RegEx used repeatedly in the application.
  • Select Inline Regular Expression if the regular expressions are shared across applications and used multiple times.

How

  • Interpreted Regular Expressions can be created as follows:
...
Regex r = new Regex("xyz*");
Regex.Match("9876foo", @"(\d*)foo");
...

* Precompiled Regular Expressions can be created as follows:

...
Regex r = new Regex("xyz*", RegexOptions.Compiled);
Regex.Match("9876foo", @"(\d*)foo");
...

* Inline Regular Expressions can be created as follows:

... 
RegexCompilationInfo regexInfo = new RegexCompilationInfo("xyz*", RegexOptions.None, "standard", "XYZ.Regex", true); 
Regex.CompileToAssembly(new RegexCompilationInfo[]{regexInfo}, new AssemblyName("bar.dll"));
...

Problem Example

A Data Cleansing Application which reads the data from the database, performs data cleansing operations and saves the corrected data to a new database. The application reads the data for Email Address, Zip Code, Phone Number etc and validates the data structure using Regular Expressions. The application uses Interpreted Regular Expressions. Since the application needs to process millions of database rows for data cleansing, the runtime performance using interpreted regular expression is slow.

...
for(i=0; i<numberOfRecords; i++)
{
 //Validate Email Address
 Regex reg = new Regex("(?<user>[^@]+)@(?<host>.+)");
 Boolean match = reg.IsMatch(emailAddress);
 if (!match)
 {
   //process the data
 }

 //Validate Zip Code 
 reg = new Regex("\d{5}(-\d{4})?");
 match = reg.IsMatch(zipCode);
 if (!match)
 {
   //process the data
 }

 //Validate Phone Number
 reg = new Regex("((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}");
 match = reg.IsMatch(phoneNumber);
 if (!match)
 {
   //process the data
 }
}
...

Solution Example

A Data Cleansing Application which reads the data from the database, performs data cleansing operations and saves the corrected data to a new database. The application reads the data for Email Address, Zip Code, Phone Number etc and validates the data structure using Regular Expressions. The application uses Precompiled Regular Expressions which gives better runtime performance:

...
Regex reg1 = new Regex("(?<user>[^@]+)@(?<host>.+)",RegexOptions.Compiled );
Regex reg2 = new Regex("\d{5}(-\d{4})?", RegexOptions.Compiled);
Regex reg3 = new Regex("((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}", RegexOptions.Compiled);

for(i=0; i<numberOfRecords; i++)
{
 //Validate Email Address
 Boolean match = reg1.IsMatch(emailAddress);
 if (!match)
 {
   //process the data
 }

 //Validate Zip Code 
   match = reg2.IsMatch(zipCode);
 if (!match)
 {
   //process the data
 }

 //Validate Phone Number
 match = reg3.IsMatch(phoneNumber);
 if (!match)
 {
   //process the data
 }
}
...

Additional Resources

Personal tools