4. Reduced memory allocations
Less time spent in GC collections and less overall GC pressure
Less time allocating and deallocating objects means more CPU for you
Across the framework, lots of small improvements over many classes.
6. Span<T>
https://adamsitnik.com/Span/
Stack access only (use Memory<T> for the heap)
Can’t use it as a field in a class (since a class is on the heap) but can use it in a struct.
Can’t do async/await with it (since the compiler creates a state machine… on the heap)
8. Memory<T>
Has a .Span property that you can use to get a Span in a method
Create it from a string, array, or something implementing IOwnedMemory.
Lots of methods in .NET Core 2.1+ take Spans as arguments.
Many more do so in .NET Core 3.0 (.Net Standard 2.1)
https://apisof.net/
Base64.EncodeToUtf8(ReadOnlySpan<Byte>,Span<Byte>,Int32,Int32,Boolean)
9. System.Buffers.ArrayPool
Object pooling pattern - https://www.codeproject.com/articles/20848/c-object-pooling
In .NET Core (System.Buffers) - https://adamsitnik.com/Array-Pool/
var samePool = ArrayPool<byte>.Shared;
byte[] buffer = samePool.Rent(minLength);
try {
Use(buffer);
} finally {
samePool.Return(buffer);
}
Cheaper as soon as you need 1K of memory (or more) – and no allocations required.
12. ref locals and ref returns
ref int Max(ref int first, ref int second, ref int third) {
ref int max = ref first;
if (first < second) max = second;
if (second < third) max = third;
return ref max;
}
The method result is simply a reference to whichever value was the largest.
It has zero allocations.
13. Reduce casting and boxing
Warning: Casting to generic interfaces is sloooow!
https://www.danielcrabtree.com/blog/191/casting-to-ienumerable-t-is-two-orders-of-
magnitude-slower
Boxing operations create invisible allocations. Some boxing operations are hard to spot.
14. LINQ & Closures
class Symbol { public string Name { get; private set; } /*...*/
}
class Compiler {
private List<Symbol> symbols;
public Symbol FindMatchingSymbol(string name) {
return symbols.FirstOrDefault(s => s.Name == name);
}
}
private class Lambda1Environment {
public string capturedName;
public bool Evaluate(Symbol s) {
return s.Name == this.capturedName;
}
}
Lambda1Environment l = new Lambda1Environment
capturedName = name };
var predicate = new Func<Symbol, bool>(l.Evaluate);
Func<Symbol, bool> predicate = s => s.Name == name;
return symbols.FirstOrDefault(predicate);
Boxing operation.
FirstOrDefault() is an extension
method on IEnumerable<T>
Compiles to…
15. Alternative implementation?
Not as pretty, but no allocations.
foreach will use the List<T> iterator. No casting and no hidden lambda code.
public Symbol FindMatchingSymbol(string name)
{
foreach (Symbol s in symbols)
{
if (s.Name == name) return s;
}
return null;
}
16. MemoryMarshal (helps with Spans)
public Span<byte> FloatsToSpanOfBytes() => MemoryMarshal.Cast<float, byte>(arrayOfFloats);
----
[StructLayout(LayoutKind.Explicit)]
public struct Bid {
[FieldOffset(0)] public float Value;
[FieldOffset(4)] public long ProductId;
[FieldOffset(12)] public long UserId;
[FieldOffset(20)] public DateTime Time;
}
…
public Bid Deserialize(ReadOnlySpan<byte> serialized) => MemoryMarshal.Read<Bid>(serialized);
17. stackalloc Keyword
Allows you to directly allocate memory on the stack
Don’t overdo it and keep it for short-lived usage
Beware: It’s easy to misuse this and make things worse
Span<byte> bytes = length <= 128 ?
stackalloc byte[length] :
new byte[length];
18. Platform Instrinsics
System.Runtime.Intrinsics – let you use hardware accelerated SIMD specific to ARM, x64, etc.
https://bits.houmus.org/2018-08-18/netcoreapp3.0-instrinsics-in-real-life-pt1
For general use the platform independent Vector SIMD instructions are preferred.
(check System.Numerics.Vector.IsHardwareAccelerated)
19. Theory Time is Over
LET’S IMPROVE THE PERFORMANCE OF “SOMETHING”
23. Let’s work with some real code!
Our target library: PdfPig
Features:
* Targets .NET Standard 2.0
* Port of Apache PDFBox to C#
* Has lots of tests
(And it’s not something I’d seen before prepping this session)
25. What we’ll do
Measure current performance (using .NET Core 2.2)
Upgrade to .NET Core 3.0 prev. 7 & compare performance
Analyse performance using PerfView
Run microbenchmarks to measure specific performance areas
26. What you’ll do
Clone https://github.com/rbanks54/PdfPig
◦ use the benchmarks branch
Identify an area you want to improve
Go ahead. Try and improve it. And prove it.
Suggested developer loop:
1. Ensure all unit tests pass & baseline current performance
2. Make a change
3. Check unit tests still pass
4. Measure new performance and compare with baseline
5. Repeat from step 2 until happy