Click here to Skip to main content
Click here to Skip to main content

Profiling of C++-Applications in Visual Studio for Free

By , 11 Jul 2012
 

Introduction

While the Team- or Premium-editions of Visual Studio contain a profiler, users of e.g. the professional edition are lacking such a feature. In this article, I present a method for profiling C++-applications with freely available tools and a simple application for presenting the results in an informative view.

profileWithVsPerfCmd/Screenshot2.png

Of course, the resulting solution will not be as comfortable as an integrated profiler, but if you use profiling only when you find bottlenecks, this method is certainly sufficient - and €5.000 cheaper.

The following method was tested with Visual Studio 2008 Professional and Visual C++ 2010 Express.

The Method

The method consists of four steps:

  • Generate a special "/PROFILE" build using Visual Studio (all editions can do this).
  • Profile the freshly compiled application with the freely available Microsoft tool "VSPerfCmd".
  • Convert the resulting .vsp-file to .csv-files using "VSPerfReport".
  • Analyze the resulting - huge - csv-files with the attached Profile Result Viewer.

Generate a /PROFILE-build

Note: The following steps should of course be done with a release build and with debugging symbols enabled.

In Visual Studio 2008, go to the solution explorer, select the project and open the properties page. In the section, "Configuration Properties", choose "Linker" - "Advanced".
Set "Profile" to "Enable Profiling information (/PROFILE)" (the exact steps for other versions of Visual Studio might differ).

profileWithVsPerfCmd/screenshot1.png

Compile the project.

Generate a Profile Report (vsp)

First of all, install the Visual Studio Performance Tools. The Visual Studio 2008 Service Pack 1 Stand-Alone Profiler can be found at this link.

The generation of the report requires to enter a handful of commands in the command line.

Preparations in the Commandline

Open the command prompt. Define a shortcut to the performance tools and switch to the folder, your application is located in:

set pt="C:\Program Files\Microsoft Visual Studio 9.0\Team Tools\Performance Tools"
cd [my_app_folder]

Profiling a Full Application Run

The simplest method is to profile the full uptime of the application.

:: Start the profiler
%pt%\vsperfcmd /start:sample /output:my_sampled_data.vsp

:: Launch the application via the profiler
%pt%\vsperfcmd /launch:my_app.exe

:: Shut down the profiler (this command waits, until the application is terminated)
%pt%\vsperfcmd /shutdown

Profiling only part of the application Uptime

It is also possible, to profile only part of the application uptime.

Here the handling becomes a bit tedious, so I suppose it would be best to implement some simple app to automize the following steps. Anyways, I never really had the demand for profiling only part of the uptime, since I could (maybe by modifying the application's startup code) always come up with code, that spent 90% of its time in the relevant part. Nevertheless, if you cannot modify your code adequately, here are the additional steps.

Start vsperfcmd as above and attach it to your application afterwards:

:: Start the profiler
%pt%\vsperfcmd /start:sample /output:my_sampled_data.vsp

:: Send the profiler to a waiting state
%pt%\vsperfcmd /globaloff

:: Attach to your application by specifying the pid
%pt%\vsperfcmd /attach:[pid]

After attaching to your application, perform whatever preparation is necessary. Now initiate the function that you want to profile and activate the profiler.

:: Activate the profiler
%pt%\vsperfcmd /globalon 

When the function is finished (or you think enough time has passed), stop profiling:

:: Detach the profiler from your application
%pt%\vsperfcmd /detach

:: Shut down the profiler
%pt%\vsperfcmd /shutdown 

Convert the Profile Report to a csv-file

The tool vsperfreport loads symbol-files (.pdb) and merges them with the generated report (.vsp) to create .csv-Files.

:: generate the report files (.csv)
%pt%\vsperfreport /summary:all my_sampled_data.vsp 

If you are also interested in calls within system files, you can also specify a path to a symbol server or a local cache.

%pt%\vsperfreport /summary:all my_sampled_data.vsp 
/symbolpath:"srv*C:\MySymbolCache*http://msdl.microsoft.com/download/symbols" 

View the Report

The csv-files above can in principle be opened with any spreadsheet application, nonetheless, they are typically huge, and not immediately helpful.

Therefore, I wrote a small application that parses one of the generated files (the one named "...CallerCalleeSummary.csv") and presents it as a tree view, ordered by total percentage. Starting from this view, it should be easy to find the cause for performance problems. For my part, I could uncover several bottlenecks in C++-legacy code, which I would never have found, just by looking at the code.

Using the Code

Attached is also a small C++-application for which I generated a profile. The generated .vsp and .csv-files are contained in the "Release"-subfolder. Opening "my_sampled_data_CallerCalleeSummary.csv" with ProfileResultViewer should resemble the screenshot I have given above.

Points of Interest

  • The profile report will show statistics only for functions, not for individual lines.
  • Functions that are inlined will not be contained in the profile, since they also never appear in the native call stack.
  • The presented treeview is not strictly hierarchic: If A calls B, then in the presented view B might seem to require more time than A. This appears, because total percentages are shown, i.e., it is not shown how much time B took, when it was called from A, but how much time B took in total. I am not sure if the csv-files contain enough information to recover the more detailed figures.

Of course, some of the steps above could be easily integrated into some GUI-Application, but for the moment the procedure I described above is sufficiently simple for me.

History

  • 11.01.2011: Initial version
  • 11.07.2012: Improved speed of ProfileResultViewer for large CSV-files (thanks to SteelixB for giving me the hint)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

hofingerandi
Software Developer
Austria Austria
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionGreat!memberCOFF3 Apr '13 - 16:32 
I found this tool very useful!
GeneralYou've done a great job!memberecquadrat6 Feb '13 - 0:58 
Searching for an easy way to get an overview where the most time is spent in my application I found your article. Your approach to the problem is a great idea and by the way it helps to save a lot of money.
 
For profiling an application only a few times for a rough overview it's a real alternative against buying an expensive tool.
 
Thank you very much!
QuestionA follow-up articlememberMattias Högström17 Jan '13 - 7:12 
Excellent article. Thanks for showing us.
 
I made my own variant with some additions.
I gave you credits in the article and linked to this article.
 
* Analysis of root nodes. Identifies threads.
* Drilldown and percentage recalculation.
* Supports both vs2010 and vs2012
* Supports also trace summaries
* Parses both comma and dot separated decimals
* Made in F#
 

Analyzing profiling data from vsperfcmd[^]
 
Have you looked at the data summary file from the vs2012 tools?
It contains a lot more interesting info than vs2010.
One can do more advanced analysis than before.
GeneralMy vote of 5memberMattias Högström29 Nov '12 - 23:24 
This is excellent. I was googling for profilers, and found this alternative. Great!! and so much cheaper.
GeneralMy vote of 5memberMichael Haephrati מיכאל האפרתי21 Oct '12 - 8:23 
Great and useful information. Thanks!
GeneralMy vote of 5memberSergey Podobry14 Oct '12 - 22:35 
Thanks for the useful info! Keep going!
QuestionAlternativemembergeoyar16 Jul '12 - 8:14 
Thank you for an interesting tip. My vote is 5.
By a way, there is an alternative, also free: CodeAnalyst from AMD. It automatically integrates with Visual Studio. I am using it on my machine (AMD Athlon), but it seems it can work with other MCU too.
geoyar

GeneralMy vote of 5memberMihai MOGA14 Jul '12 - 20:18 
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
SuggestionWorking with huge CSV filesmemberSteelixB11 Jun '12 - 1:11 
In a program I was profiling, I generated a huge CSV file. Opening this file basically causes the profile result viewer to process the tree view forever and become unresponsive. This has a knock on effect of causing all other running processes to run sluggish.
 
The solution is to only fill each node before it is expanded - Fill all top level nodes with a dummy node, and handle the BeforeExpand event on the tree view. In the event handler, remove the dummy node and fill with actual nodes.
 
The difference in time to display the tree is from 10 minutes to 5 seconds.
AnswerRe: Working with huge CSV filesmemberhofingerandi11 Jul '12 - 2:09 
Thanks for the hint!
 
I finally found the time to implement your suggestion and updated the article. The viewer really is much faster now!
SuggestionCall tre XSLmemberTrigve10 Jun '12 - 23:08 
I'm pasting here some xsl template for call trace flat->hierarchical processing. Maybe someone find it useful Smile | :)
 
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
                                        xmlns:fn="http://www.w3.org/2005/xpath-functions"
                                        xmlns:xs="http://www.w3.org/2001/XMLSchema">
     <xsl:output method="xml" encoding="utf-8" version = "1.0" indent="yes" />
     <xsl:template match="/PerformanceReport/CallTreeSummary">
          <xsl:element name= "output">
               <xsl:call-template name="process-level">
                    <xsl:with-param name="population" select="CallTree"/>
                    <xsl:with-param name="level" select="1"/>
               </xsl:call-template>
          </xsl:element>
     </xsl:template>
 
     <xsl:template name="process-level">
          <xsl:param name="population" required="yes" as="element()*"/>
          <xsl:param name="level" required="yes" as="xs:integer"/>
          <xsl:for-each-group select="$population" group-starting-with="*[xs:integer(@Level) eq $level]">
               <xsl:choose>
                    <xsl:when test="count(current-group()) = 1">
                         <xsl:copy>
                              <xsl:copy-of select ="@Level" />
                              <xsl:copy-of select ="@FunctionName" />
                              <xsl:copy-of select ="@NumCalls" />
                              <xsl:copy-of select ="@ExclusiveApplicationTimePercent" />
                              <xsl:copy-of select ="@InclusiveApplicationTimePercent" />
                              <xsl:copy-of select ="@ExclusiveElapsedTimePercent" />
                              <xsl:copy-of select ="@InclusiveElapsedTimePercent" />
                              <xsl:copy-of select ="@ModulePath" />
                         </xsl:copy>
                    </xsl:when>
                    <xsl:otherwise>
                         <xsl:copy>
                              <xsl:copy-of select ="@Level" />
                              <xsl:copy-of select ="@FunctionName" />
                              <xsl:copy-of select ="@NumCalls" />
                              <xsl:copy-of select ="@ExclusiveApplicationTimePercent" />
                              <xsl:copy-of select ="@InclusiveApplicationTimePercent" />
                              <xsl:copy-of select ="@ExclusiveElapsedTimePercent" />
                              <xsl:copy-of select ="@InclusiveElapsedTimePercent" />
                              <xsl:copy-of select ="@ModulePath" />
                              <xsl:call-template name="process-level">
                                   <xsl:with-param name="population" select="current-group()[position() != 1]"/>
                                   <xsl:with-param name="level" select="$level + 1"/>
                              </xsl:call-template>
                         </xsl:copy>
                    </xsl:otherwise>
               </xsl:choose>
          </xsl:for-each-group>
     </xsl:template>
</xsl:stylesheet>
 
Edit: Better xslt
---
 
Wait till u canSmile | :)
GeneralMy vote of 5memberMember 766644014 Mar '12 - 6:00 
I just placed everything into simple batch file and I just have to double click one file and open the results with the project provided. Easy, simple and very powerful method. 5 stars.
QuestionFiltering by pattern in file name using wildcardmemberSergey Chepurin8 Feb '12 - 23:28 
In the sample C# application if you want to filter files not only by extension but also by certain part of file name use also FileName property. Lets say you want to open only certain .csv file in directory with CallerCalleeSummary pattern in the name (profiling output files):
dlg.FileName = "*CallerCalleeSummary*";                                                                                                        dlg.Filter = "CSV and TXT (*.csv;*.txt)|*.csv;*.txt";

AnswerRe: Filtering by pattern in file name using wildcardmemberhofingerandi10 Feb '12 - 8:44 
There are certainly parts of the code that would require improvement; unfortunately this article has already been edited, so it is not so easy any more for me, to change something...
 
For me, the part that would most importantly need revision is the population of the treeview. I am sure this can be made faster by using
BeginUpdate/EndUpdate
instead of
SuspendLayout/ResumeLayout
or by reworking the code to perform a bulk update.
 
If there is interest, I can maybe transfer the code to some other platform to allow for participation.
GeneralRe: Filtering by pattern in file name using wildcardmemberSergey Chepurin10 Feb '12 - 22:12 
Don't get me wrong, you wrote a very good article and code. I just added this small improvement for anyone who uses your application analyzing profiling data (so you don't have to think every time which file to open). Thank you, once more, the articles like yours make Codeproject.com even better.
QuestionVisual Studio 2010 Performance Tools [modified]memberSergey Chepurin6 Feb '12 - 22:55 
By the way, Performance Tools for VC ++ 2010 are here Visual Studio 2010 Performance Tools with Service Pack 1[^]

modified 9 Feb '12 - 10:03.

QuestionError processing file: Requested value 'memberDave Calkins4 Nov '11 - 6:33 
I'm trying to use this on a 64-bit app built with VS2010 on Win7. I've tried both the VS2008 and VS2010 standalone performance tools. In either case, I use /launch to run my app under the profiler and then use /shutdown and exit the app. The .vsp file is created. When I use ProfileResultViewer.exe and pick the .vsp file I get this error, "Error processing file: Requested value '"
AnswerRe: Error processing file: Requested value 'memberDave Calkins4 Nov '11 - 6:37 
looks like Reader.ReadLines(dlg.FileName) is throwing an exception, "Requested value '".
AnswerRe: Error processing file: Requested value 'memberhofingerandi4 Nov '11 - 10:11 
If I understood correctly, you are opening the wrong file:
As stated in the article, you must convert the .vsp to a .csv-file and open the correct .csv-file afterwards (the one named "...CallerCalleeSummary.csv").
 
But of course, I should implement a better error message Wink | ;)
GeneralRe: Error processing file: Requested value 'memberDave Calkins5 Nov '11 - 2:52 
oops! my bad Smile | :) I suppose I was too eager to get to the results! Sorry!
GeneralRe: Error processing file: Requested value 'memberDave Calkins7 Nov '11 - 5:54 
I ran vsperfreport which generated the .csv files. When I try and open the callercallee file, the viewer app seems to hang. It runs but never shows any results and is non-responsive.
BugI met the error when doing the step %pt%\vsperfcmd /start:sample /output:my_sampled_data.vsp [modified]memberMember 62414649 Oct '11 - 21:57 
Hi,
 
I met the error when doing the step %pt%\vsperfcmd /start:sample /output:my_sampled_data.vsp
Error VSP1398 : The monitor was unable to start the VS performance driver. This
driver has been blocked from loading Consider using the /Admin:Driver,Start and
/Admin:Security options of VSPerfCmd from an elevated environment. Profiling c
annot continue.
Error VSP1341 : Could not start logging engine. Shutting down.
Error VSP1394 : Could not start profile monitor
(My environment: Windows 7 SP1, VS2008 SP1)
Could you please help me?
 
Thanks,
Ha

modified 10 Oct '11 - 4:07.

AnswerRe: I met the error when doing the step %pt%\vsperfcmd /start:sample /output:my_sampled_data.vspmemberhofingerandi10 Oct '11 - 1:27 
This seems to be the same problem as answered below: Has anyone luck with this on Window7?[^]
 
Either start the profiler in an elevated command prompt or set permissions for profiling[^].
GeneralGood articlememberCIDev14 Feb '11 - 3:10 
Nice little technique.
Just because the code works, it doesn't mean that it is good code.

GeneralDownloading now ...memberJerry Evans13 Jan '11 - 8:23 
Wonderful resource. Thanks. Gets my 5. I'll report back on W7x64.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 11 Jul 2012
Article Copyright 2011 by hofingerandi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid