Return to
HomePage
Code Review: Managed Code 1.1 Performance
Source:
http://msdn.microsoft.com/library/en-us/dnpag/html/ScaleNetChapt13.aspJ.D. Meier, Srinath Vasireddy, Ashish Babbar, Rico Mariani, and Alex Mackman
Managed Code and CLR Performance
While the .NET CLR is designed for high performance, the way in which you write managed code can either take advantage of that high performance or hinder it. Use the review questions in this section to analyze your entire managed source code base. The review questions apply regardless of the type of assembly. This section helps you identify inefficient managed code coding techniques that can lead to performance problems. For more information about the issues raised in this section, see Chapter 5, "Improving Managed Code Performance." This section describes the following:
* Memory management
* Looping and recursion
* String operations
* Exception handling
* Arrays
* Collections
* Locking and synchronization
* Threading
* Asynchronous processing
* Serialization
* Visual Basic considerations
* Reflection and late binding
* Code access security
* Ngen.exe
Memory Management
Use the following review questions to assess how efficiently your code uses memory:
* Do you manage memory efficiently?
* Do you call GC.Collect?
* Do you use finalizers?
* Do you use unmanaged resources across calls?
* Do you use buffers for I/O operations?
Do You Manage Memory Efficiently?
To identify how efficiently your code manages memory, review the following questions:
* Do you call Dispose or Close?
Check that your code calls Dispose or Close on all classes that support these methods. Common disposable resources include the following:
* Database-related classes: Connection,
DataReader, and Transaction.
* File-based classes:
FileStream and
BinaryWriter. * Stream-based classes:
StreamReader, TextReader, TextWriter, BinaryReader, and
TextWriter. * Network-based classes: Socket,
UdpClient, and
TcpClient. Also check that your Visual C#® code uses the using statement to ensure that Dispose is called. If you have Visual Basic® .NET code, make sure it uses a Finally block to ensure that resources are released.
* Do you have complex object graphs?
Analyze your class and structure design and identify those that contain many references to other objects. These result in complex object graphs at runtime, which can be expensive to allocate and create additional work for the garbage collector. Identify opportunities to simplify these structures. Simpler graphs have superior heap locality and they are easier to maintain.
Another common problem to look out for is referencing short-lived objects from long-lived objects. Doing so increases the likelihood of short-lived objects being promoted from generation 0, which increases the burden on the garbage collector. This often happens when you allocate a new object and then assign it to a class level object reference.
* Do you set member variables to null before long-running calls?
Identify potentially long-running method calls. Check that you set any class-level member variables that you do not require after the call to null before making the call. This enables those objects to be garbage collected while the call is executing.
Note: There is no need to explicitly set local variables to null because the just - in - time (JIT) compiler can statically determine that the variable is no longer referenced.
* Do you cache data using
WeakReference objects?
Look at where your code caches objects to see if there is an opportunity to use weak references. Weak references are suitable for medium- to large-sized objects stored in a collection. They are not appropriate for very small objects.
By using weak references, cached objects can be resurrected easily if needed or they can be released by garbage collection when there is memory pressure.
Using weak references is just one way of implementing caching policy. For more information about caching, see “Caching” in Chapter 3, “Design Guidelines for Application Performance.”
* Do you call
ReleaseComObject?If you create and destroy COM objects on a per-request basis under load, consider calling
ReleaseComObject. Calling
ReleaseComObject releases references to the underlying COM object more quickly than if you rely on finalization. For example, if you call COM components from ASP.NET, consider calling
ReleaseComObject. If you call COM components hosted in COM+ from managed code, consider calling
ReleaseComObject. If you are calling a serviced component that wraps a COM component, you should implement Dispose in your serviced component, and your Dispose method should call
ReleaseComObject. The caller code should call your serviced component's Dispose method.
Do You Call GC.Collect?
Check that your code does not call GC.Collect explicitly. The garbage collector is self - tuning. By programmatically forcing a collection with this method, the chances are you hinder rather than improve performance.
The garbage collector gains its efficiency by adopting a lazy approach to collection and delaying garbage collection until it is needed.
Do You Use Finalizers?
Finalization has an impact on performance. Objects that need finalization must necessarily survive at least one more garbage collection than they otherwise would; therefore, they tend to get promoted to older generations.
As a design consideration, you should wrap unmanaged resources in a separate class and implement a finalizer on this class. This class should not reference any managed object. For example, if you have a class that references managed and unmanaged resources, wrap the unmanaged resources in a separate class with a finalizer and make that class a member of the outer class. The outer class should not have a finalizer.
Identify which of your classes implement finalizers and consider the following questions:
* Does your class need a finalizer?
Only implement a finalizer for objects that hold unmanaged resources across calls. Avoid implementing a finalizer on classes that do not require it, because it adds load to the finalizer thread as well as the garbage collector.
* Does your class implement
IDisposable?Check that any class that provides a finalizer also implements
IDisposable, using the Dispose pattern described in Chapter 5, "Improving Managed Code Performance."
* Does your Dispose implementation suppress finalization?
Check that your Dispose method calls
GC.SuppressFinalization. GC.SuppressFinalization instructs the runtime to not call Finalize on your object because the cleanup has already been performed.
* Can your Dispose method be safely called multiple times?
Check that clients can call Dispose multiple times without causing exceptions. Check that your code throws an
ObjectDisposedException exception from methods (other than Dispose) if they are invoked after calling Dispose.
* Does your Dispose method call base class Dispose methods?
If your class inherits from a disposable class, then make sure that it calls the base class's Dispose.
* Does your Dispose method call Dispose on class members?
If you have any member variables that are disposable objects, they too should be disposed.
* Is your finalizer code simple?
Check that your finalizer code releases resources simply, and does not perform more complex operations. Anything else adds overhead to the dedicated finalizer thread which can result in blocking.
* Is your cleanup code thread safe?
For your thread safe types, make sure that your cleanup code is also thread safe. You need to do this to synchronize your cleanup code in the case where multiple client threads call Dispose at the same time.
Do You Use Unmanaged Resources Across Calls?
Check that any class that uses an unmanaged resource, such as a database connection across method calls, implements the
IDisposable interface. If the semantics of the object are such that a Close method is more logical than a Dispose method, provide a Close method in addition to Dispose.
Do You Use Buffers for I/O Operations?
If your code performs I/O or long-running calls that require pinned memory, investigate where in your code the buffers are allocated. You can help reduce heap fragmentation by allocating them when your application starts. This increases the likelihood that they end up together in generation 2, where the cost of the pin is largely eliminated. You should also consider reusing and pooling the buffers for efficiency.
Looping and Recursion
Inefficient looping and recursion can create many bottlenecks. Also, any slight inefficiency is magnified, due to it being repeatedly executed. For this reason, you should take extra care to ensure the code within the loop or the recursive method is optimized. For more information about the questions and issues raised in this section, see "Iterating and Looping" in Chapter 5, "Improving Managed Code Performance." Use the following review questions to help identify performance issues in your loops:
* Do you repetitively access properties?
* Do you use recursion?
* Do you use foreach?
* Do you perform expensive operations within your loops?
Do You Repetitively Access Properties?
Repeated accessing of object properties can be expensive. Properties can appear to be simple, but might in fact involve expensive processing operations.
Do You Use Recursion?
If your code uses recursion, consider using a loop instead. A loop is preferable in some scenarios because each recursive call builds a new stack frame for the call. This results in consumption of memory, which can be expensive depending upon the number of recursions. A loop does not require any stack frame creation unless there is a method call inside the loop.
If you do use recursion, check that your code establishes a maximum number of times it can recurse, and ensure there is always a way out of the recursion and no danger of running out of stack space.
Do You Use foreach?
Using foreach can result in extra overhead because of the way enumeration is implemented in .NET Framework collections. .NET Framework 1.1 collections provide an enumerator for the foreach statement to use by overriding the
IEnumerable.GetEnumerator. This approach is suboptimal because it introduces both managed heap and virtual function overhead associated with foreach on simple collection types. This can be a significant factor in performance-sensitive regions of your application. If you are developing a custom collection for your custom type, consider the following guidelines while implementing
IEnumerable: * If you implement
IEnumerable.GetEnumerator, also implement a nonvirtual
GetEnumerator method. Your class's
IEnumerable.GetEnumerator method should call this nonvirtual method, which should return a nested public enumerator struct.
* Explicitly implement the
IEnumerator.Current property on your enumerator struct.
For more information about implementing custom collections and about how to implement
IEnumerable as efficiently as possible, see “Collection Guidelines” in Chapter 5, “Improving Managed Code Performance.”
Consider using a for loop instead of foreach to increase performance for iterating through .NET Framework collections that can be indexed with an integer.
Do You Perform Expensive Operations Within Your Loops?
Examine the code in your loop and look for the following opportunities for optimization:
* Move any code out of the loop that does not change in the loop.
* Investigate the methods called inside the loop. If the called methods contain small amounts of code, consider inlining them or parts of them.
* If the code in the loop performs string concatenation, make sure that it uses
StringBuilder. * If you test for multiple exit conditions, begin the expression with the one most likely to allow you to exit.
* Do not use exceptions as a tool to exit one or more loops.
* Avoid calling properties within loops and if you can, check what the property accessor does. Calling a property can be a very expensive operation if the property is performing complex operations.
String Operations
Review your code to see how it performs string manipulation. Intensive string manipulation can significantly degrade performance. Consider the following questions when reviewing your code's string manipulation:
* Do you concatenate strings?
* Do you use
StringBuilder? * Do you perform string comparisons?
Do You Concatenate Strings?
If you concatenate strings where the number of appends is known, you should use the + operator as follows.
String str = "abc" + "def" + "ghi";
If the number and size of appends is unknown, such as string concatenation in a loop, you should use the
StringBuilder class as follows.
for (int i=0; i< Results.Count; i++){
[StringBuilder.Append] (Results[i]);
}
Do You Use
StringBuilder?StringBuilder is efficient for string concatenation where the number and size of appends is unknown. Some of the scenarios which demonstrate an efficient way of using
StringBuilder are as follows.
* String concatenation
//Prefer this
StringBuilder sb;
sb.Append(str1);
sb.Append(str2);
//over this
sb.Append(str1+str2);
* Concatenating strings from various functions
//Prefer this
void f1( sb,…);
void f2( sb,…);
void f3( sb,…);
//over this
StringBuilder sb;
sb.Append(f1(…));
sb.Append(f2(…));
sb.Append(f3(…));
Do You Perform String Comparisons?
Check whether your code performs case-insensitive string comparisons. If it does, check that it uses the following overloaded Compare method.
String.Compare (string strA, string strB, bool ignoreCase);
Watch out for code that calls the
ToLower method. Converting strings to lowercase and then comparing them involves temporary string allocations. This can be very expensive, especially when comparing strings inside a loop.
More Information
For more information about the issues raised in this section, see "String Operations" in Chapter 5, "Improving Managed Code Performance."
Exception Handling
Managed code should use exception handling for robustness, security, and to ease maintenance. Used improperly, exception management can significantly affect performance. For more information about the questions and issues raised in this section, see "Exception Management" in Chapter 5, "Improving Managed Code Performance." Use the following review questions to help ensure that your code uses exception handling efficiently:
* Do you catch exceptions you cannot handle?
* Do you control application logic with exception handling?
* Do you use finally blocks to ensure resources are freed?
* Do you use exception handling inside loops?
* Do you re-throw exceptions?
Do You Catch Exceptions You Cannot Handle?
You should catch exceptions for very specific reasons, because catching generally involves rethrowing an exception to the code that calls you. Rethrowing an exception is as expensive as throwing a new exception.
Check that when your code catches an exception, it does so for a reason. For example, it might log exception details, attempt to retry a failed operation, or wrap the exception in a new exception and throw the outer exception back to the caller. This operation should be performed carefully and should not obscure error details.
Do You Control Application Logic with Exception Handling?
Check that your code does not use exception handling to control the flow of your normal application logic. Make sure that your code uses exceptions for only exceptional and unexpected conditions. If you throw an exception with the expectation that something other than a general purpose handler is going to do anything with it, you have probably done something wrong. You can consider using bool return values if you need to specify the status (success or failure) of a particular activity.
For example, you can return false instead of throwing an exception if a user account was not found in the database. This is not an exceptional condition. Failing to connect to the database, however, warrants an exception.
Do You Use Finally Blocks to Ensure Resources Are Freed?
Make sure that resources are closed after use by using try/catch blocks. The finally block is always executed, even if an exception occurs, as shown in the following example.
SqlConnection conn = new
SqlConnection(connString);try
{
conn.Open(); // Open the resource
}
finally
{
if(null!=conn)
conn.Close(); // Always executed even if an exception occurs
}
Note: C# provides the using construct that ensures an acquired resource is disposed at the end of the construct. The acquired resource must implement
System.IDisposable or a type that can be implicitly converted to
System.IDisposable, as shown in the following example.
Font
MyFont3 = new Font("Arial", 10.0f);
using
(MyFont3){
// use [MyFont3]
} // compiler will generate code to call Dispose on
MyFont3 Do You Use Exception Handling Inside Loops?
Check if your code uses exceptions inside loops. This should be avoided. If you need to catch an exception, place the try/catch block outside the loop for better performance.
Do You Rethrow Exceptions?
Rethrowing exceptions is inefficient. Not only do you pay the cost for the original exception, but you also pay the cost for the exception that you rethrow.
Rethrowing exceptions also makes it harder to debug your code because you cannot see the original location of the thrown exception in the debugger. A common technique is to wrap the original exception as an inner exception. Therefore, if you choose to rethrow, your additional information from the inner exception is competing value-wise with superior debugging you would get if you had done nothing.
Arrays
Arrays provide basic functionality for grouping types. To ensure that your use of arrays is efficient, review the following questions:
* Do you use strongly typed arrays?
* Do you use multidimensional arrays?
Do You Use Strongly Typed Arrays?
Identify places in your code where you use object arrays (arrays containing the Object type). If you use object arrays to store other types, such as integers or floats, the values are boxed when you add them to the array. Use a strongly typed array instead, to avoid the boxing. For example, to store integers, use the following.
int[] arrIn = new int
10;
Use the preceding to store integers instead of the following.
Object[] arrObj = new Object
10;
Do You Use Multidimensional Arrays?
If your code uses multidimensional arrays, see if you can replace the code with a jagged array (a single dimensional array of arrays) to benefit from MSIL performance optimizations.
Note: Jagged arrays are not CLS compliant and may not be used across languages. For more information, see “Use Jagged Arrays Instead of Multidimensional Arrays” in Chapter 5, "Improving Managed Code Performance."
Collections
To avoid creating bottlenecks and introducing inefficiencies, you need to use the appropriate collection type based on factors such as the amount of data you store, whether you need to frequently resize the collection, and the way in which you retrieve items from the collection.
For design considerations, see Chapter 4, "Architecture and Design Review of a .NET Application for Performance and Scalability." Chapter 4 addresses the following questions:
* Are you using the right collection type?
* Have you analyzed the requirements?
* Are you creating your own data structures unnecessarily?
* Are you implementing custom collections?
For more information see "Collection Guidelines" in Chapter 5, "Improving Managed Code Performance." Chapter 5 asks the following questions:
* Do you need to sort your collection?
* Do you need to search your collection?
* Do you need to access each element by index?
* Do you need a custom collection?
Review the following questions if your code uses arrays or one of the .NET Framework collection classes:
* Have you considered arrays?
* Do you enumerate through collections?
* Do you initialize the collection to an approximate final size?
* Do you store value types in a collection?
* Have you considered strongly typed collections?
* Do you use
ArrayList? * Do you use Hashtable?
* Do you use
SortedList? Have You Considered Arrays?
Arrays avoid boxing and unboxing overhead for value types, as long as you use strongly typed arrays. You should consider using arrays for collections where possible unless you need special features such as sorting or storing the values as key/value pairs.
Do You Enumerate Through Collections?
Enumerating through a collection using foreach is costly in comparison to iterating using a simple index. You should avoid foreach for iteration in performance-critical code paths, and use for loops instead.
Do You Initialize the Collection to an Approximate Final Size?
It is more efficient to initialize collections to a final approximate size even if the collection is capable of growing dynamically. For example, you can initialize an
ArrayList using the following overloaded constructor.
ArrayList ar = new
ArrayList (43);
Do You Store Value Types in a Collection?
Storing value types in a collection involves a boxing and unboxing overhead. The overhead can be significant when iterating through a large collection for inserting or retrieving the value types. Consider using arrays or developing a custom, strongly typed collection for this purpose.
Note: At the time of this writing, the .NET Framework 2.0 (code-named "Whidbey") introduces generics to the C# language that avoid the boxing and unboxing overhead.
Have You Considered Strongly Typed Collections?
Does your code use an
ArrayList for storing string types? You should prefer
StringCollection over
ArrayList when storing strings. This avoids casting overhead, which occurs when you insert or retrieve items and also ensures that type checking occurs. You can develop a custom collection for your own data type. For example, you could create a Cart collection to store objects of type
CartItem.Do You Use
ArrayList?If your code uses
ArrayList, review the following questions:
* Do you store strongly typed data in
ArrayLists?Use
ArrayList to store custom object types, particularly when the data changes frequently and you perform frequent insert and delete operations. By doing so, you avoid the boxing overhead. The following code fragment demonstrates the boxing issue.
ArrayList al = new
ArrayList();al.Add(42.0F); // Implicit boxing because the Add method takes an object
float f = (float)al
0; // Item is unboxed here
* Do you use Contains to search
ArrayLists?Store presorted data and use
ArrayList.BinarySearch for efficient searches. Sorting and linear searches using Contains are inefficient. This is of particular significance for large lists. If you only have a few items in the list, the overhead is insignificant. If you need several lookups, then consider Hashtable instead of
ArrayList Do You Use Hashtable?
If your code uses a Hashtable collection of key/value pairs, consider the following review questions:
* Do you store small amounts of data in a Hashtable?
If you store small amounts of data (10 or fewer items), this is likely to be slower than using a
ListDictionary. If you do not know the number of items to be stored, use a
HybridDictionary. * Do you store strings?
Prefer
StringDictionary instead of Hashtable for storing strings, because this preserves the string type and avoids the cost of up-casting and down-casting during storing and retrieval.
Do You Use
SortedList?You should use a
SortedList to store key-and-value pairs that are sorted by the keys and accessed by key and by index. New items are inserted in sorted order, so the
SortedList is well suited for retrieving stored ranges.
You should use
SortedList if you need frequent re-sorting of data after small inserts or updates. If you need to perform a number of additions or updates and then re-sort the whole collection, an
ArrayList performs better than the
SortedList.Evaluate both collection types by conducting small tests and measuring the overall overhead in terms of time taken for sorting, and choose the one which is right for your scenario.
Locking and Synchronization
To help assess the efficiency of your locking and synchronization code, use the following questions:
* Do you use Mutex objects?
* Do you use the Synchronized attribute?
* Do you lock "this"?
* Do you lock the type of an object?
* Do you use
ReaderWriterLocks? Do You Use Mutex Objects?
Review your code and make sure that Mutex objects are used only for cross-process synchronization and not cross-thread synchronization in a single process. The Mutex object is significantly more expensive to use than a critical section with the Lock (C#) or
SyncLock (VB) statement.
Do You Use the Synchronized Attribute?
See which of your methods are annotated with the synchronized attribute. This attribute is coarse-grained and it serializes access to the entire method such that only one thread can execute the method at any given instance, with all threads waiting in a queue. Unless you specifically need to synchronize an entire method, use an appropriate synchronization statement (such as a lock statement) to apply granular synchronization around the specific lines of code that need it. This helps to reduce contention and improve performance.
Do You Lock "this"?
Avoid locking "this" in your class for correctness reasons, not for any specific performance gain. To avoid this problem, provide a private object to lock on.
public class A {
…
lock(this) { … }
…
}
// Change to the code below:
public class A
{
private Object thisLock = new Object();
…
lock(thisLock) { … }
…
}
Use this approach to safely synchronize only relevant lines of code. If you require atomic updates to a member variable, use System.Threading.Interlocked.
Do You Lock The Type of an Object?
Avoid locking the type of the object, as shown in the following code sample.
lock(typeof(MyClass)); If there are other threads within the same process that lock on the type of the object, this might cause your code to hang until the thread locking the type of the object is finished executing.
This also creates a potential for deadlocks because there might be some other application in a different application domain but same process acquiring a lock on the same type and never releasing it.
Consider providing a static object in your class instead, and use that as a means of synchronization.
private static Object _lock = new Object();
lock(_lock);
For more information, see "A Special Dr. GUI: Don't Lock Type Objects!" on MSDN at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaskdr/html/askgui06032003.asp.
Do You Use
ReaderWriterLock?Check whether your code uses
ReaderWriterLock objects to synchronize multiple reads and occasional writes. You should prefer the
ReaderWriterLock over the other locking mechanisms such as lock and Monitor, where you need to occasionally update data which is read frequently, such as a custom cache collection. The
ReaderWriterLock allows multiple threads to read a resource concurrently but requires a thread to wait for an exclusive lock to write the resource.
For more information, see "ReaderWriterLock Class" in the .NET Framework Class Library on MSDN at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfSystemThreadingReaderWriterLockClassTopic.asp.More Information
For more information about the questions and issues raised in this section, see "Locking and Synchronization" and "Locking and Synchronization Guidelines" in Chapter 5, "Improving Managed Code Performance."
To review your approach to locking and synchronization from a design perspective, see "Concurrency" in Chapter 4, "Architecture and Design Review for .NET Application Performance and Scalability."
Threading
If you misuse threads, you can easily reduce your application's performance rather than improve it. Review your code by using the following questions to help identify potential performance-related issues caused by misuse of threads or inappropriate threading techniques. For more information about the questions and issues raised in this section, see "Threading" in Chapter 5, "Improving Managed Code Performance."
* Do you create additional threads?
* Do you call Thread.Suspend or Thread.Resume?
* Do you use volatile fields?
* Do you execute periodic tasks?
Do You Create Additional Threads?
You should generally avoid creating threads, particularly in server-side code — use the CLR thread pool instead. In addition to the cost of creating the underlying operating system thread, frequently creating new threads can also lead to excessive context switching, memory allocation, and additional cleanup when the thread dies. Recycling threads with the thread pool generally leads to superior results.
Do You Call Thread.Suspend or Thread.Resume?
Use synchronization objects if you need to synchronize threads. Calling Thread.Suspend and Thread.Resume to synchronize the activities of multiple threads can cause deadlocks. Generally, Suspend and Resume should be used only in the context of debugging or profiling, and not at all for typical applications. Use synchronization objects such as
ManualResetEvent objects if you need to synchronize threads.
Do You Use Volatile Fields?
Limit the use of the volatile keyword because volatile fields restrict the way the compiler reads and writes the contents of the fields. Volatile fields are not meant for ensuring thread safety.
Do You Execute Periodic Tasks?
If you require a single thread for periodic tasks, it is cheaper to have just one thread explicitly executing the periodic tasks and then sleeping until it needs to perform the task again. However, if you require multiple threads to execute periodic tasks for each new request, you should use the thread pool.
Use the Threading.Timer class to periodically schedule tasks. The Timer class uses the CLR thread pool to execute the code.
Note that a dedicated thread is more likely to get scheduled at the correct time than a pooled thread. This is because if all threads are busy, there could be a delay between the scheduled time of the background work and a worker thread becoming available. If there is a dedicated thread for the background work, a thread will be ready at the appointed time.
Asynchronous Processing
You can use asynchronous calls to help increase application concurrency.
To ensure that you use asynchronous processing appropriately, review the following questions:
* Do you poll for asynchronous invocation resources?
* Do you call
EndInvoke after calling
BeginInvoke? Do You Poll for Asynchronous Invocation Results?
Avoid polling for asynchronous invocation results. Polling is inefficient and uses precious processor cycles which can be used by other server threads. Use a blocking call instead. Methods of
AsyncResult.AsyncWaitHandle.WaitHandle class such as
WaitOne, WaitAll, and
WaitAny are good examples of blocking calls.
Do You Call
EndInvoke After Calling
BeginInvoke?Review your code to see where it calls
BeginInvoke to use asynchronous delegates. For each call to
BeginInvoke, make sure your code calls
EndInvoke to avoid resource leaks.
More Information
For more information about the questions and issues raised in this section, see "Asynchronous Calls" and "Asynchronous Calls Guidelines" in Chapter 5, "Improving Managed Code Performance."
To review your design and how it uses asynchronous processing see "Concurrency" in Chapter 4, "Architecture and Design Review for .NET Application Performance and Scalability."
Serialization
Inefficient serialization code is a common performance-related problem area. To review whether your code uses serialization, search for the "Serializable" string. Classes that support serialization should be decorated with the
SerializableAttribute; they may also implement
ISerializable. If your code does use serialization, review the following questions:
* Do you serialize too much data?
* Do you serialize
DataSet objects?
* Do you implement
ISerializable? Do You Serialize Too Much Data?
Review which data members from an object your code serializes. Identify items that do not need to be serialized, such as items that can be easily recalculated when the object is deserialized. For example, there is no need to serialize an Age property in addition to a
DateOfBirth property because the Age can easily be recalculated without requiring significant processor power. Such members can be marked with the
NonSerialized attribute if you use the
SoapFormatter or the
BinaryFormatter or the
XmlIgnore attribute if you use the
XmlSerializer class, which Web services use.
Also identify opportunities to use structures within your classes to encapsulate the data that needs to be serialized. Collecting the logical data in a data structure can help reduce round trips and lessen the serialization impact.
Do You Serialize
DataSet Objects?
The
DataSet object generates a large amount of serialization data and is expensive to serialize and deserialize. If your code serializes
DataSet objects, make sure to conduct performance testing to analyze whether it is creating a bottleneck in your application. If it is, consider alternatives such as using custom classes.
Do You Implement
ISerializable?If your classes implement
ISerializable to control the serialization process, be aware that you are responsible for maintaining your own serialization code. If you implement
ISerializable simply to restrict specific fields from being serialized, consider using the Serializable and
NonSerialized attributes instead. By using these attributes, you will automatically gain the benefit of any serialization improvements in future versions of the .NET Framework.
More Information
For more information about improving serialization performance and
DataSet serialization, see "How To: Improve Serialization Performance" in the "How To" section of this guide.
For more information about the various options for passing data across the tiers of a distributed .NET application, see Chapter 4, "Architecture and Design Review of a .NET Application for Performance and Scalability."
Visual Basic Considerations
When optimized, Visual Basic .NET code can perform as well as C# code. If you have ported existing Visual Basic code to Visual Basic .NET, performance is unlikely to be optimized because you are unlikely to be using the best .NET coding techniques. If you have Visual Basic .NET source code, review the following questions:
* Have you switched off int checking?
* Do you use on error goto?
* Do you turn on Option Strict and Explicit?
* Do you perform lots of string concatenation?
Have You Switched Off int Checking?
Int checking is beneficial during development, but you should consider turning it off to gain performance in production. Visual Basic turns on int checking by default, to make sure that overflow and divide-by-zero conditions generate exceptions.
Do You Use On Error Goto?
Review your code to see if it uses the on error goto construct. If it does, you should change your code to use the .NET structured exception handling with Try/Catch/Finally blocks. The following code uses on error goto.
Sub
AddOrderOld(connstring)
On Error [GoTo] endFunc
Dim dataclass As [DAOrder] = New [DAOrder]
Dim conn As [SqlConnection] = New
[SqlConnection(connstring)]
[dataclass.AddOrder(conn)]
[EndFunc:]
If Not(conn is Nothing) Then
conn.Close()
End If
End Sub
The following code shows how this should be rewritten, using exception handling.
Sub
AddOrder(connstring)
Dim conn As [SqlConnection]
Try
Dim dataclass As [DAOrder] = New [DAOrder]
conn = New [SqlConnection(connstring)]
[dataclass.AddOrder(conn)]
Catch ex As Exception
' Exception handling code
Finally
If Not(conn is Nothing) Then
conn.Close()
End If
End Try
End Sub
Do You Turn on Option Strict and Explicit?
Review your code and ensure that the Strict and Explicit options are turned on. This ensures that all narrowing type coercions must be explicitly specified. This protects you from inadvertent late binding and enforces a higher level of coding discipline. Option Explicit forces you to declare a variable before using it by moving the type-inference from run time to compile time. The code for turning on Explicit and Strict is shown in the following code sample.
Option Explicit On
Option Strict On
If you compile from the command line using the Vbc.exe file, you can indicate that the compiler should turn on Strict and Explicit as follows.
vbc mySource.vb /optionexplicit+ /optionstrict+
Do You Perform Lots of String Concatenation?
If your code performs lots of string concatenations, make sure that it uses the
StringBuilder class for better performance.
Note: If you use ASP.NET to emit HTML output, use multiple Response.Write calls instead of using a
StringBuilder. Reflection and Late Binding
Use the following review questions to review your code's use of reflection:
If your code uses reflection, review the following questions:
* Do you use .NET Framework classes that use reflection?
* Do you use late binding?
* Do you use System.Object to access custom objects?
Do You Use .NET Framework Classes that Use Reflection?
Analyze where your code uses reflection. It should be avoided on the critical path in an application, especially in loops and recursive methods. Reflection is used by many .NET Framework classes. Some common places where reflection is used are the following:
* The page framework in ASP.NET uses reflection to create the controls on the page, and hook event handlers. By reducing the number of controls, you enable faster page rendering.
* Framework
APIs such as
Object.ToString use reflection. Although
ToString is a virtual method, the base Object implementation of
ToString uses reflection to return the type name of the class. Implement
ToString on your custom types to avoid this.
* The .NET Framework remoting formatters,
BinaryFormatter and
SOAPFormatter, use reflection. While they are fast for referenced objects, they can be slow for value types which have to be boxed and unboxed to pass through the reflection API.
Do You Use Late Binding?
In Visual Basic .NET, a variable is late bound if it is declared as an Object or is without an explicit data type. When your code accesses members on late-bound variables, type checking and member lookup occurs at run time. As a result, early-bound objects have better performance than late-bound objects. The following example shows a data class being assigned to an object.
Sub
AddOrder()
Dim dataclass As Object = New [DAOrder]
' Dim dataclass as [DAOrder] = New [DAOrder] will improve performance
' Do other processing
End Sub
Do You Use System.Object to Access Custom Objects?
Avoid using System.Object to access custom objects because this incurs the performance overhead of reflection. Use this approach only in situations where you cannot determine the type of an object at design time.
More Information
For more information about the questions and issues raised in this section, see "Reflection and Late Binding" in Chapter 5, "Improving Managed Code Performance."
Code Access Security
Code access security supports the safe execution of semi-trusted code, protects users from malicious software, and prevents several kinds of attacks. It also supports the controlled, code identity-based access to resources. Use the following review questions to review your use of code access security:
* Do you use declarative security?
* Do you call unmanaged code?
Do You Use Declarative Security?
Where possible, it is recommended that you use declarative security instead of imperative security checks. The current implementation of demand provides better performance and support with the security tools that are currently being built to help security audits.
Note that if your security checks are conditional within a method, imperative security is your only option.
Do You Call Unmanaged Code?
When calling unmanaged code, you can remove the runtime security checks by using the
SuppressUnmanagedCodeSecurity attribute. This converts the check to a
LinkDemand check, which is much faster. However, you should only do so if you are absolutely certain that your code is not subject to luring attacks.
More Information
For more information about the questions and issues raised in this section, see "Code Access Security" in Chapter 5, "Improving Managed Code Performance."
For more information about the danger of luring attacks and the potential risks introduced by using
SuppressUnmanagedCodeSecurity and
LinkDemand, see Chapter 8, "Code Access Security in Practice" in "Improving Web Application Security: Threats and Countermeasures" on MSDN at
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/ThreatCounter.asp.Class Design Considerations
Review your class design using the following questions:
* Do you use properties?
* Do you define only the required variables as public?
* Do you seal your classes or methods?
Do You Use Properties?
You can expose class-level member variables by using public fields or public properties. The use of properties represents good object-oriented programming practice because it allows you to encapsulate validation and security checks and to ensure that they are executed when the property is accessed.
Properties must be simple and should not contain more code than required for getting/setting and validation of the parameters. Properties can look like inexpensive fields to clients of your class, but they may end up performing expensive operations.
Do You Define Only the Required Variables As Public?
You can scope member variables as either public or private members. Think carefully about which members should be made public because with public members you run the risk of exposing sensitive data that can easily be manipulated. In addition to security concerns, you should also avoid unnecessary public members to prevent any additional serialization overhead when you use the
XmlSerializer class, which serializes all public members by default.
Do You Seal Your Classes or Methods?
If you do not want anybody to extend your base classes, you should mark them with the sealed keyword. Also, if you derive from a base class that has virtual members and you do not want anybody to extend the functionality of your derived class, consider sealing the virtual members in the derived class. Sealing the virtual methods makes them candidates for inlining and other compiler optimizations.
Ngen.exe
The Native Image Generator utility (Ngen.exe) allows you to precompile your assemblies to avoid JIT compilation at run time. However, Ngen.exe does not guarantee improved performance and you should carefully consider whether to use it.
Ngen.exe cannot be used on assemblies that need to be shared across application domains. Therefore, sharing code is one of the prime considerations for choosing Ngen.exe. When considering using Ngen.exe, review the following questions:
* Do you precompile Windows Forms applications?
* Do you create large shared libraries?
* Do you use application domains?
Do You Precompile Windows Forms Applications?
Windows® Forms applications use a large number of shared libraries provided with the .NET Framework. As a result, the load and initialization time for Windows Forms applications can be much higher than other kinds of applications. While not always the case, precompiling Windows Forms applications usually improves performance. You should test your application with and without precompilation, to be sure.
Do You Create Large Shared Libraries?
Precompiling your code using Ngen.exe generally helps if you create large shared libraries, because you pay the cost of loading then much more often. Microsoft precompiles the .NET Framework assemblies because the assemblies are shared across applications. This reduces the working set size and improves startup time.
Summary
Performance and scalability code reviews are similar to regular code reviews or inspections, except that the focus is on the identification of coding flaws that can lead to reduced performance and scalability.
This chapter has shown how to review managed code for top performance and scalability issues. It has also shown you how to identify other more subtle flaws that can lead to performance and scalability issues.
Performance and scalability code reviews are not a panacea. However, they can be very effective and should be a regular milestone in the development life cycle.
Additional Resource
For more information, see the following resources:
* Chapter 4 "Architecture and Design Review of a .NET Application for Performance and Scalability."
* Chapter 6, "Improving ASP.NET Performance."
* Chapter 7, "Improving Interop Performance."
* Chapter 8, "Improving Enterprise Services Performance."
* Chapter 9, "Improving XML Performance."
* Chapter 10, "Improving Web Services Performance."
* Chapter 11, "Improving Remoting Performance."
* Chapter 12, "Improving ADO.NET Performance."
For printable checklists, see the following checklists in the "Checklists” section of this guide:
* "Checklist: ASP.NET Performance."
* "Checklist: Managed Code Performance."
* "Checklist: Enterprise Services Performance."
* "Checklist: Interop Performance."
* "Checklist: Remoting Performance."
* "Checklist: Web Services Performance."
* "Checklist: XML Performance."
For further reading, see the following resource:
* For more information about designing for performance, see "Performance" on MSDN at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vsent7/html/vxconPerformance.asp?frame=true.
Return to
HomePage