Professional Documents
Culture Documents
One solution is to sit back and ask the advice of fellow .NET experts.
Developers who have had exactly the same problem at some point and
spent hours finding the right solution.
That’s what we did. We asked .NET developers everywhere for their tips
on how to make .NET perform smarter, faster, and better.
And this eBook is the result.
All that knowledge distilled into an easily digestible form so that rather
than taking hours to find the answer you need, it will take just seconds.
Thanks to the .NET community for contributing so many valuable tips.
Thanks too to Paul Glavich and Ben Emmett who edited them.
Now it’s over to you.
Enjoy the tips – and if you slave for hours over a problem we’ve not
covered and come up with a beautifully elegant solution, let us know.
We’ll save it for the next book.
Carly Meichen
dotnetteam@red-gate.com
Contents
Tips 1-6
General performance advice 5
Tips 7-17
.NET performance 10
improvements
Tips 18-26
ASP.NET 20
Tips 27-40
Database access 28
Tips 41-47
Memory usage 44
Tips 48-52
General hints 51
4
GENERAL PEFORMANCE
ADVICE
If you must measure small time
1 differences in your code, ensure you use
the StopWatch class
Giorgi Dalakishvili
@GioDalakishvili
www.aboutmycode.com
DateTime.UtcNow isn’t designed for high-precision timing and
will often have a resolution over 10ms, making it unsuitable
for measuring small periods of time. The StopWatch class is
designed for this purpose, although beware of ending up with
an entire codebase with StopWatch instrumentation.
5
In the diagram below, for example, an application has four steps.
It first reads a file from disk, does some processing on it, stores
results in a database, and finally deletes the file. Each red box
is a separate thread, and each vertical ‘swim-lane’ represents a
particular resource. The process flows from top to bottom and
uses queues as buffers between each resource-switch.
Main thread
start threads, get batch files & fill first queue,
monitor and wait for all queues to empty
Queues
Step 1
Read file
Step 2
Process
Step 2
Process
Step 3
Store Stats
Step 4
Archive
Delete
6
Use defensive coding techniques
3 such as performing null checks
wherever applicable to avoid
NullReferenceExceptions at runtime
Rob Karatzas
@ebiztutor
Exceptions can be slow and should only be used in exceptional
circumstances, rather than for general control flow. Instead of
assuming an object will not be null at runtime, utilize defensive
coding to ensure your code only acts on objects that are not
null. This will assist performance by throwing fewer exceptions,
and ensure a more robust and reliable application.
7
Prepare performance optimization
5 projects in advance
Rainer Stropek
@rstropek
www.software-architects.com
8
Before optimizing code, profile the app
6 so that you know where bottlenecks are
Giorgi Dalakishvili
@GioDalakishvili
www.aboutmycode.com
9
.NET PERFORMANCE
IMPROVEMENTS
Concurrent asynchronous I/O on
7 server applications
Tugberk Ugurlu
Redgate
@tourismgeek www.tugberkugurlu.com
[HttpGet]
public async Task<IEnumerable<Car>>
AllCarsInParallelNonBlockingAsync() {
IEnumerable<Task<IEnumerable<Car>>> allTasks =
PayloadSources.Select(uri => GetCarsAsync(uri));
IEnumerable<Car>[] allResults = await Task.
WhenAll(allTasks);
10
When concurrent requests are made, the asynchronous method
typically performs six times faster.
11
Don’t use interop (WinFormsHost) with
9 WPF when display performance is
critical
Srinivasu Achalla
www.practicaldevblog.wordpress.com
if (myobject.GetType() == typeof(bool)) { …. /* do
something */ }
-- or --
if (myobject.GetType() == typeof()(MyCustomObject)) { ….
/* do something */ }
12
You can then do the following:
if (arg.GetType() == CommonTypes.TypeOfBoolean)
{
// Do something
}
Or:
if (arg.GetType() == CommonTypes.TypeOfString)
{
// Do something
}
13
NGen your EntityFramework.dll to
12 improve startup performance
Raghavendra Rentala
@vamosraghava
14
Remember that type casting and
14 conversions incur performance
penalties
Rob Karatzas
@ebiztutor
That way, the cast is only done once with the ‘as’ parameter. If
it fails, the value is NULL. If not, you can go ahead and use it,
resulting in only one cast.
15
Spot potential issues in your code with
15 Concurrency Visualizer
Michael Sorens
www.linkedin.com/in/michaelsorens
16
This is from a recent project where a multithreaded system has
a large number of users making requests via a web interface
and expecting prompt responses back. The system allows an
administrator to hot swap pieces of the back end supposedly
without interfering with the horde of eager users. But there was
a delay of a few seconds now and then, and the source was not
clear.
17
Take advantage of spans
16 Michael Sorens
www.linkedin.com/in/michaelsorens
using (Markers.EnterSpan(description))
{
// do stuff here
}
18
Perception is king
17 Ben Emmett
Redgate
19
ASP.NET
Consider front-end performance issues
18 as well as back-end performance issues
David Berry
@DavidCBerry13
www.buildingbettersoftware.blogspot.com
20
Flush HTML responses early
19 Nik Molnar
@nikmd23
www.nikcodes.com/
21
Efficiently Streaming Large HTTP
21 Responses With HttpClient
Tugberk Ugurlu
Redgate
@tourismgeek www.tugberkugurlu.com
response.Content = null;
}
}
}
22
By calling GetAsync method directly, every single byte is loaded
into memory.
The HttpCompletionOption.ResponseHeadersRead
enumeration value reads the headers and returns the control
back rather than buffering the response. The CopyTo Async
method then streams the content rather than downloading it all
to memory.
23
Move to ASP.NET MVC
22 Jonathan Danylko
@jdanylko
www.danylkoweb.com/
24
Cache your static content by directory
24 Jonathan Danylko
@jdanylko
www.danylkoweb.com/
25
Keep an eye on your server-side code
25 Nik Molnar
@nikmd23
www.nikcodes.com/
26
Minificafion and bundling of JavaScript & CSS files can be
automatically handled by ASP.NET from v4.5 onwards. Just
make sure you set BundleTable.EnableOptimizations = true;.
27
DATABASE ACCESS
If your ORM is capable of handling
27 multi-dataset returns, use this to reduce
your database round-trips
Rob Karatzas
@ebiztutor
This is often called a ‘none-chatty’ design, where a single larger
call is preferred over multiple smaller calls. Each call involves
a degree of negotiation and overhead and the more calls you
make, the more overhead is incurred. A single larger call reduces
the overhead.
28
Confirm you are retrieving only the
29 records you need
Jonathan Danylko
@jdanylko
www.danylkoweb.com/
If you are calling the database using Entity Framework, you may
write this command:
return context.Products.ToList().FirstOrDefault();
return context.Products.FirstOrDefault();
29
Delayed execution in EF can trip you up
30 Nick Harrison
@neh123us
www.simple-talk.com/author/nick-harrison
If your Model exposes an IQueryable object, your View may
actually run a query multiple times when it tries to read the
object’s data.
It’s easy to spot these additional queries with a tool like ANTS
Performance Profiler, which shows you what queries are being
run by your application.
30
Avoid issues with string queries
32 Raghavendra Rentala
@vamosraghava
31
Don’t overlook ‘WHERE IN’ style LINQ
33 to SQL Queries
Raghavendra Rentala
@vamosraghava
32
Beware hidden cursors
34 Raghavendra Rentala
@vamosraghava
33
Use LINQ’s ‘let’ keyword to
35 tune emitted SQL
Raghavendra Rentala
@vamosraghava
from s in Scholars
where s.ID == 2764
select new
{
s.School.Address1,
s.School.Address2,
s.School.City,
s.School.State,
s.School.ZIP,
s.School.PhoneNo,
s.School.Email,
Principal_FirstName = s.School.Leader.FirstName,
Principal_LastName = s.School.Leader.LastName,
Principal_Email = s.School.Leader.Email
}
34
This generates the following SQL:
SELECT
1 AS [C1],
[Extent2].[Address1] AS [Address1],
[Extent2].[Address2] AS [Address2],
[Extent2].[City] AS [City],
[Extent2].[State] AS [State],
[Extent2].[ZIP] AS [ZIP],
[Extent2].[PhoneNo] AS [PhoneNo],
[Extent2].[Email] AS [Email],
[Join2].[FirstName] AS [FirstName],
[Join4].[LastName] AS [LastName],
[Join6].[Email] AS [Email1]
FROM [dbo].[Scholar] AS [Extent1]
LEFT OUTER JOIN [dbo].[School] AS [Extent2] ON [Extent1].
[SchoolID] = [Extent2].[ID]
LEFT OUTER JOIN (SELECT [Extent3].[ID] AS [ID1],
[Extent4].[FirstName] AS [FirstName]
FROM [dbo].[Staff] AS [Extent3]
INNER JOIN [dbo].[Person] AS [Extent4] ON [Extent3].
[ID] = [Extent4].[ID] ) AS [Join2] ON [Extent2].
[LeaderStaffID] = [Join2].[ID1]
LEFT OUTER JOIN (SELECT [Extent5].[ID] AS [ID2],
[Extent6].[LastName] AS [LastName]
FROM [dbo].[Staff] AS [Extent5]
INNER JOIN [dbo].[Person] AS [Extent6] ON [Extent5].
[ID] = [Extent6].[ID] ) AS [Join4] ON [Extent2].
[LeaderStaffID] = [Join4].[ID2]
LEFT OUTER JOIN (SELECT [Extent7].[ID] AS [ID3],
[Extent8].[Email] AS [Email]
FROM [dbo].[Staff] AS [Extent7]
INNER JOIN [dbo].[Person] AS [Extent8] ON [Extent7].
[ID] = [Extent8].[ID] ) AS [Join6] ON [Extent2].
[LeaderStaffID] = [Join6].[ID3]
WHERE 2764 = [Extent1].[ID]
35
Using the ‘let’ keyword allows us to define the navigation
as an alias:
from s in Scholars
where s.ID == 2764
let leader = s.School.Leader
select new
{
s.School.Address1,
s.School.Address2,
s.School.City,
s.School.State,
s.School.ZIP,
s.School.PhoneNo,
s.School.Email,
Principal = new {
leader.FirstName,
leader.LastName,
leader.Email
}
}
36
Looking at the query execution plan in SSMS, the first query
is roughly twice as expensive as the second, so not only is the
query cleaner, but it performs and scales better as well.
Let’s say you need to load a webserver log into SQL Server. You
would still need to load a file, read the file, parse the file, and
load the data into objects. Then you would create a DataTable
(you could also use the DataReader or an array of DataRow too):
37
DataTable table = new DataTable();
table.TableName = “LogBulkLoad”;
table.Columns.Add(“IpAddress”, typeof(string));
table.Columns.Add(“Identd”, typeof(string));
table.Columns.Add(“RemoteUser”, typeof(string));
table.Columns.Add(“LogDateTime”, typeof(System.
DateTimeOffset));
table.Columns.Add(“Method”, typeof(string));
table.Columns.Add(“Resource”, typeof(string));
table.Columns.Add(“Protocol”, typeof(string));
table.Columns.Add(“QueryString”, typeof(string));
table.Columns.Add(“StatusCode”, typeof(int));
table.Columns.Add(“Size”, typeof(long));
table.Columns.Add(“Referer”, typeof(string));
table.Columns.Add(“UserAgent”, typeof(string));
Next step would be to load the DataTable with data that you’ve
parsed:
row[“IpAddress”] = log.IpAddress;
row[“Identd”] = log.Identd;
row[“RemoteUser”] = log.RemoteUser;
row[“LogDateTime”] = log.LogDateTime;
row[“Method”] = log.Method;
row[“Resource”] = log.Resource;
row[“Protocol”] = log.Protocol;
row[“QueryString”] = log.QueryString;
row[“StatusCode”] = log.StatusCode;
row[“Size”] = log.Size;
row[“Referer”] = log.Referer;
row[“UserAgent”] = log.UserAgent;
table.Rows.Add(row);
}
38
This example also provides the column mappings from the
DataTable to the table in SQL Server. If your DataTable columns
and SQL server columns are in the same positions, then there
will be no need to provide the mapping, but in this case the SQL
Server table has an ID column and the DataTable does not need
to explicitly map them:
s.ColumnMappings.Add(“IpAddress”, “IpAddress”);
s.ColumnMappings.Add(“Identd”, “Identd”);
s.ColumnMappings.Add(“RemoteUser”, “RemoteUser”);
s.ColumnMappings.Add(“LogDateTime”, “LogDateTime”);
s.ColumnMappings.Add(“Method”, “Method”);
s.ColumnMappings.Add(“Resource”, “Resource”);
s.ColumnMappings.Add(“Protocol”, “Protocol”);
s.ColumnMappings.Add(“QueryString”, “QueryString”);
s.ColumnMappings.Add(“StatusCode”, “StatusCode”);
s.ColumnMappings.Add(“Size”, “Size”);
s.ColumnMappings.Add(“Referer”, “Referer”);
s.ColumnMappings.Add(“UserAgent”, “UserAgent”);
s.WriteToServer((DataTable)table);
}
}
39
Use AsNoTracking when retrieving data
37 for reading with Entity Framework
Andre Kraemer
@codemurai
www.andrekraemer.de/blog
40
Indexing tables is not an exact science
38 Rob Karatzas
@ebiztutor
Indexing tables requires some trial and error combined with lots
of testing to get things right. Even then, performance metrics
change over time as more and more data is added or becomes
aged.
For a deeper analysis of queries, you can also use a tool like
ANTS Performance Profiler.
41
Use caching to reduce load on the
39 database
Paul Glavich
@glav
http://weblogs.asp.net/pglavich
42
If you don’t need all the columns from
40 your table, don’t select them
Andre Kraemer
@codemurai
www.andrekraemer.de/blog
A line like this is great if you only have a few columns in your
products table or if you don’t care much about performance or
memory consumption. With this code, Entity Framework selects
all the columns of the products table, but if your product table
has 25 columns and you only need two of them, your database,
network, and PC all run slower.
Do your users a favor and retrieve only the columns you need. If,
for example, you just need the ‘Id’ and ‘Name’ fields, you could
rewrite your statement like this:
43
MEMORY USAGE
Use lists instead of arrays when the size
41 is not known in advance
Megha Maheshwari
www.linkedin.com/in/formegha
www.quora.com/megha-maheshwari
When you want to add or remove data, use lists instead of
arrays. Lists grow dynamically and don’t need to reserve more
space than is needed, whereas resizing arrays is expensive. This
is particularly useful when you know what the pattern of growth
is going to be, and what your future pattern of access will be.
44
Learn about the .NET Garbage Collector
43 (GC) and when it can cause pauses that
slow your application down
Matt Warren
@matthewwarren
www.mattwarren.org
45
Fix memory leaks to avoid performance
44 issues
Cosimo Nobile
@cosimo_nobile
46
This confirmed the process was leaking unmanaged memory. It
also gave me some initial indications that there could potentially
be several causes because the graph shows some random steps in
addition to the constant gradient slope. Fixing the cause of the
constant leak resulted in the following:
The slope was clearly gone but the occasional random steps
were still present. This was harder to find but it soon became
clear what component was responsible for it because after
disabling it I obtained a flat tracing with occasional spikes:
47
Finally, having located and fixed the leak in the identified
component, the following graph provided me with the
reassurance that all issues had been found and addressed:
48
Avoid duplicate string reference issues
46 Shubhajyoti Ghosh
Shubhajyoti Ghosh
@radcorpindia
class Program
{
static void Main()
{
// A.
// String generated at runtime.
// Is not unique in string pool
string s1 = new StringBuilder().Append(“cat”).
Append(“ and dog”).ToString();
// B.
// Interned string added at runtime.
// Is unique in string pool.
string s2 = string.Intern(s1);
}
}
49
Dispose of unmanaged resources
47 Rob Karatzas
@ebiztutor
50
GENERAL HINTS
Optimize allocations
48 Bartosz Adamczewski
@badamczewski01
51
b) Off heap allocate
52
Avoid false sharing
49 Bartosz Adamczewski
@badamczewski01
HW T1 HW T1
Write X Read Y
186A0 186A1 186A2 ... 186DF Line1
53
We create four threads and assign each thread to a specific array
index and then increment the counter. This program has false
sharing because every thread will use the same cache line. In
order to eliminate false sharing, we need to assign each thread
its own cache line by offsetting the array index given to each:
54
Avoid hidden allocations
50 Bartosz Adamczewski
@badamczewski01
a) Hidden boxing
55
b) Lambdas
56
The code for FirstOrDefault looks as follows:
return null;
}
57
Avoid hidden false sharing
51 Bartosz Adamczewski
@badamczewski01
CLR T T1 T2
In our program, each thread actually uses not one but two cache
lines and the first cache line is shared between all other threads.
Since they only need to read from it, everything should be
alright.
58
The sample code shows, however, that the first thread uses this
line. There will therefore be one thread that will be writing
while others are reading. In other words, false sharing.
59
Optimize your code for cache
52 prefetching
Bartosz Adamczewski
@badamczewski01
int n = 10000;
int m = 10000;
int[,] tab = new int[n, m];
int n = 10000;
int m = 10000;
int[,] tab = new int[n, m];
60
Now this program will execute in around 700 ms, so on average
it will run at least two times faster. In terms of pure memory
access, array indexing should not make a change since virtual
memory addressing makes the memory seam linear and its
access times should be more or less the same.
Way 0 Way 1
0x1 0x186A0
0x80 0x18720
0x100 0x187A0
0x180 0x18820
0x200 0x187A0 0x188E0
0x280
0x300
64 B
0x380
61
The second example loads a cache line, modifies all data cells in
that line and then loads the next one and so on, which is much
faster.
The given example was doing writes only, but if it read from an
already populated array the performance impact would be much
greater because writes are always volatile and some cycles are
wasted writing to main memory.
62
That’s not the end of
the story
We hope you’ve founds lots of tips and tricks to boost your .NET
performance in this eBook. If you’d like more, or would like a
detailed guide on .NET performance or memory management,
take a look at the other eBooks we’ve published:
63
NET Performance Testing and Optimization
by Paul Glavich and Chris Farrell
64
You don’t just need the
right knowledge – you
need the right tools
Redgate has developed two of the most popular .NET
performance and memory profilers that help .NET
professionals save time and fix the things that matter.
65
Find memory leaks in minutes
66