Tune your .NET Application that suffer from contention, poor performance, and deadlocks

29 ביולי 2008

Hi,


If you develop .NET application that uses:




  • CLR thread pool


  • You have available CPU cycles


  • Your application performs I/O bound operations such as calling a Web method or accessing the file system


  • The ASP.NET Applications/Requests In Application Queue performance counter indicates that you have queued requests

If you are unsure whether your application is using the CLR threat pool you can check as follows. Your application may be using the CLR thread pool if any of these conditions is true:




  • Your application makes Web service calls.
  • Your application uses the WebRequest or HttpWebRequest classes to make outgoing Web requests.
  • Your application explicitly queues work to the thread pool by calling the QueueUserWorkItem method

You can tune .NET thread pool using maxWorkerThreads, minWorkerThreads, maxIoThreads, minFreeThreads, minLocalRequestFreeThreads and maxconnection attributes in App.config or Web.config file.


ConnectionManagment section:


The maxconnection attribute in App.config\Web.config limits the number of concurrent
outbound calls. This setting does not apply to local requests (requests that originate from ASP.NET
applications on the same server as the Web service). The setting applies to outbound connections
from the current computer, for example, to ASP.NET applications and Web services calling other
remote Web services.


The default setting for maxconnection is two per connection group. For desktop
applications that call Web services, two connections may be sufficient. For ASP.NET
applications that call Web services, two is generally not enough. Change the
maxconnection attribute from the default of 2 to (12 times the number of CPUs)
as a starting point.


Here is an example for the config settings:



<system.net>


  <connectionManagement>


      <add address="*" maxconnection="24" />


      <add address="[Specific IP]" maxconnection="10" />


  </connectionManagement>


</system.net>


 


The configuration is one way but lets say you install your application on different machines with different hardware parts like: Numbers of CPU, memory and so on. So better solution is to do it dynamically using code.


Here is a code example on ASP.NET application.


The function for updating the section will look like:


 



public const string CONNECTION_MANAGEMENT_ALL_IPS = "*";


public const int CONNECTION_MANAGEMENT_THREADS_PER_PROCESSOR = 12;


 


/// <summary>


/// Add -or- Update system.net –> connectionManagement section.


///    Best Performance for all IPs (12 Threads * CPU#).


/// </summary>


/// <param name="config">The Configuration to be updated.</param>


/// <returns>true if ConnectionManagement section was updated, otherwise return false.</returns>


public static bool ApplyConnectionManagement(Configuration config)


{


     return ApplyConnectionManagement(config,


          CONNECTION_MANAGEMENT_ALL_IPS,


          CONNECTION_MANAGEMENT_THREADS_PER_PROCESSOR * Environment.ProcessorCount);


}


 


/// <summary>


/// Add -or- Update system.net –> connectionManagement section.


/// </summary>


/// <param name="config">The Configuration to be updated.</param>


/// <param name="address">The Address to be added / updated.</param>


/// <param name="maxConnections">The Max Connections to update.</param>


/// <returns>true if ConnectionManagement section was updated, otherwise return false.</returns>


public static bool ApplyConnectionManagement(Configuration config, string address, int maxConnections)


{


    bool isUpdate = false;


 


    if (config == null)


        throw new ArgumentNullException("config");


 


    if (String.IsNullOrEmpty(address))


        throw new ArgumentNullException("address");


 


    if (maxConnections < 1)


        throw new ArgumentException("maxConnections must be greater than zero.", "maxConnections");


 


    NetSectionGroup netSectionGroup = NetSectionGroup.GetSectionGroup(config);


 


    ConnectionManagementSection connectionManagementSection = netSectionGroup.ConnectionManagement;


 


    if (connectionManagementSection.ConnectionManagement.Count == 0)


    {


        // Add configuration to all IPs (*)


        if (address == CONNECTION_MANAGEMENT_ALL_IPS)


        {


            connectionManagementSection.ConnectionManagement.Clear();


 


            // Add new required element.


            ConnectionManagementElement connectionManagementElement =


                new ConnectionManagementElement(


                    address,


                    maxConnections);


 


            connectionManagementSection.ConnectionManagement.Add(connectionManagementElement);


            isUpdate = true;


        }


    }


    else


    {


        // Update Max Connection for required existing IP.


        foreach (ConnectionManagementElement connectionManagementElement in


            connectionManagementSection.ConnectionManagement)


        {


            if (connectionManagementElement.Address == address)


            {


                if (connectionManagementElement.MaxConnection != maxConnections)


                {


                    connectionManagementElement.MaxConnection = maxConnections;


                    isUpdate = true;


                }


                break;


            }


        }


    }


 


    return isUpdate;


 


}


 


Not on the Global.asax.cs file we will add the code that update the section:



protected void Application_Start(object sender, EventArgs e)




    // Load custom configuration file.


    Configuration configuration = ConfigurationManager.OpenMachineConfiguration();


    string configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web.config");


            Configuration config = ConfigurationManager.OpenMappedExeConfiguration(new ExeConfigurationFileMap { MachineConfigFilename = configuration.FilePath, ExeConfigFilename = configPath }, ConfigurationUserLevel.None);


 


    if (ApplyConnectionManagement(config))


    {


       config.Save(ConfigurationSaveMode.Modified);


    }


}


 


On the first time you apllication start you will write the section using ConfigurationUtils.ApplyConnectionManagement(config) that return true. This will restart your application again but on the next apllication start the ConfigurationUtils.ApplyConnectionManagement(config) will return false.


 


Thread Pool (minFreeThreads, minLocalRequestFreeThreads):


Here is an example for the config settings:


<httpRuntime minFreeThreads="8" minLocalRequestFreeThreads="4" />


<processModel maxWorkerThreads="20" maxIoThreads="20" />

Now lets see it in code. first the function that update the thread pool:

 



/// <summary>


/// Apply Thread Pool Optimized Threading Settings.


///    Max = 100, Min = 50


/// </summary>


public static void ApplyThreadPoolOptimization()


{


    int maxWorkerThreads, maxCompletionPortThreads, minWorkerThreads, minCompletionPortThreads;


 


    ThreadPool.GetMinThreads(out minWorkerThreads, out minCompletionPortThreads);


    ThreadPool.GetMaxThreads(out maxWorkerThreads, out maxCompletionPortThreads);


 


    if (minWorkerThreads < 50) minWorkerThreads = 50;


    if (minCompletionPortThreads < 50) minCompletionPortThreads = 50;


    if (maxWorkerThreads < 100) minWorkerThreads = 100;


    if (maxCompletionPortThreads < 100) maxCompletionPortThreads = 100;


 


    ApplyThreadPoolSettings(maxWorkerThreads, maxCompletionPortThreads, minWorkerThreads, minCompletionPortThreads);


}


 


/// <summary>


/// Apply Thread Pool Threading Settings.


/// </summary>


/// <param name="maxWorkerThreads">The maximum number of worker threads in the thread pool.</param>


/// <param name="maxCompletionPortThreads">The maximum number of asynchronous I/O threads in the thread pool.</param>


/// <param name="minWorkerThreads">The new minimum number of idle worker threads to be maintained by the thread pool.</param>


/// <param name="minCompletionPortThreads">The new minimum number of idle asynchronous I/O threads to be maintained by the thread pool.</param>


public static void ApplyThreadPoolSettings(


    int maxWorkerThreads, int maxCompletionPortThreads,


    int minWorkerThreads, int minCompletionPortThreads)


{


    ThreadPool.SetMaxThreads(maxWorkerThreads, maxCompletionPortThreads);


    ThreadPool.SetMinThreads(minWorkerThreads, minCompletionPortThreads);


}


 


Again on your global.asax you can add call to update this setting:


protected void Application_Start(object sender, EventArgs e)



     ApplyThreadPoolOptimization();


}


 


 


The settings that are recommended may not work for all applications but If you are making one Web service call to a single IP address from each ASPX page, Microsoft recommends that you use the following configuration settings:





  • Set the values of the maxWorkerThreads parameter and the maxIoThreads parameter to 100.


  • Set the value of the maxconnection parameter to 12*N (where N is the number of CPUs that you have).


  • Set the values of the minFreeThreads parameter to 88*N and the minLocalRequestFreeThreads parameter to76*N.


  • Set the value of minWorkerThreads to 50. Remember, minWorkerThreads is not in the configuration file by default. You must add it.

More information and great microsoft article on this issue can be found on Microsoft Help & Support here.


 


 


 

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. (*) שדות חובה מסומנים

3 תגובות

  1. Ofir22 ביולי 2009 ב 16:33

    brilliant – extremely helpful

    Thanks!

    BTW – Any chance any1 know why "12* # of processors" is defined as the "Best performance"?

    How come?

    להגיב
  2. Rotem Bloom22 ביולי 2009 ב 17:18

    Ofir,
    The number 12 is just Microsoft recommendation, so they probably check that this number is the best to use for most cases. Microsoft wrote as note:
    "The recommendation to limit the number of ASP.NET requests to 12 per CPU is a little arbitrary. However, this limit has proved to work well for most applications."

    You know Microsoft :-) what can I say!!!

    להגיב