Runtime Analysis in Visual Studio. Net




Дата канвертавання24.04.2016
Памер51.99 Kb.
Runtime Analysis in Visual Studio .NET
Introduction

Visual Studio.NET is a new and exciting programming environment from Microsoft. It attracted the unprecedented number of Beta testers and it is surely on its way to become the standard programming environment on Windows, the successor of widely spread and popular DevStudio and Visual Studio programming environments, also coming from Microsoft.


The Visual Studio.NET programming environment simplifies the development process, especially development of web applications. However, even though the process of creating Windows applications has become simpler and in the case of managed code applications – immune to some typical C++ hurdles, there are still various obstacles that can prevent Visual Studio.NET projects from succeeding. Numerous memory errors and memory leaks in unmanaged code applications, or excessive memory usage and performance issues in managed code applications, also known from the existing native (Visual C++) and managed code (Java, VB p-code) development environments are some of the possible problems every .NET developer will sooner, or later have to deal with.
A discipline of software development called Runtime Analysis helps in preventing these potential showstoppers from occurring and it helps building fast and reliable .NET applications. The Runtime Analysis solution from Rational Software is a product called Rational PurifyPlus.


Managed and unmanaged applications – what’s the difference?
Visual Studio.NET programming environment and .NET Framework offer some new choices for the developers of Windows applications:
C++ developers now have a choice between building native Windows applications (unmanaged code), and building applications that use the CLR (Common Language Runtime) in order to run (managed code). Additionally it is also possible to create applications that have portions of the code marked as “safe”, meaning that this part of the applications will take advantage of the CLR automatic memory management. A similar feature is built into C# as well, whereas the default for the C# applications is “safe”. Visual Basic.NET applications are all executed through Common Language Runtime.
Native applications are Windows applications as we know them today. The source code is compiled into the PE formatted executable binaries where PE stands for Portable Executable. These applications can be executed by the Windows operating system. The runtime environment for native applications is built into the operating system, or has to be installed separately like in the case of Visual Basic runtime modules.

Managed .NET applications use .NET Common Language Runtime (CLR) as the foundation built on top of the operating system that enables the execution of the IL (Intermediate Language) binaries. In managed code applications the source code is not compiled into a native Win32 PE formatted executable file, but rather into an IL file (Intermediate Language], that is basically an extension of the PE format. The Intermediate Language code is compiled into assemblies that look like Windows EXE, or DLL files. Each assembly has PE header extensions that indicate that this is a managed module and has to be executed through the .NET CLR. This architecture opens up many new possibilities in the development of .NET applications. The programming language and the IL compiler is just a feature in the Visual Studio.NET programming environment. Visual Studio.NET and its common architecture allow developers to mix modules developed in different programming languages without having to use COM. The .NET Common Language Runtime controls the memory access, and it is also responsible for cleaning and managing of memory used by the managed code .NET applications.
.NET Garbage Collector
The heart of the .NET Common Language Runtime is the automatic memory management system. The managed code applications take advantage of the automatic memory management in order to avoid memory related problems common in C/C++ development. When .NET CLR receives a request for the creation of a new object it will allocate the object on the managed heap. From this moment on the life of this object is in the hands of the CLR memory management. The garbage collector as the vital part of this automatic memory management will browse the tree of objects and references that are created during the execution of an application and mark those objects that are “alive”, i.e. objects that have valid references to them. All unmarked objects represent the candidates for the garbage collection. Please note that the decision whether and when these objects will actually be released from memory is completely up to the garbage collector. There are several factors that will influence this decision. One is of course the state and type of references that an object in memory has. The age of the object in memory is also important. .NET uses a generational garbage collector. It follows the FIFO rule – “First In First Out” for the each generation of objects. The objects that survive a garbage collection intervention get shifted to one side of the managed heap and these remaining objects automatically become one generation older. The new objects allocated after a round of garbage collection become a generation older. The longer an object stays in memory, the lesser probability that it will be removed from it. Furthermore garbage collector will always check if an objects has a finalize() method specified. The finalize() method is not a destructor of an object as often wrongly assumed. It is a method where a developer can specify the procedures that need to be executed before an object is removed from memory. Finalize() method doesn’t not speed up the removal of an object, quite contrary it increases the life span of an object in memory.


The ups and downs of building managed code compared to building unmanaged code:
Unmanaged code is faster.

Unmanaged, native applications will always be faster than their managed counterparts simply because it takes some time to execute the intermediate language and transform its commands into the machine code, the only language that computers understand. Additionally, automatic memory management with its logic and memory optimization routines will also require some additional time for the execution compared to the native applications.


Managed code is safer.

The automatic memory management cleans the memory from objects with references that went out of scope, thus eliminating the possibility of having memory leaks. The CLR automatic memory management also prevents from directly accessing the memory used by the application and takes away the possibility of memory access violations, uninitialized memory reads etc. The automatic memory management however, does leave the possibility of having cases of excessive memory usage that can significantly reduce the performance of managed applications, or in some drastic case even cause errors and even crashes.


It seems like the tradeoff is fair; sacrificing some speed for more safety. At this point it is important to notice that more safety doesn’t mean absolute safety. Furthermore, bad performance of the managed code application could under some circumstances present a serious issue that can endanger the development project, just as a large number of memory errors can endanger the project of a native Windows application. Both, memory errors and memory related performance problems require significant time and engineering effort to solve. A field of software engineering that deals with these types of problems is called Runtime Analysis.

An example of a potential memory problem in Visual Studio.NET applications:
A) Unmanaged code
public class forEver {
void process (CString *s) {

// do something with the string

}

void runForEver () {



while (true) {

CString *s = new CString(“C++ is dangerous”);

process(s);

s = null; // Leak the string

}

}

}


The method runForEver allocates a CString variable every time it is executed. After the function returns, or as shown in the example for better understanding, when the reference to the object allocated on the heap is NULL-ed, the reference to the object created by using the memory allocation API new() that contains the string “C++ is dangerous” will be destroyed. As a consequence the memory allocated in the function will not be returned to the system. It is unreachable and represents a typical C++ memory leak. Such a problem cannot be detected during the compilation of the application. It actually takes to execute the method runForEver() in order to find the problem.
The Rational memory error and memory leak checking tool is called Rational Purify. This automated tool will check every byte of memory allocated by the application for errors in manipulation and for memory leaks like shown in this little code snippet.
B) Managed code (the same example in C#)
public class forEver {
void process (String s) {

// do something with the string

}

void runForEver () {



while (true) {

String s = new String(“C# is Great”);

process(s);

s = null; // GC frees the unused memory

}

}

}


The Common Language Runtime managed application does not allow memory leaks to happen. The Garbage Collection of the CLR keeps a list of all the objects created dynamically on the heap and checks if these objects have valid references to them. If it detects that an object is no longer in use it becomes a candidate for garbage collection. The garbage collector doesn’t automatically remove it from the memory. Depending on the memory usage of the application, the algorithm of the garbage collection, the age of the object and the method finalize() it will decide when to release the memory used by this object.
Managed code is safe from memory leaks, where memory leak per definition a block of memory on the heap that has no pointers to this block, or to anywhere within this block. Does this mean that managed code is safe from memory problems? Not at all! Check the following modification of the above example.
C) Managed code (slightly modified example)
Suppose we use the above piece of code in an application and we decided to modify it in order to cache the string allocated in runForEver() for the later reference. Here is an example:
public class forEver {

Array bottomLess;

void process (String s) { // do something with the string

bottomLess.add (s); // and cache it for later reference

}

void runForEver () {



while (true) {

String s = new String(“C# is Great”);

process(s);

s = null; // The memory is no longer freed



}

}

}


It looks like nothing much has changed, but is it really the case? This time we have a hard reference to the String when we decide to NULL the string in the method runForEver(). The Garbage Collector will consider string s to be in use and it will not be removed from memory. It is not a leak in the C++ sense, but it may be considered as a logical memory leak.
The above example is just one of the possible pitfalls that can unexpectedly sneak into the otherwise perfectly correct code and cause a big performance impact. The way to inspect the .NET managed code application for this another possible memory usage errors is by creating memory profiles of the tested .NET applications by using the runtime analysis tools.
What is Runtime Analysis?
Runtime Analysis is one of the key activities in building quality software. Quality in this respect means building software applications that are reliable (no crashes, peculiar error messages, false results etc.), applications that are fast (performance) and applications that are thoroughly tested.
Every developer has performed runtime analysis at some point when building an application, by using a debugger for example. Runtime Analysis is necessary because without actually executing the application that uses dynamic memory allocation it is impossible to detect the memory errors and memory leaks due to errors in coding the native applications, or extensive memory usage in the manage code applications.
Furthermore, the performance of an application is hard to measure by just testing it with the stopwatch, or even by using the built-in user made procedures, because of the influence of the concurrently run processes, differences in hardware on the testing machines etc. Visual Studio.NET provides a set of CLR API’s that enables the runtime analysis tools to connect to the runtime during the execution of the managed code applications and collect the information about the events. Such runtime analysis tools are called performance profilers. One example of such a profiler tool is Rational PurifyPlus tool called Rational Quantify. The native, unmanaged applications need to be prepared for profiling in a process called instrumentation. The result of runtime analysis of the performance of an application in both cases is the same – a detailed report about times spent in executing the methods and lines of code.
The fourth segment of Runtime Analysis is the analysis of the coverage of the methods and lines of code that were actually exercised during the runtime analysis, or during unit, or functional testing. If the code was not executed then it also means that it wasn’t tested. Such code can have hidden defects, or simply present a performance burden if it just takes space without any use.
These four main areas of Runtime Analysis are:

  • Memory error and memory leak checking

  • Memory profiling

  • Performance profiling

  • Code coverage analysis



Why practicing Runtime Analysis?

The main reason for practicing Runtime Analysis on a daily basis is to detect memory and performance problems as early as possible. Can you allow yourself to use the most important customers as beta testers? Maybe once, but if you constantly ship poor quality software even the most patient customers will start looking for a similar competitive solution. The more advanced features are not an advantage if these features don’t work. OK, one can build a system testing group, employ a couple of Quality Assurance engineers and equip them with modern functional testing tools. Will they be able to detect all the problems before shipping the final product to the customer?


Not really. Nobody can test the new software on all possible hardware and software configurations that a potential customer may use. Furthermore, how often are these functional tests performed? Is functional testing done once in a project cycle, or once a month, maybe weekly? The longer the period between checking in the new code and the actual testing, the harder it will be to track down the exact cause of a defect detected during functional testing. This is just the beginning of a life story of a defect. The next question to answer after a defect is detected is how to relate the cause of the functional error to the source code and pinpoint the root cause of the problem?
Obviously, the best-case scenario would be to detect the cause of a potential error in software functionality by checking the software for the most common causes of unexpected behaviors while the application is being created – on the developers’ machine. The majority of the problems that manifest as poor performance of the final product, or as unexplainable crashes are caused by errors in memory allocation or by errors in memory usage in general. Detecting these problems during the application development is the main task of Runtime Analysis.
Surely, Runtime Analysis can also be practiced on a “need to have” basis as well, but the effect of reducing the time and the costs associated with fixing the problems are far bigger then when the problems are detected shortly after they have been created.
Runtime Analysis in Visual Studio.NET environment today
How to start practicing Runtime Analysis in the .NET environment right away? Here are some examples:


  1. Checking Visual C++.NET unmanaged code for memory access errors and memory leaks.

Choosing Visual Studio.NET environment doesn’t mean giving up the C++ performance and the power. The way to create fast applications that are reliable, i.e. to minimize risk of creating code that contains memory errors and leaks is to use Visual Studio.NET together with the PurifyPlus tool Rational Purify.


Rational Purify supports the debug data associated to the unmanaged code created with the Visual C++.NET compiler and uses this information to report memory access errors and memory leaks that cannot be detected statically, without running the application. This tool automates otherwise time consuming and tedious job of pinpointing the memory access errors and memory leaks.


Figure 1: Rational Purify memory error and leak report



  1. Checking .NET managed code applications for excessive memory usage

Creating applications where the performance is not critical doesn’t mean that performance is not important. Building web applications is a good example. Yes, an online web store is not an application where each method should have a response measured in microseconds, but the performance should stay within certain parameters. Nobody wants to shop in an on-line store where it takes a long time between requesting additional information about a certain product and the actual display of the information in the client application. How about a web store that crashes after providing information for 1013 users? That would certainly not be a product worth investing in. Rational Purify provides capabilities of creating memory profiles for .NET managed code applications. These profiles are snapshots of memory usage taken as the application runs. The profiling information can be collected for both the server side and for the client. This data can be saved for the later use, or compared and merged into the new datasets by using the advanced features of this memory profiling tool.




Figure 2: Rational Purify Call Graph for a memory profile

The advantage of having a professional memory profiling tool like Rational Purify is in the level of details in the memory profiles, but also in the variety of views to the same collection of profiling data.


The classical data representation in form of method and object tables allows easy sorting of important parameters. In order to check the age of objects in memory at the moment of creating a snapshot, it is enough to click on the Object View table and sort all the objects per age in memory:


Figure 3: Rational Purify Object List View
Further analysis of this memory profile could lead to the Object and Reference Browser that provides detailed look into the relationship among the objects. Object and Reference Browser can lead the user directly to the objects that anchor a lot of other objects by keeping the references to them. Do this object and its dependencies need to be in memory at that point in time. If not, removing the references to the anchor could potentially release not only the memory to this one object, but to all its dependencies as well:



Figure 4: Rational Purify Object And Reference Graph



  1. Checking .NET managed and .NET unmanaged code for performance

The first step toward improving the application performance is detecting the performance bottlenecks and in order to determine that it is necessary to create performance profiles of an application and measure times spent in executing each method and maybe even each line of code. Rational PurifyPlus tool Rational Quantify automates the process of finding performance bottlenecks by collecting performance data for the PUT (Program Under Test), it highlights the performance bottlenecks both numerically and visually and it also provides numerous views to the collected performance data set. The additional power of a comprehensive profiling tool like Rational Quantify is in its ability to compare the profiles of consecutive builds of an applications, allowing you to easily detect “Performance Leaks induced by code change. The results of comparing different data sets are displayed both numerically and graphically.




Figure 5: Rational Quantify Function Detail View
This is one of the views in Rational Quantify that displays the details about performance for a particular method, its callers and descendants. Other available views in Rational Quantify are the Call Graph (similar to the Purify Call Graph for memory displayed in the Image…), Function List View, and Run Summary (with the visual representation of threads and their status) and Annotated Source displayed in the following screenshot:



Figure 6: Rational Quantify Annotated Source View

  1. Analyzing code coverage in .NET managed and .NET unmanaged code

The fourth aspect of Runtime Analysis is determining how much code is actually exercised during the tests. Collecting code coverage is important not only to determine the quality of testing scenarios and the thoroughness of the system tests, but it is valuable information for each developer about the quality of the code that he, or she is delivering. Rational PurifyPlus tool PureCoverage collects code coverage information for both .NET managed and .NET unmanaged code on the method and the line level. Additionally PureCoverage code coverage information can be collected automatically while performing Purify reliability tests. PureCoverage pinpoints the methods and lines of code that were not tested. These methods and lines of code are potential sources of memory, or performance problems.




Figure 7: Rational PureCoverage File View
Advanced features in PurifyPlus – Pure APIs
Rational PurifyPlus runtime analysis tools provide a number of API functions (Application Programming Interface) to control data collection during program execution of managed applications. For unmanaged code applications there is a set of standard Quantify APIs.
Here is the list of currently available Pure API functions:
Program status functions IsRunning

IsRecordingData
Data collection functions AddAnnotation

ClearData

DisableRecordingData

SaveData

StartRecordingData

StopRecordingData
These functions can be inserted into the source code and the selected PurifyPlus tool will execute them during the run of the managed code application. The advantage of using the APIs is in the ability to create the profiling, or coverage data set for a portion of the application only. This can be very helpful when analyzing large scale applications, or when investigating the memory usage, performance, or code coverage of only one segment of an application. The PureAPI can be integrated in the daily debug builds of the developed application and used to automatically create profiles of the tested applications during functional testing.

Conclusion
Runtime Analysis as an integral part of software development significantly reduces the number of defects and the efforts invested in fixing the detected errors and performance problems. Starting new Visual Studio.NET projects offers a unique possibility to additionally reduce the development time by measuring the quality of the developed application by running performance and memory tests on a daily basis. The minimal amount of effort needed for adopting runtime analysis pays back after a short period of time in form of less time spent in debugging, more time available for introducing new features, more effective ways of measuring the quality of the .NET projects and more happy customers.
Rational PurifyPlus is a complete set of automated runtime analysis tools for improving application performance and quality, for software developers who need to build and deploy resilient, reliable software applications in C#, VB.NET, C/C++, Java and VB. It consists of Rational Purify, Quantify, and PureCoverage, packaged together at an attractive price with a common install and common licensing for convenience.
Building fast and reliable software is not a luxury, it’s a must!


Appendix:
Rational PurifyPlus documentation: www.rational.com/products/pqc/
Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework by Jeffrey Richter:

http://msdn.microsoft.com/msdnmag/issues/1100/gci/gci.asp
Garbage Collection—Part 2: Automatic Memory Management in the Microsoft .NET Framework by Jeffrey Richter

http://msdn.microsoft.com/msdnmag/issues/1200/gci2/gci2.asp
“Manage C# Objects” by Bill Wagner, DevX:

http://www.devx.com/premier/mgznarch/vbpj/2001/10oct01/ce0110/ce0110-1.asp
“Clear Common C# Hurdles” by Don Preuninger and Joe Dour, DevX:

http://www.devx.com/premier/mgznarch/vbpj/2001/10oct01/dp0110/dp0110-1.asp
“Introducing .NET” by J. Conard, P. Dengler, B. Francis, J. Glynn, B. Harvey, B. Hollis, R. Ramchandran, J. Schenken, S. Short, C. Ullman, Wrox Press Ltd., 2000.


База данных защищена авторским правом ©shkola.of.by 2016
звярнуцца да адміністрацыі

    Галоўная старонка