2. 2www.luxoft.com
Yevhen Tatarynov
Software developer with 14 years of experience in commercial
software and database development (.NET / MS SQL / Delphi)
PhD in math, specializing in the theoretical foundations of
computer science and cybernetics
Point of professional interest:
application performance optimization and analysis
writing C# code similar in performance to C++
advanced debugging
At Luxoft DXC, I work in the Gas & Oil and Financial domains.
Most of the projects involve performing complex mathematical
calculations and processing large amounts of data.
5. 5www.luxoft.com
CONSOLE .NET APPLICATION
Read *.csv data files
Process data in multiple threads
Write results in MS Excel file
Use .NET Framework 4.6.2
Run on Windows
It works correctly
7. 7www.luxoft.com
NON-FUNCTIONAL REQUIREMENTS
We need to process 2 files > 1.5 GB , 2 files > 256MB, and 2 files >
5.5MB (the size of file will grow)
Results needed every day at a specific time
The virtual machine is shared by other data processing applications, so
we shouldn’t affect them.
9. 9www.luxoft.com
WHAT IS THE CHALLENGE?
GOALS
Smooth peak memory load
Reduce total memory allocation
Don’t break the application output
VALUES
Fewer GC runs
Less impact on other processes
Processing finished in appropriate
time
15. 15www.luxoft.com
CHOOSE METRICS
PEAK MEMORY LOAD
Unstable in multithread
Depends on available free RAM
space
GC runs
Hard to reproduce
TOTAL MEMORY USAGE
Stable in any use case
Depends only on application logic
Easy to reproduce
20. 20www.luxoft.com
BOXING
ConcurrentDictionary<TKey,TValue> classes have the same functionality
as the Hashtable class. A ConcurrentDictionary <TKey,TValue> of a specific
type (other than Object) provides better performance than a Hashtable
for value types.
This is because the elements of Hashtable are of type Object; therefore,
boxing and unboxing typically occur when you store or retrieve a value
type.
21. 21www.luxoft.com
StackOverflow
Since you only override Equals and don’t implement IEquatable<T>, the
dictionary is forced to box one of the two instances whenever it compares two
of them for equality because it's passing an instance into an Equals-method-
accepting object.
If you implement IEquatable<T>, then the dictionary can (and will) use the
version of Equals that accepts the parameter as a T, which won't require
boxing.
26. 26www.luxoft.com
#4 DO WE NEED TO CAST TO DYNAMIC?
For Struct we have no defined
operation +, so to maintain generic
MyClass<T> we need to cast to
dynamic and we make boxing
We use only double type for T
public class MyClass
{
double Add(double a,double b)
=> a + b;
}
31. 31www.luxoft.com
#6 REUSE StringBuilder
No new Allocation
Can be used for different item
lengths
Can grow in 8,000 bytes if it’s
necessary to expand the internal
buffer
Cache StringBuilder in private field
char[] item
_stringBuilder.Clear();
/*.....*/
_stringBuilder.Write(item);
return item;
39. 39www.luxoft.com
/*Read single item of csv*/
_stringBuilder.Append();
/*Copy collected chars into array*/
item = new char[n];
/*.....*/
return item;
#10 TOO MUCH Char[]?
50. 50www.luxoft.com
# 14 GetOrAdd vs. TryGetValue
public ValueClass GetValue(string name)
{
var val = null;
if (!_dict.TryGetValue(name, out val))
{
_cache.Add(name);
value = new ValueClass(name);
_dict.TryAdd(myKey, storedValue);
}
return val;
}
public ValueClass GetValue(string
name)
{
var val = new ValueClass(name);
_cache.Add(name);
return _dict.GetOrAdd(name, val);
}
52. 52www.luxoft.com
#15 GetOrAdd GLOBAL REFACTORING
public ValueClass GetValue(string name)
{
var value = null;
if (!_dict.TryGetValue(name, out
value))
{
value = new ValueClass(name);
_dict.TryAdd(myKey, storedValue);
}
return value;
}
public ValueClass GetValue(string
name)
{
var value = new ValueClass(name);
return _dict.GetOrAdd(name,
value);
}
58. 58www.luxoft.com
TOTAL MEMORY USAGE
Total memory usage
old
419,570 MB
Total memory usage
new
12.815 MB
Total memory usage
diff
-406.755 MB
Percent ~97 %
Ratio ~32.74
61. 61www.luxoft.com
SUMMARY
Keep your code clean
Avoid Boxing
Reuse / cache object (ArrayPool)
Use StringBuilder when you work with chars and strings
Avoid string operations (concatenation, ToUpper, etc.)
Use lambdas carefully
Don’t allocate memory for useless data
65. 65www.luxoft.com
LINKS
Use dotTrace Command-Line Profiler
.NET Performance Optimization & Profiling with JetBrains dotTrace
Use dotMemory Command-Line Profiler
"Pooling large arrays with ArrayPool" Adam Sitnik
Hashtable and dictionary collection types
Why GC run when using a struct as a generic dictionary
Matt Ellis. Writing Allocation Free Code in C#
Konrad Kokosa. High-performance code design patterns in C#
66. 66www.luxoft.com
Links
Maarten Balliauw. Let’s refresh our memory! Memory management in
.NET
Konrad Kokosa. Pro .NET Memory Management: For Better Code,
Performance, and Scalability
Sasha Goldshtein. Pro .NET Performance: Optimize Your C#
Applications
Ben Watson. Writing High-Performance .NET Code, 2nd Edition
Maarten Balliauw
Sasha Goldshtein
Yevhen Tatarynov GitHub