Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Improving performance using .NET Core 3.0, Span<T>, and friends

Improving performance using .NET Core 3.0, Span<T>, and friends

Slides from a talk at the Sydney Alt.Net meetup about improving the performance of existing applications using .NET Core 3.0, Span, stackalloc and more.

https://www.meetup.com/Sydney-Alt-Net/events/jxqsbqyzkbfc/

Richard Banks

July 30, 2019
Tweet

More Decks by Richard Banks

Other Decks in Technology

Transcript

  1. 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.
  2. 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)
  3. Memory<T> Has a 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/
  4. 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.
  5. String interning https://taagung.com/string-interning/ https://docs.microsoft.com/en-us/dotnet/api/system.string.intern?view=netframework-4.7.2 Compiler puts all hardcoded strings in

    an assembly into an “intern pool” and references point to them to avoid duplications. String.Intern() is for using the same concept at runtime. Warning: Strings in the intern pool are NEVER GC’ed. Great for unplanned memory leaks! Used with caution can reap large benefits in certain scenarios.
  6. 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.
  7. 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.
  8. 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…
  9. 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; }
  10. 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);
  11. 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];
  12. 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)
  13. 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)
  14. Tooling PerfView ◦ https://github.com/microsoft/perfview BenchmarkDotNet ◦ https://benchmarkdotnet.org/ ILSpy: ◦ https://github.com/icsharpcode/ILSpy

    VisualStudio 2019 Diagnostic tools (Optional) Speedscope ◦ https://www.speedscope.app/ --- For X-Plat: dotnet-counters, dotnet-trace, dotnet-dump ◦ https://github.com/dotnet/diagnostics/tree/master/documentation
  15. 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
  16. 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