Improving .NET Performance Part 17: Measuring .NET Application Performance (II)

This is part II of measuring .NET application performance. The .NET Framework provides a series of performance counters, which you can monitor using System Monitor and other monitoring tools. To measure other aspects of .NET application performance, you need to add instrumentation to your applications.

 

CLR and Managed Code

This section describes what you need to measure in relation to the CLR and managed code and how you capture the key metrics. This applies to all managed code, regardless of the type of assembly, for example, ASP.NET application, Web service, serviced component, and data access component.

What to Measure

When measuring the processes running under CLR some of the key points to look for are as follows:

·         Memory. Measure managed and unmanaged memory consumption.

·         Working set. Measure the overall size of your application’s working set.

·         Exceptions. Measure the effect of exceptions on performance.

·         Contention. Measure the effect of contention on performance.

·         Threading. Measure the efficiency of threading operations.

·         Code access security. Measure the effect of code access security checks on performance.

How to Measure

You can measure the performance of your managed code by using system performance counters. The main counters used to measure managed code performance and to identify CLR related bottlenecks are summarized in Table 15.1.

Performance Counters Used to Measure Managed Code Performance

Area

Counter

Memory

Process\Private Bytes

.NET CLR Memory\% Time in GC

.NET CLR Memory\# Bytes in all Heaps

.NET CLR Memory\# Gen 0 Collections

.NET CLR Memory\# Gen 1 Collections

.NET CLR Memory\# Gen 2 Collections

.NET CLR Memory\# of Pinned Objects

.NET CLR Memory\Large Object Heap Size

Working Set

Process\Working Set

Exceptions

.NET CLR Exceptions\# of Exceps Thrown / sec

Contention

.NET CLR LocksAndThreads\Contention Rate/sec

.NET CLR LocksAndThreads\Current Queue Length

Threading

.NET CLR LocksAndThreads\# of current physical Threads

Thread\% Processor Time

Thread\Context Switches/sec

Thread\Thread State

Code Access Security

.NET CLR Security\Total RunTime Checks

.NET CLR Security\Stack Walk Depth

 

Memory

To measure memory consumption, use the following counters:

  • Process\Private Bytes

Threshold: The threshold depends on your application and on settings in the Machine config file. The default for ASP.NET is 60 percent available physical RAM or 800 MB, whichever is the minimum. Note that .NET Framework 1.1 supports 1,800 MB as the upper bound instead of 800 MB if you add a /3GB switch in your Boot.ini file. This is because the .NET Framework is able to support 3 GB virtual address space instead of the 2 GB for the earlier versions.

Significance: This counter indicates the current number of bytes allocated to this process that cannot be shared with other processes. This counter is used for identifying memory leaks.

  • .NET CLR Memory\% Time in GC

Threshold: This counter should average about 5 percent for most applications when the CPU is 70 percent busy, with occasional peaks. As the CPU load increases, so does the percentage of time spent performing garbage collection. Keep this in mind when you measure the CPU.

Significance: This counter indicates the percentage of elapsed time spent performing a garbage collection since the last garbage collection cycle. The most common cause of a high value is making too many allocations, which may be the case if you are allocating on a per-request basis for ASP.NET applications. You need to study the allocation profile for your application if this counter shows a higher value.

  • .NET CLR Memory\# Bytes in all Heaps

Threshold: No specific value.

Significance: This counter is the sum of four other counters — Gen 0 Heap Size, Gen 1 Heap Size, Gen 2 Heap Size, and Large Object Heap Size. The value of this counter will always be less than the value of Process\Private Bytes, which also includes the native memory allocated for the process by the operating system. Private Bytes – # Bytes in all Heaps is the number of bytes allocated for unmanaged objects.

This counter reflects the memory usage by managed resources.

  • .NET CLR Memory\# Gen 0 Collections

Threshold: No specific value.

Significance: This counter indicates the number of times the generation 0 objects are garbage-collected from the start of the application. Objects that survive the collection are promoted to Generation 1. You can observe the memory allocation pattern of your application by plotting the values of this counter over time.

  • .NET CLR Memory\# Gen 1 Collections

Threshold: One-tenth the value of # Gen 0 Collections.

Significance: This counter indicates the number of times the generation 1 objects are garbage-collected from the start of the application.

  • .NET CLR Memory\# Gen 2 Collections

Threshold: One-tenth the value of # Gen 1 Collections.

Significance: This counter indicates the number of times the generation 2 objects are garbage-collected from the start of the application. The generation 2 heap is the costliest to maintain for an application. Whenever there is a generation 2 collection, it suspends all the application threads. You should profile the allocation pattern for your application and minimize the objects in generation 2 heap.

  • .NET CLR Memory\# of Pinned Objects

Threshold: No specific value.

Significance: When .NET-based applications use unmanaged code, these objects are pinned in memory. That is, they cannot move around because the pointers to them would become invalid. These can be measured by this counter. You can also pin objects explicitly in managed code, such as reusable buffers used for I/O calls. Too many pinned objects affect the performance of the garbage collector because they restrict its ability to move objects and organize memory efficiently.

  • .NET CLR Memory\Large Object Heap Size

Threshold: No specific values.

Significance: The large object heap size shows the amount of memory consumed by objects whose size is greater than 85 KB. If the difference between # Bytes in All Heaps and Large Object Heap Size is small, most of the memory is being used up by large objects. The large object heap cannot be compacted after collection and may become heavily fragmented over a period of time. You should investigate your memory allocation profile if you see large numbers here.

Working Set

To measure the working set, use the following counter:

  • Process\Working Set

Threshold: No specific value.

Significance: The working set is the set of memory pages currently loaded in RAM. If the system has sufficient memory, it can maintain enough space in the working set so that it does not need to perform the disk operations. However, if there is insufficient memory, the system tries to reduce the working set by taking away the memory from the processes which results in an increase in page faults. When the rate of page faults rises, the system tries to increase the working set of the process. If you observe wide fluctuations in the working set, it might indicate a memory shortage. Higher values in the working set may also be due to multiple assemblies in your application. You can improve the working set by using assemblies shared in the global assembly cache.

Exceptions

To measure exceptions, use the following counter:

  • .NET CLR Exceptions\# of Exceps Thrown / sec

Threshold: This counter value should be less than 5 percent of Request/sec for the ASP.NET application. If you see more than 1 request in 20 throw an exception, you should pay closer attention to it.

Significance: This counter indicates the total number of exceptions generated per second in managed code. Exceptions are very costly and can severely degrade your application performance. You should investigate your code for application logic that uses exceptions for normal processing behavior. Response.Redirect, Server.Transfer, and Response.End all cause a ThreadAbortException in ASP.NET applications.

Contention

To measure contention, use the following counters:

  • .NET CLR LocksAndThreads\Contention Rate / sec

Threshold: No specific value.

Significance: This counter displays the rate at which the runtime attempts to acquire a managed lock but without a success. Sustained nonzero values may be a cause of concern. You may want to run dedicated tests for a particular piece of code to identify the contention rate for the particular code path.

  • .NET CLR LocksAndThreads\Current Queue Length

Threshold: No specific value.

Significance: This counter displays the last recorded number of threads currently waiting to acquire a managed lock in an application. You may want to run dedicated tests for a particular piece of code to identify the average queue length for the particular code path. This helps you identify inefficient synchronization mechanisms.

Threading

To measure threading, use the following counters:

  • .NET CLR LocksAndThreads\# of current physical Threads

Threshold: No specific value.

Significance: This counter indicates the number of native operating system threads currently owned by the CLR that act as underlying threads for .NET thread objects. This gives you the idea of how many threads are actually spawned by your application.

This counter can be monitored along with System\Context Switches/sec. A high rate of context switches almost certainly means that you have spawned a higher than optimal number of threads for your process. If you want to analyze which threads are causing the context switches, you can analyze the Thread\Context Swtiches/sec counter for all threads in a process and then make a dump of the process stack to identify the actual threads by comparing the thread IDs from the test data with the information available from the dump.

  • Thread\% Processor Time

Threshold: No specific value.

Significance: This counter gives you the idea as to which thread is actually taking the maximum processor time. If you see idle CPU and low throughput, threads could be waiting or deadlocked. You can take a stack dump of the process and compare the thread IDs from test data with the dump information to identify threads that are waiting or blocked.

  • Thread\Context Switches/sec

Threshold: No specific value.

Significance: The counter needs to be investigated when the System\Context Switches/sec counter shows a high value. The counter helps in identifying which threads are actually causing the high context switching rates.

  • Thread\Thread State

Threshold: The counter tells the state of a particular thread at a given instance.

Significance: You need to monitor this counter when you fear that a particular thread is consuming most of the processor resources.

Code Access Security

To measure code access security, use the following counters:

  • .NET CLR Security\Total RunTime Checks

Threshold: No specific value.

Significance: This counter displays the total number of runtime code access security checks performed since the start of the application. This counter used together with the Stack Walk Depth counter is indicative of the performance penalty that your code incurs for security checks.

  • .NET CLR Security\Stack Walk Depth

Threshold: No specific value.

Significance: This counter displays the depth of the stack during that last runtime code access security check. This counter is not an average. It just displays the last observed value.

Timing Your Code Path

There are often requirements that you need to know how much time a particular code path takes during execution. You may need this information when you are comparing various prototypes in the design stage or profiling the APIs or critical code paths during the development stage. You need to instrument your code to calculate the time duration and log it in an appropriate event sink such as Event Log or Windows Trace Session Manager. The timing code in your application may look like the following.

QueryPerfCounter myTimer = new QueryPerfCounter();

// Measure without boxing

myTimer.Start();

for(int i = 0; i < iterations; i++)

{

  // do some work to time

}

myTimer.Stop();

// Calculate time per iteration in nanoseconds

double result = myTimer.Duration(iterations);

 

You might also like