SPEAKER: Hello and welcome to the collecting results by
threads video.
It will demonstrate how AQtime Pro saved profiling results
for multi-threaded applications, and why this is
helpful for result analysis.
AQtime Pro supports profiling multi-threaded applications,
and collects profiling applications by threads so
that you can see what code was executed by individual threads
during the application's run.
So let me demonstrate how AQtime Pro saves profiling
results for multi-threaded applications.
In our example today, I'm going to use this removing
duplicates sample application.
This application generates two arrays of both integers and
string items, and then returns the new sorted arrays with all
the duplicate elements removed.
The removal of duplicates is handled by this function right
here, RemoveDuplicates.
This function executes two threads simultaneously.
In one of the threads, the function removes duplicates
from the array of integers.
In the other thread, it removes duplicates from the
array of strings.
So to estimate the actual performance of the
RemoveDuplicates function, we need to know the execution
time in each thread, and AQtime Pro
allows us to do this.
So the first thing we want to do is switch back to AQtime,
and we want to create a new project for our application.
So to do this, I'm going to click the Create a New Project
from Module button right here.
And that's going to bring up this open file dialog where I
can browse out to where my executable lives.
So here's my application right here, Removing Duplicates,
I'll click open.
And now AQtime's going to create a profiling project for
our application.
Now once we've done that, I'm going to choose the
Performance Profiler from the list of
profilers that are available.
And then I'll press Run to start the profiling process.
This invokes the run settings dialog box.
This dialog allows you to configure a number of settings
that have an effect on the profiler's functioning.
So let me focus your attention here on this
thread model option.
This option specifies what thread model AQtime will use
to gather profiling results.
If you select Win32 threads, the profiler will group
results by operating system threads.
If you choose CLR threads, the profiler will gather results
by managed threads.
Now remember that operating system threads differ from
manage threads that may exist in .NET applications.
Managed threats are controlled by the CLR, so it can create
one or several operating system threads to manage a
single managed thread.
And then the final option, COM threads, means that AQtime
should analyze logical threads when a COM server works with
several COM clients simultaneously.
In today's run, we're going to use the Win32 threads model.
So I'm going to select that, and press Run.
OK, AQtime is now launching the profiled application.
And inside the application, I'm going to
generate some arrays.
So we're going to click the Generate Arrays button.
And this will cause the application to create two
arrays of 10,000 values each.
Then I'm going to click the Remove Duplicates button.
And now the application is going to remove duplicated
elements from the arrays, and display the resulted arrays in
sorted order.
So now I'm going to close the application down.
And AQtime will generate our profiling results and display
them on screen.
As you can see, AQtime organizes results into three
categories.
Routines, source files, and modules.
Source files and modules let you view summary profiling
results for each source file and module in your
application.
The routines category contains results for every single
routine that was included in the profiling run.
Within the categories, the results
are grouped by threads.
Here, we can see that our application
executed on three threads.
There's the main thread, the processing int array thread,
and the processing string array thread.
Now you may be wondering how AQtime recognizes
which thread is which.
Well, by default, AQtime is going to refer to threads
based on their thread identifiers.
And those identifiers are integers that the operating
system assigns to a thread when it's created.
That identifier isn't very descriptive, and it may be
difficult to find the correspondence between an
integer based identifier and the
application's actual threads.
So it's easier to analyze your profiling results if your
threads have descriptive names.
Now, you can assign descriptive names to threads
using AQtime's SDK.
And we actually have a video about that up on our website.
It's called Extending AQtime's Functionality
Using the AQtime SDK.
You can check that out.
But if you've got an application that's written in
the .NET Framework, specifically versions two or
later, you can also assign names to threads using the
thread.name property.
And then those names that you assign will be used instead of
the default integer based identifiers in
the profiling results.
So in our application--
I'm just going to jump back into Studio here--
you can see I've assigned names to the threads directly.
So, processing int array thread, and processing string
array thread.
Go back into AQtime.
and those are the actual identifiers that were used in
the profiling results.
So now by selecting a thread in the explorer panel, you can
see what functions were called by that thread during the
application run here on the report panel.
Now the results also contain this all threads group.
This contains cumulative results for all the threads.
So let's look at the results of this group.
So I'm going to double click on all threads here.
And now in the report pane, I can see the total results for
all the profiled functions.
So I'm just going to sort on time with children here,
because I want to find the slowest performing function in
my application.
And according to my results here, this RemoveDuplicates
guy that I've got highlighted is one of the slowest
performing routines that we profiled.
Now as you recall, our application is actually
calling this function in two threads.
And the hit count column that you see right here is showing
us that this function was actually executed twice.
The time with children column right here displays the total
time spent on calls to that routine in several threads,
and that includes calls to child routines as well.
But it's difficult to estimate the performance of this
function just based on this value, because the function's
execution time can differ significantly
in different threads.
And so in this case, what we need to do is analyze how fast
the function actually performed in each thread.
OK, so if we look at the main thread here and look through
the routines, we notice that the RemoveDuplicates, there's
no results for this guy right here.
It wasn't called.
So we know that this thread is not calling RemoveDuplicates.
So now we're going to go over the
processing int array thread.
And we can see that processing int array called that method,
and it took about 50 milliseconds in order for that
to execute.
Now if we come down here to the call tree panel, we can
see that the RemoveDuplicates function that we called here
called the RemoveDuplicatedIntegers
function as a child routine.
OK, so that makes sense because that's actually
removing duplicates from the array of integer values.
If we look up here at the processing string array, we
can see that RemoveDuplicates took 633.58 milliseconds.
So much, much longer.
And in this case, we see that RemoveDuplicates is calling
RemoveDuplicatedStrings.
So, in looking at this, we can see that removing to
duplicates in the integer array is much faster than
removing duplicates from the string array.
Now this could depend on the data type.
Operations on strings typically take more time than
operations on integers, or it could depend on how those
functions, the RemoveDuplicatedStrings and
RemoveDuplicatedIntegers functions, are actually being
implemented.
So let's look at the source code of the
RemoveDuplicatedString function.
So to do this, I'm going to select it here
in the reports panel.
And then I'm going to switch over to the editor panel.
And now I can actually look at the code that
makes up that function.
So in looking at this function, we can see that two
list objects are used.
Source list is our initial array, and final list is a
list that's only going to contain unique elements of
that initial array.
So after we've removed duplicates, only the unique
values will be what's left.
So, before adding an element to the final list, we use this
contains method to check to see that element's
already in the list.
This method compares a current value with each and every
other item in the list, and if it doesn't find that item,
then it adds it to the list.
So now let's take a look at how the
RemoveDuplicatedIntegers function is implemented.
So to do that, we'll come back up here and we'll look at
RemoveDuplicatedIntegers code.
And again, like the RemoveDuplicatedStrings, this
is also using two list objects.
However, in addition to that, it's also using this
dictionary.
And for each value in the initial array, the function is
calling this dictionary contains key method to check
to see if the value has already been added to the
dictionary.
And then if it hasn't, it adds it to both the dictionary and
the final list.
So as you can see, this approach is similar to what we
were using for the string array, but the difference is
how it's actually implemented.
And, based on what we've seen in the profiling results, this
contains key approach works much faster than the look up
of elements that we're using in the string array.
So as a result, the RemoveDuplicateIntegers
function works much faster than the
RemoveDuplicateStrings function.
Looks like we found the source of our performance problem.
So what we're going to do now is modify the
RemoveDuplicatedStrings code so that it uses this contains
key method, and see if that improves our performance.
OK, I've already opened to be removing duplicates solution
inside of Visual Studio here and loaded up the
appropriate code file.
Now this file actually already contains two implementations
of the RemoveDuplicateStrings method.
One implementation uses the non optimized approach which
we've seen already.
And the other is going to use a quick approach which will
use the dictionary approach that we saw in the
RemoveDuplicatedIntegers function.
So in order to determine which one of these routines is going
to be used, come up here to this define statement.
And you'll notice that this statement is actually
commented out right now.
So that means that the application is going to use
the non optimal approach to removing duplicates strings.
So I'm just going to uncomment that.
That's going to enable my faster version right here, as
you can see.
And you'll notice that the old version that is slower is now
grayed out. so now I'm going to save my changes and rebuild
my application.
All right, so let's see if our application is going to work
faster or not.
Coming back in to AQtime, you can see that AQtime has
automatically detected there have been changes here.
So we're going to say, yes, we want to reload our
application.
And we're going to reprofile our app.
So I'm just going to click the Run button again.
OK, I've fast forwarded a bit.
I've already reprofiled my application and
generated some results.
So now let's check to see if our RemoveDuplicates function
has sped up.
So here we go.
We're going to come to our all threads section here, and
we'll look at the RemoveDuplicates function.
And you'll see this time around RemoveDuplicates only
took about 47 milliseconds to get called.
So that's an improvement of our previous run here.
If we go back to our previous run, you can see that
RemoveDuplicates previously took 684 milliseconds.
So we've sped that up quite a bit.
So now let's look back at the processing string array there.
We can see that the RemoveDuplicateStrings
function is now taking 5.46 milliseconds compared to the
old value of 625.48 milliseconds.
So we've set this up considerably by improving the
performance in the
RemoveDuplicateStrings function.
This concludes our video.
You've seen how easy it is to analyze multi-threaded
applications with AQtime.
Organizing results by threads lets you quickly find
bottlenecks in your application
and eliminate them.
Note that this feature is only available in AQtime Pro.
AQtime Standard does not provide this functionality.
And for additional information, please contact us
at our website, smartbear.com.
Thanks very much for watching and have a great day.