MethodImplOptions.AggressiveInlining

The JIT compiler logically determines which methods to inline. But sometimes we know better than it does. With AggressiveInlining, we give the compiler a hint. We tell it that the method should be inlined. Actually, the only hint we give the compiler is to ignore the size restriction on the method or the property you want to inline. Using this attribute does not guarantee that the method will be inlined. There are 1000 and 1 reasons why it cannot be (being virtual for one thing)

Example

This example benchmarks a method with no attribute, and with AggressiveInlining. The method body contains several lines of useless code. This makes the method large in bytes, so the JIT compiler may decide not to inline it.

And: We apply the MethodImplOptions.AggressiveInlining option to Method2. It is an enum.

using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
 
class Program
{
    const int _max = 10000000;
    static void Main()
    {
        // ... Compile the methods
        Method1();
        Method2();
        int sum = 0;
 
        var s1 = Stopwatch.StartNew();
        for (int i = 0; i < _max; i++)
        {
            sum += Method1();
        }
        s1.Stop();
        var s2 = Stopwatch.StartNew();
        for (int i = 0; i < _max; i++)
        {
          sum += Method2();
        }
        s2.Stop();
        
        Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns"));
    
        Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns"));
        Console.Read();
    }
 
    static int Method1()
    {
        // ... No inlining suggestion
        return "one".Length + "two".Length + "three".Length +
            "four".Length + "five".Length +   "six".Length +
            "seven".Length + "eight".Length + "nine".Length +
            "ten".Length;
    }
 
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static int Method2()
    {
        // ... Aggressive inlining
        return "one".Length + "two".Length + "three".Length +
            "four".Length + "five".Length + "six".Length +
            "seven".Length + "eight".Length + "nine".Length +
            "ten".Length;
    }
}

Output

7.34 ns No options
0.32 ns MethodImplOptions.AggressiveInlining

We see that with no options, the method calls required seven nanoseconds each. But with inlining specified (with AggressiveInlining), the calls required less than one nanosecond each.

Tip 1: Consider for a moment all the things you could do with those seven nanoseconds.

Tip 2: If you are scheduling your life based on nanoseconds, please consider reducing your coffee intake.

Performance Drawbacks of Destructors (Finalizers) in .NET

Every decent .NET developer understands why we need destructors and how they work. In short once you create a method ‘~[ClassName]’ that particular object is placed into finalization queue. Normally those classes that use destructors do also implement the IDisposable interface, and the destructor is there only as a safety net – i.e. in case the developer forgets to call implicitly or explicitly ‘Dispose()’.

However, even though it is clearly stated, not every developer realizes the implications of having to put that object into finalization queue. The popular trail of thought is that if ‘GC.SuppressFinalize()’ there would be no performance penalty, at the end of the day we are removing the object from the finalisation queue and the GC has less work to do.
What many do not realise is that there is only one finalisation queue and if working with multiple threads .NET would have to synchronise the access to that queue somehow. So even when creating objects with destructors from multiple threads, inadvertently we create contention to the finalisation queue and even the creation of such objects could potentially be slower.

Low Latency Applications in C#

Introduction

For years, C# has been dismissed as the language of choice for low latency applications. I have been working in the financial industry for over 15 years and very often I heard that any low latency software such as HFT (High-Frequency Trading) had to be implemented in native languages such as C++.

Low Latency

I think the primary requirement for low latency is not just the performance but consistency and predictability of execution time. This is exactly why the “unpredictable” behaviour of GC puts developers off from using C# (and other .NET languages) in low latency scenarios. However, I have recently started to notice several positions being advertised where “Low Latency” featured together with one of the .NET languages. I have also worked as Trade Desk Algo Developer at Credit-Suisse developing various trading automation employing C# and .NET as my “weapons” of choice without any problems.
The language itself is not the only criteria for low latency applications. Very rarely (or never) low latency applications live in isolation. In the world of finance, you need to connect to various third-party services. Such as market data and order execution providers, and that is where the network connectivity and its performance also plays a significant role.
Another aspect of managed languages is the perceived overhead of various checks. In this article, I would try to examine to what degree these affect the performance.

Disadvantages

Garbage Collector

The standard argument is that CLR (Common Language Runtime) employs a non-deterministic way of cleaning up unused memory – Garbage Collection (GC). “Nondeterministic” in this context means that the developer has no direct control over when the memory is freed. It is up to the runtime (CLR) to decide when is the most appropriate time to perform the garbage collection, based on available memory and the application behaviour. Although the developer can initiate GC by calling the “Collect()” method, this practice is commonly discouraged.
The CLR GC employs a tracing method of identifying and freeing up memory, whereby the algorithm traverses a graph of objects allocated on the heap and identifies the ones that are not then used. The CLR has to pause some threads during GC to be able to identify, free up and compact memory. Because the memory is compacted (aka defragmented), all the live references have also got to be updated.
All of that takes time and more importantly when the GC will start, causing a small pause in the execution of the application. This behaviour deemed unacceptable for low latency applications.

Just In Time Compilation

Just In Time compilation (JIT) is an approach to compilation, where each method is compiled on demand. This leads to a slower start up times and occasional pauses, when a method is accessed for the first time. However, this is only applicable to “cold starts” and is easily solved by running NGen utility against the assemblies, which generates native code for the same assembly. There is also another way – manually “touching” the methods at the beginning of the program – forcing JIT to do its job once and for all, rather than on demand basis. But this is more like a hack.

Advantages

Garbage Collector

Despite its perceived disadvantages, GC eliminates a lot of potential bugs and enhances the productivity of a developer, who doesn’t have to be concerned about memory deallocation. This significantly reduces the possibility of memory related bugs and means that the software could be delivered much faster.
Because of the work being done by GC, memory allocations are extremely fast. One of the last stages of GC is memory compaction. Since the memory is compacted (there are few exceptions that leave “holes” in memory because of pinned object and Large Object Heap) allocation of new objects is extremely fast. All the CLR has to do it to advance the “next object pointer”. Whereas in C/C++ the memory would have to be scanned, to find an unused block for the new object.
Even though GC causes small pauses, they are usually unnoticeable. The GC team is working hard to make sure GC causes as little interruption to the program execution as possible. They have also added additional flavours of GC, giving the developer the option of specifying which behaviour they want depending on the application type. In addition more recent changes to the .NET Framework and C#, such as return by reference which opens the possibility of using large structs rather than reference types to save on a number of allocations. Before such practice would most probably backfire, as copying large structs is relatively expensive.

GC Flavours

Source: http://www.lybecker.com/blog/2007/04/03/garbage-collection-flavors/

Concurrent Workstation Garbage Collection

This mode is optimised for interactivity by frequent short garbage collects. Each collect is split up into smaller pieces and is interleaved with the managed applications threads. This improves responsiveness at the cost of CPU cycles. This is ideal for interactive desktop applications where a freezing application is an annoyance for the users and ideal CPU time is abundant when waiting for user input. Concurrent workstation mode improves the usability with perceived performance.
Note that interactive GC only applies for Gen2 (full heap) collects because Gen0 and Gen1 collects are in nature very fast.

Non-concurrent Workstation Garbage Collection

Non-concurrent workstation mode works by suspending managed application threads when a GC is initiated. It provides better throughput but might appear as application hiccups where everything freezes for the users.

Server Garbage Collection

In server mode, a managed heap and a dedicated garbage collector thread are created for each CPU. This means the each CPU allocates memory in its own heap, therefore, results in lock-free allocation. When a collect is initiated all the managed application threads are suspended and all the GC threads collect in parallel.
Another thing to note is that the size of the managed heap segments is larger in server mode than workstation mode. A segment is a unit of which the memory is allocated on the managed heap.

It is possible to choose the type of GC for a managed application in the configuration file. Under the element add one of the below three settings and depending on the number of CPU, the garbage collector will run in the configured mode. Garbage Collection type settings
Running a standalone managed application the GC mode is by default concurrent workstation. Managed application hosts like ASP.Net and SQLCLR run with Server GC by default

JIT

.NET Assemblies contain IL code which is not yet executable and requires one last step – compilation to native instructions by JIT. Since it’s done on a machine where the code is going to run and not on a build server, JIT compiler has all the information about the hardware, primarily the CPU, to tailor the generated native instructions to the particular CPU architecture and its features, such as extensions, additional registers etc. Theoretically, this is ought to yield faster code. I haven’t seen any benchmarks, but my guess would be that the difference is very marginal.

Less Control over Optimisations

Whereas in C++ you can embed assembler code, you cannot do so in C#. Personally, I don’t see the reason why assembler should be used in 2015 unless of course you’re writing something really low level.
Other features, like inlining, although present in a form of an attribute and a flag in C# – [MethodImpl(MethodImplOptions.AggressiveInlining)] it merely acts as a hint to the compiler. Aggressive inlining option tells it not to include the size of the method into the list of criteria when deciding whether to inline the method or not. Even so, there is no guarantee. CLR already does an excellent job of inlining methods or properties automatically where necessary. AggressiveInlining option should be used in very rare circumstances, forcing to inline large methods could lead to the less efficient use of CPU cache.

Automatic Checks

The beauty of managed code is that, together with the memory management, many checks have been introduced to eliminate the most common bugs.
One example is array boundary checks. The pro native language camp will say those checks significant performance. This is not exactly the case, while the checks are made, and this is an essential feature of CLR, they are not necessarily always performed at runtime. CLR will use static analysis to eliminate checks. Even if a check has to be performed on an array, the JIT team used a nice trick to reduce the number of instructions. Here is an example (x86):

cmp     EDX, dword ptr [ECX+4]         
jae     SHORT G_M60672_IG03            // unsigned comparison

EDX contains the array index, and [ECX+4] the length of the array. “jae” instruction performs the comparison that index < length. The trick here is that it seems that the code doesn’t check for negative index. JIT guys employ unsigned arithmetic properties and use “jae” instruction which performs an unsigned check. So, if EDX is, negative, casting it to an unsigned representation will yield the value of 2^31, thus causing it to fail validation.

Does it matter?

There is quite a long list of features in native languages that are not available in C#. The questions are whether it makes a significant difference.
I would argue that the productivity and the speed at which the software can be developed plays a more important role, especially in these days when it cheaper to throw in more powerful hardware rather than spending more time on writing, optimising and testing the code.
Low Latency is not only dependent on the language the application and the framework are written in, but on other factors such as the performance of the network. For instance, you might want to disable Nagle’s algorithms, which optimises the traffic over TCP at the expense of latency. This can easily be done in C# by setting Socket.NoDelay option to true
Equipped with decent hardware and sufficient memory GC pauses would be imperceptible and there are more components in the chain that could lead to latency than the code itself.

Automatic Non-Deterministic Finalisation

The automatic mechanism cannot be deterministic, because it must rely on on the GC to discover whether the object is referenced or not. At times this behaviour is a show stopper. because temporary “resource leaks” or holding a shared resource locked for slightly longer than necessary might be unacceptable in an application. At others, it’s perfectly acceptable. I will focus on the scenarios where it is.

Any type can override protected Finilize method defined by System.Object to indicate that it required automatic finalisation. However the C# syntax for requesting automatic finalisation on a class A is to implement method ~A(). This method is called finiliser a must be invoked when the object is destroyed.

Incidentally any type can have a finalizer, even the value types. However the finalizer on the value type object will never be invoked.

Continue reading “Automatic Non-Deterministic Finalisation”

Reference types vs value types

All C# types fall into the following categories:

  • Value types
  • Reference types
  • Generic type parameters
  • Pointer types

Value types comprise most built-in types (specifically, all numeric types, the char type, and the bool type) as well as custom struct and enum types.

Reference types comprise all class, array, delegate, and interface types. (This includes the predefined string type.) The fundamental difference between value types and reference types is how they are handled in memory.

Continue reading “Reference types vs value types”

The Weird and Wonderful World of Garbage Collection

Fundamentals

Each process has its separate address space. The CLR allocates a segment in memory to store and manage objects. This address space is called managed memory heap as opposed to a native heap. All threads in the process allocate objects on the managed object heap

How Garbage Collection Works

The garbage collection is an automatic process of reclaiming memory from the objects that are no longer in use. It provides several benefits:
· Enables you to develop without having to explicitly fee memory, using ‘delete’ and thus eliminates the potential bugs associated with this manual process whereby a developer deletes and objects still in use or forgets to do so leading to memory leaks

· Significantly improves the allocation performance. In order to allocate an object in CLR world, all the framework has to do is to advance the next object pointer, relying on the fact that the memory is compacted

The garbage collection occurs when the system becomes low on memory; the size of the managed heap surpasses the acceptable threshold or ‘GC.Collect()‘ function is called, triggering the collection.

Generations

The managed heap is further segregated into a large object heap LOH and a small object heap. The small object heap is split into 3 generations: Gen0, Gen1 and Gen2 however this depended on the platform, for example if you’re developing using Xamarin for Android you only have 2 generations.

The generations dictate how often the garbage collection is performed.

Generation 0

This is the youngest generation almost all objects are initially allocated in this generation, unless they are larger than 85,000 bytes in which case they are allocated in LOH.

Most objects are reclaimed by garbage collection in generation 0 however the ones that do survive move on to generation 1.

Generation 1

This generation is a buffer between short lived objects and long-lived objects

Generation 2

This generation contains long lived objects. GC tends to collect objects in this area of memory space quite infrequently.

What Happens During Garbage Collection

A garbage collection has the following steps:
· Marking phase – traverses the graph of all objects and marks them as alive. Each object (‘class’) has a special flag that enables this. Structs in turn do not have this field since they don’t live on the heap

· Relocation of the references to the objects that will be compacted. It should be noted that the GC works around the pinned objects. Pinned objects are usually the ones following by the fixed(…) statement. This pins the object in memory thus allowing to pass the pointer to that object to unmanaged code and at the same time guarantee that the pointer wouldn’t change. This is critical to the correct operation, it also hinders the performance of the garbage collector, hence the use of pinned objects should be minimal.

· Compacting phase that reclaims the space occupied by the dead objects. It moves all objects to the beginning of the memory segment and makes next object pointer, point to the end of the segment.

Originally the LOH was never compacted, which lead to fragmentation and excessive memory usage; however since version of 4.5.1 CLR provides the ability to defragment the LOH by setting ‘GCSettings.LargeObjectHeapCompactionMode

The garbage collector uses the following information to determine whether objects are live:
· Stack roots. Stack variables provided by the just-in-time (JIT) compiler and stack walker.

· Garbage collection handles. Handles that point to managed objects and that can be allocated by user code or by the common language runtime.

· Static data. Static objects in application domains that could be referencing other objects. Each application domain keeps track of its static objects.

Finilizers and Managing Unmanaged Resources

If your object uses any of the unmanaged resources it has to provide the ability to free them. The common pattern is to use ‘IDisposable’ interface to deterministically dispose of unmanaged resources. In case the ‘Dispose’ method isn’t used, the developer should provide a backup, in the form of a Finilizer. The finalizer should only be called if the client code never called the ‘Dispose()’ method. So you logic should handle the case that if the dispose method was called the finaliser never executes. You can achieve that by calling ‘GC.SuppressFinalize(this)’. If you don’t the object will be in freechable queue, then in finalisation queue and even though your object is in Gen0 it will be cleaned up after several garbage collections. ‘GC.SuppressFinalize(this)’ removed the object from the freechable queue eliminating almost all performance drawbacks of having a finilizer. Why almost? Well there is a small catch. When an object has Finilizer it adds itself into freechable queue. If the objects are allocated from different threads and add themselves to freechable queue, that creates contention, theoretically slowing the allocation down due to synchronisation.

To be continued…

Blog at WordPress.com.

Up ↑