Posts about .NET

Measuring Performance Improvements in .NET Core with BenchmarkDotNet (Part 1)



A few days ago Stephen Toub published a great post at the Microsoft .NET Blog: Performance Improvements in .NET Core. He showed some significant performance changes in .NET Core 2.0 Preview 1 (compared with .NET Framework 4.7). The .NET Core uses RyuJIT for generating assembly code. When I first tried RyuJIT (e.g., CTP2, CTP5, 2014), I wasn't excited about this: the preview versions had some bugs, and it worked slowly on my applications. However, the idea of a rethought and open-source JIT-compiler was a huge step forward and investment in the future. RyuJIT had been developed very actively in recent years: not only by Microsoft but with the help of the community. I'm still not happy about the generated assembly code in some methods, but I have to admit that the RyuJIT (as a part of .NET Core) works pretty well today: it shows a good performance level not only on artificial benchmarks but also on real user code. Also, there are a lot of changes not only in dotnet/coreclr (the .NET Core runtime), but also in dotnet/corefx (the .NET Core foundational libraries). It's very nice to watch how the community helps to optimize well-used classes which have not changed for years.

Now let's talk about benchmarks. For the demonstration, Stephen wrote a set of handwritten benchmarks. A few people (in comments and on HackerNews) asked about BenchmarkDotNet regarding these samples (as a better tool for performance measurements). So, I decided to try all these benchmarks on BenchmarkDotNet.

In this post, we will discuss how can BenchmarkDotNet help in such performance investigations, which benchmarking approaches (and when) are better to use, and how can we improve these measurements.

Read more    Comments


BenchmarkDotNet v0.10.7



BenchmarkDotNet v0.10.7 has been released. In this post, I will briefly cover the following features:

  • LINQPad support
  • Filters and categories
  • Updated Setup/Cleanup attributes
  • Better Value Types support
  • Building Sources on Linux

Read more    Comments


65535 interfaces ought to be enough for anybody



It was a bright, sunny morning. There were no signs of trouble. I came to work, opened Slack, and received many messages from my coworkers about failed tests.

After a few hours of investigation, the situation became clear:

  • I'm responsible for the unit tests subsystem in Rider, and only tests from this subsystem were failing.
  • I didn't commit anything to the subsystem for a week because I worked with a local branch. Other developers also didn't touch this code.
  • The unit tests subsystem is completely independent. It's hard to imagine a situation when only the corresponded tests would fail, thousands of other tests pass, and there are no changes in the source code.
  • git blame helped to find the "bad commit": it didn't include anything suspicious, only a few additional classes in other subsystems.
  • Only tests on Linux and MacOS were red. On Windows, everything was ok.
  • Stacktraces in failed tests were completely random. We had a new stack trace in each test from different subsystems. There was no connection between these stack traces, unit tests source code, and the changes in the "bad commit." There was no clue where we should look for a problem.

So, what was special about this "bad commit"? Spoiler: after these changes, we sometimes have more than 65535 interface implementations at runtime.

Read more    Comments


A bug story about named mutex on Mono



When you write some multithreading magic on .NET, you can use a cool synchronization primitive called Mutex:

var mutex = new Mutex(false, "Global\\MyNamedMutex");

You also can make it named (and share the mutex between processes) which works perfectly on Windows:

However, today the .NET Framework is cross-platform, so this code should work on any operation system. What will happen if you use named mutex on Linux or MacOS with the help of Mono or CoreCLR? Is it possible to create some tricky bug based on this case? Of course, it does. Today I want to tell you a story about such bug in Rider which was a headache for several weeks.

Read more    Comments


InvalidDataException in Process.GetProcesses



Consider the following program:

public static void Main(string[] args)
{
    try
    {
        Process.GetProcesses();
    }
    catch (Exception e)
    {
        Console.WriteLine(e);
    }
}

It seems that all exceptions should be caught. However, sometimes, I had the following exception on Linux with dotnet cli-1.0.0-preview2:

$ dotnet run
System.IO.InvalidDataException: Found invalid data while decoding.
   at System.IO.StringParser.ParseNextChar()
   at Interop.procfs.TryParseStatFile(String statFilePath, ParsedStat& result, ReusableTextReader reusableReader)
   at System.Diagnostics.ProcessManager.CreateProcessInfo(ParsedStat procFsStat, ReusableTextReader reusableReader)
   at System.Diagnostics.ProcessManager.CreateProcessInfo(Int32 pid, ReusableTextReader reusableReader)
   at System.Diagnostics.ProcessManager.GetProcessInfos(String machineName)
   at System.Diagnostics.Process.GetProcesses(String machineName)
   at System.Diagnostics.Process.GetProcesses()
   at DotNetCoreConsoleApplication.Program.Main(String[] args) in /home/akinshin/Program.cs:line 12

How is that possible?

Read more    Comments


Why is NuGet search in Rider so fast?



I'm the guy who develops the NuGet manager in Rider. It's not ready yet, there are some bugs here and there, but it already works pretty well. The feature which I am most proud of is smart and fast search:

Today I want to share with you some technical details about how it was implemented.

Read more    Comments


NuGet2 and a DirectorySeparatorChar bug



In Rider, we care a lot about performance. I like to improve the application responsiveness and do interesting optimizations all the time. Rider is already well-optimized, and it's often hard to make significant performance improvements, so usually I do micro-optimizations which do not have a very big impact on the whole application. However, sometimes it's possible to improve the speed of a feature 100 times with just a few lines of code.

Rider is based on ReSharper, so we have a lot of cool features out of the box. One of these features is Solution-Wide Analysis which lets you constantly keep track of issues in your solution. Sometimes, solution-wide analysis takes a lot of time to run because there are many files which should be analyzed. Of course, it works super fast on small and projects.

Let's talk about a performance bug (#RIDER-3742) that we recently had.

  • Repro: Open Rider, create a new "ASP .NET MVC Application", enable solution wide-analysis.
  • Expected: The analysis should take 1 second.
  • Actual: The analysis takes 1 second on Windows and 2 minutes on Linux and MacOS.

Read more    Comments


Performance exercise: Division



In the previous post, we discussed the performance space of the minimum function which was implemented via a simple ternary operator and with the help of bit magic. Now we continue to talk about performance and bit hacks. In particular, we will divide a positive number by three:

uint Div3Simple(uint n)   => n / 3;
uint Div3BitHacks(uint n) => (uint)((n * (ulong)0xAAAAAAAB) >> 33);

As usual, it's hard to say which method is faster in advanced because the performance depends on the environment. Here are some interesting results:

Simple BitHacks
LegacyJIT-x86 ≈8.3ns ≈2.6ns
LegacyJIT-x64 ≈2.6ns ≈1.7ns
RyuJIT-x64 ≈6.9ns ≈1.5ns
Mono4.6.2-x86 ≈8.5ns ≈14.4ns
Mono4.6.2-x64 ≈8.3ns ≈2.8ns

Read more    Comments


Performance exercise: Minimum



Performance is tricky. Especially, if you are working with very fast operations. In today benchmarking exercise, we will try to measure performance of two simple methods which calculate minimum of two numbers. Sounds easy? Ok, let's do it, here are our guinea pigs for today:

int MinTernary(int x, int y)  => x < y ? x : y;
int MinBitHacks(int x, int y) => x & ((x - y) >> 31) | y & (~(x - y) >> 31);

And here are some results:

Random Const
Ternary BitHacks Ternary BitHacks
LegacyJIT-x86 ≈643µs ≈227µs ≈160µs ≈226µs
LegacyJIT-x64 ≈450µs ≈123µs ≈68µs ≈123µs
RyuJIT-x64 ≈594µs ≈241µs ≈180µs ≈241µs
Mono-x64 ≈203µs ≈283µs ≈204µs ≈282µs

What's going on here? Let's discuss it in detail.

Read more    Comments


Stopwatch under the hood



In the previous post, we discussed DateTime. This structure can be used in situations when you don't need a good level of precision. If you want to do high-precision time measurements, you need a better tool because DateTime has a small resolution and a big latency. Also, time is tricky, you can create wonderful bugs if you don't understand how it works (see Falsehoods programmers believe about time and More falsehoods programmers believe about time).

In this post, we will briefly talk about the Stopwatch class:

  • Which kind of hardware timers could be a base for Stopwatch
  • High precision timestamp API on Windows and Linux
  • Latency and Resolution of Stopwatch in different environments
  • Common pitfalls: which kind of problems could we get trying to measure small time intervals

If you are not a .NET developer, you can also find a lot of useful information in this post: mainly we will discuss low-level details of high-resolution timestamping (probably your favorite language also uses the same API). As usual, you can also find useful links for further reading.

Read more    Comments


DateTime under the hood



DateTime is a widely used .NET type. A lot of developers use it all the time, but not all of them really know how it works. In this post, I discuss DateTime.UtcNow: how it's implemented, what the latency and the resolution of DateTime on Windows and Linux, how the resolution can be changed, and how it can affect your application. This post is an overview, so you probably will not see super detailed explanations of some topics, but you will find a lot of useful links for further reading.

Read more    Comments


LegacyJIT-x86 and first method call



Today I tell you about one of my favorite benchmarks (this method doesn't return a useful value, we need it only as an example):

[Benchmark]
public string Sum()
{
    double a = 1, b = 1;
    var sw = new Stopwatch();
    for (int i = 0; i < 10001; i++)
        a = a + b;
    return string.Format("{0}{1}", a, sw.ElapsedMilliseconds);
}

An interesting fact: if you call Stopwatch.GetTimestamp() before the first call of the Sum method, you improve Sum performance several times (works only with LegacyJIT-x86).

Read more    Comments


Visual Studio and ProjectTypeGuids.cs



It's a story about how I tried to open a project in Visual Studio for a few hours. The other day, I was going to do some work. I pulled last commits from a repo, opened Visual Studio, and prepared to start coding. However, one of a project in my solution failed to open with a strange message:

error  : The operation could not be completed.

In the Solution Explorer, I had "load failed" as a project status and the following message instead of the file tree: "The project requires user input. Reload the project for more information." Hmm, ok, I reloaded the project and got a few more errors:

error  : The operation could not be completed.
error  : The operation could not be completed.

Read more    Comments


Blittable types



Challenge of the day: what will the following code display?

[StructLayout(LayoutKind.Explicit)]
public struct UInt128
{
    [FieldOffset(0)]
    public ulong Value1;
    [FieldOffset(8)]
    public ulong Value2;
}
[StructLayout(LayoutKind.Sequential)]
public struct MyStruct
{
    public UInt128 UInt128;
    public char Char;
}
class Program
{
    public static unsafe void Main()
    {
        var myStruct = new MyStruct();
        var baseAddress = (int)&myStruct;
        var uInt128Adress = (int)&myStruct.UInt128;
        Console.WriteLine(uInt128Adress - baseAddress);
        Console.WriteLine(Marshal.OffsetOf(typeof(MyStruct), "UInt128"));
    }
}

A hint: two zeros or two another same values are wrong answers in the general case. The following table shows the console output on different runtimes:

MS.NET-x86MS.NET-x64Mono
uInt128Adress - baseAddress 480
Marshal.OffsetOf(typeof(MyStruct), "UInt128")000

If you want to know why it happens, you probably should learn some useful information about blittable types.

Read more    Comments


RyuJIT RC and constant folding



Update: The below results are valid for the release version of RyuJIT.

The challenge of the day: which method is faster?

public double Sqrt13()
{
    return Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + Math.Sqrt(4) + Math.Sqrt(5) + 
           Math.Sqrt(6) + Math.Sqrt(7) + Math.Sqrt(8) + Math.Sqrt(9) + Math.Sqrt(10) + 
           Math.Sqrt(11) + Math.Sqrt(12) + Math.Sqrt(13);
}
public double Sqrt14()
{
    return Math.Sqrt(1) + Math.Sqrt(2) + Math.Sqrt(3) + Math.Sqrt(4) + Math.Sqrt(5) + 
           Math.Sqrt(6) + Math.Sqrt(7) + Math.Sqrt(8) + Math.Sqrt(9) + Math.Sqrt(10) + 
           Math.Sqrt(11) + Math.Sqrt(12) + Math.Sqrt(13) + Math.Sqrt(14);
}

I have measured the methods performance with help of BenchmarkDotNet for RyuJIT RC (a part of .NET Framework 4.6 RC) and received the following results:

// BenchmarkDotNet=v0.7.4.0
// OS=Microsoft Windows NT 6.2.9200.0
// Processor=Intel(R) Core(TM) i7-4702MQ CPU @ 2.20GHz, ProcessorCount=8
// CLR=MS.NET 4.0.30319.0, Arch=64-bit  [RyuJIT]
Common:  Type=Math_DoubleSqrtAvx  Mode=Throughput  Platform=X64  Jit=RyuJit  .NET=Current  

 Method |  AvrTime |    StdDev |         op/s |
------- |--------- |---------- |------------- |
 Sqrt13 | 55.40 ns |  0.571 ns |  18050993.06 |
 Sqrt14 |  1.43 ns | 0.0224 ns | 697125029.18 |

How so? If I add one more Math.Sqrt to the expression, the method starts work 40 times faster! Let's examine the situation..

Read more    Comments


Unrolling of small loops in different JIT versions



Challenge of the day: what will the following code display?

struct Point
{
    public int X;
    public int Y;
}
static void Print(Point p)
{
    Console.WriteLine(p.X + " " + p.Y);
}
static void Main()
{
    var p = new Point();
    for (p.X = 0; p.X < 2; p.X++)
        Print(p);
}

The right answer: it depends. There is a bug in CLR2 JIT-x86 which spoil this wonderful program. This story is about optimization that called unrolling of small loops. This is a very interesting theme, let's discuss it in detail.

Read more    Comments


RyuJIT CTP5 and loop unrolling



RyuJIT will be available soon. It is a next generation JIT-compiler for .NET-applications. Microsoft likes to tell us about the benefits of SIMD using and JIT-compilation time reducing. But what about basic code optimization which is usually applying by a compiler? Today we talk about the loop unrolling (unwinding) optimization. In general, in this type of code optimization, the code

for (int i = 0; i < 1024; i++)
    Foo(i);

transforms to

for (int i = 0; i < 1024; i += 4)
{
    Foo(i);
    Foo(i + 1);
    Foo(i + 2);
    Foo(i + 3);
}

Such approach can significantly increase performance of your code. So, what's about loop unrolling in .NET?

Read more    Comments


JIT version determining in runtime



Sometimes I want to know used JIT compiler version in my little C# experiments. It is clear that it is possible to determine the version in advance based on the environment. However, sometimes I want to know it in runtime to perform specific code for the current JIT compiler. More formally, I want to get the value from the following enum:

public enum JitVersion
{
    Mono, MsX86, MsX64, RyuJit
}

It is easy to detect Mono by existing of the Mono.Runtime class. Otherwise, we can assume that we work with Microsoft JIT implementation. It is easy to detect JIT-x86 with help of IntPtr.Size == 4. The challenge is to distinguish JIT-x64 and RyuJIT. Next, I will show how you can do it with help of the bug from my previous post.

Read more    Comments


A bug story about JIT-x64



Can you say, what will the following code display for step=1?

public void Foo(int step)
{
    for (int i = 0; i < step; i++)
    {
        bar = i + 10;
        for (int j = 0; j < 2 * step; j += step)
            Console.WriteLine(j + 10);
    }
}

If you think about specific numbers, you are wrong. The right answer: it depends. The post title suggests to us, the program can has a strange behavior for x64.

Read more    Comments


A story about JIT-x86 inlining and starg



Sometimes you can learn a lot during reading source .NET. Let's open the source code of a Decimal constructor from .NET Reference Source (mscorlib/system/decimal.cs,158):

// Constructs a Decimal from an integer value.
//
public Decimal(int value) {
    //  JIT today can't inline methods that contains "starg" opcode.
    //  For more details, see DevDiv Bugs 81184: x86 JIT CQ: Removing the inline striction of "starg".
    int value_copy = value;
    if (value_copy >= 0) {
        flags = 0;
    }
    else {
        flags = SignMask;
        value_copy = -value_copy;
    }
    lo = value_copy;
    mid = 0;
    hi = 0;
}

The comment states that JIT-x86 can't apply the inlining optimization for a method that contains the starg IL-opcode. Curious, is not it?

Read more    Comments


About UTF-8 conversions in Mono



This post is a logical continuation of the Jon Skeet's blog post “When is a string not a string?”. Jon showed very interesting things about behavior of ill-formed Unicode strings in .NET. I wondered about how similar examples will work on Mono. And I have got very interesting results.

Experiment 1: Compilation

Let's take the Jon's code with a small modification. We will just add text null check in DumpString:

using System;
using System.ComponentModel;
using System.Text;
using System.Linq;
[Description(Value)]
class Test
{
    const string Value = "X\ud800Y";
    static void Main()
    {
        var description = (DescriptionAttribute)typeof(Test).
            GetCustomAttributes(typeof(DescriptionAttribute), true)[0];
        DumpString("Attribute", description.Description);
        DumpString("Constant", Value);
    }
    static void DumpString(string name, string text)
    {
        Console.Write("{0}: ", name);
        if (text != null)
        {
            var utf16 = text.Select(c => ((uint) c).ToString("x4"));
            Console.WriteLine(string.Join(" ", utf16));
        }
        else
            Console.WriteLine("null");
    }
}

Read more    Comments


Happy Monday!



Today I tell you a story about one tricky bug. The bug is a tricky one because it doesn't allow me to debug my application on Mondays. I'm serious right now: the debug mode doesn't work every Monday. Furthermore, the bug literally tell me: "Happy Monday!".

So, the story. It was a wonderful Sunday evening, no signs of trouble. We planned to release a new version of our software (a minor one, but it includes some useful features). Midnight on the clock. Suddenly, I came up with the idea that we have a minor bug that should be fixed. It requires a few lines of code and 10 minutes to do it. And I decided to write needed logic before I go to sleep. I open VisualStudio, lunch build, and wait. But something goes wrong, because I get the following error:

Error connecting to the pipe server.

Hmm. It is a strange error.

Read more    Comments


Strange behavior of FindElementsInHostCoordinates in WinRT



Silverlight features a splendid method: VisualTreeHelper.FindElementsInHostCoordinates. It allows the HitTest, i.e. makes it possible for a point or rectangle to search for all visual sub-tree objects that intersect this rectangle or point. Formally the same method VisualTreeHelper.FindElementsInHostCoordinates is available in WinRT. And it seems the method looks in the same way, but there is a little nuance. It works differently in different versions of the platform. So, let’s see what’s going on.

Read more    Comments


About System.Drawing.Color and operator ==



Operator == that allows easy comparison of your objects is overridden for many standard structures in .NET. Unfortunately, not every developer really knows what is actually compared when working with this wonderful operator. This brief blog post will show the comparison logic based on a sample of System.Drawing.Color. What do you think the following code will get:

var redName = Color.Red;
var redArgb = Color.FromArgb(255, 255, 0, 0);
Console.WriteLine(redName == redArgb);

Read more    Comments


Setting up build configuration in .NET



You get two default build configurations: Debug and Release, when creating a new project in Visual Studio. And it’s enough for most small projects. But there can appear a necessity to extend it with the additional configurations. It’s ok if you need to add just a couple of new settings, but what if there are tens of such settings? And what if your solution contains 20 projects that need setting up of these configurations? In this case it becomes quite difficult to manage and modify build parameters.

In this article, we will review a way to make this process simpler by reducing description of the build configurations.

Read more    Comments


Jon Skeet's Quiz



Jon Skeet was once asked to give three questions to check how well you know C#. He asked the following questions:

  • Q1. What constructor call can you write such that this prints True (at least on the Microsoft .NET implementation)?
object x = new /* fill in code here */;
object y = new /* fill in code here */;
Console.WriteLine(x == y);

Note that it’s just a constructor call, and you can’t change the type of the variables.

  • Q2. How can you make this code compile such that it calls three different method overloads?
void Foo()
{
    EvilMethod<string>();
    EvilMethod<int>();
    EvilMethod<int?>();
}
  • Q3. With a local variable (so no changing the variable value cunningly), how can you make this code fail on the second line?
string text = x.ToString(); // No exception
Type type = x.GetType(); // Bang!

These questions seemed interesting to me, that is why I decided to discuss the solutions.

Read more    Comments


Unexpected area to collect garbage in .NET



The .NET framework provides an intelligent garbage collector that saves us a trouble of manual memory management. And in 95% of cases you can forget about memory and related issues. But the remaining 5% have some specific aspects connected to unmanaged resources, too big objects, etc. And it’s better to know how the garbage is collected. Otherwise, you can get surprises.

Do you think GC is able to collect an object till its last method is complete? It appears it is. But it is necessary to run an application in release mode without debugging. In this case JIT compiler will perform optimizations that will make this situation possible. Of course, JIT compiler does it when the remaining method body doesn’t contain references to the object or its fields. It should seem a very harmless optimization. But it can lead to the problems if you work with the unmanaged resources: object compilation can be executed before the operation over the unmanaged resource is finished. And most likely it will result in the application crash.

Read more    Comments


Unobviousness in use of C# closures



C# gives us an ability to use closures. This is a powerful tool that allows anonymous methods and lambda-functions to capture unbound variables in their lexical scope. And many programmers in .NET world like using closures very much, but only few of them understand how they really work. Let’s start with a simple sample:

public void Run()
{
  int e = 1;
  Foo(x => x + e);
}

Nothing complicated happens here: we just captured a local variable e in its lambda that is passed to some Foo method. Let’s see how the compiler will expand such construction.*

public void Run()
{
  DisplayClass c = new DisplayClass();
  c.e = 1;  
  Foo(c.Action);
}
private sealed class DisplayClass
{
  public int e;
  public int Action(int x)
  {
    return x + e;
  }
}

Read more    Comments


Wrapping C# class for use in COM



Let us have a C# class that makes something useful, for example:

public class Calculator
{
    public int Sum(int a, int b)
    {
        return a + b;
    }
}

Let’s create a COM interface for this class to make it possible to use its functionality in other areas. At the end we will see how this class is used in Delphi environment.

Read more    Comments