Repeated dllimport call of C dll in C# seems slow compared to straight C#

c, c#, dllimport, performance

Solution

Benchmarks are tricky to get right; especially micro-benchmarks, I think. For one thing, you're not measuring what you think you are in your straight C# test - the compiler (or probably the jitter) recognizes that the result is loop invariant, so it's running the loop only once.

Here are the results on my machine with your `runCScode.cs` unchanged:

c:\temp>csc runCScode.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.


c:\temp>runCScode
Answer is 144.703125.  Straight CS took 10001 ticks.

And here are the results with the `for` loop line commented out:

c:\temp>csc runCScode.noloop.cs
Microsoft (R) Visual C# 2010 Compiler version 4.0.30319.1
Copyright (C) Microsoft Corporation. All rights reserved.


c:\temp>runCScode.noloop
Answer is 144.703125.  Straight CS took 10001 ticks.

I thought it was strange that running that bit of code once took so long, so I guessed that there was some overhead in actually getting the `DateTime` value for some reason (I'm not sure why - it was just a guess). Actually, I was surprised the non-loop took anytime at all, since I would have guessed that the compiler would boil the whole thing down to moving a constant value into `result`. So, I added the following line after the initialization of `tick1`:

tick1 = DateTime.Now.Ticks;

Yes, that's right - I just reloaded `tick1` with `DateTime.Now.Ticks` again.

Then I ran the test again:

c:\temp>runCScode.noloop
Answer is 144.703125.  Straight CS took 0 ticks.

(note: to be fair, about a third of the time I ran the benchmark that didn't reload `tick1` I got a count of 0. But most of the runs got a count of 10000+/-1. The version that reloads `tick1` always reported a count of 0 ticks).

That said, as several people pointed out in the comments, there's no expectation that C is going to be significantly faster than C# in all things and that for small operations calling a C function will have an overhead imposed by P/Invoke and parameter/result marshaling. You'll also lose an opportunity for the jitter to perform optimizations. So, I think the bottom line is to not worry about using C for performance in a .NET application unless you have an area that needs to be faster and you have reason to believe that there's something that native C or C++ can give you that you can't get from C# (or C++/CLI).

That reason won't be a few floating point operations.

Finally, I think it should be said that one of the main reasons for calling to C or C++ DLLs isn't necessarily for performance. It's because there's a library (or even single API) that exists in a native DLL that you want to use and that functionality isn't available in a .NET class.

Problem

I was testing the speed of calling C code from a dll in C# with System.Runtime.InteropServices.DllImportAttribute. The C function generates a custom struct, fills it with values, does, a calculation, then returns the result. This process I repeated hundreds of thousands of times in a loop whereby I recorded the number of ticks before the looping and after the looping. Then I made the exact same function in straight C# and repeated this trial. The straight C# method was much faster than using the unmanaged DLL. Why? Seems to be no speed gain from unmanaged. c2cstest.c ``` #include <stdio.h> struct test { double a; double b; double c; }; _declspec(dllexport) double myCfunction(double input) { struct test one; one.a = input; one.b = one.a * one.a; one.c = one.b * one.a; return one.c; } ``` cl /LD cscstest.c runCcode.cs ``` using System; using System.Runtime.InteropServices; class test { [DllImport("c2cstest.dll")] public static extern double myCfunction (double input); static void Main() { double x = 5.25; double result = 0.0; long tick1 = DateTime.Now.Ticks; for(int y = 100000; y > 0; y--) { result = myCfunction(x); } long tick2 = DateTime.Now.Ticks; Console.WriteLine("Answer is {0}. Dllimport took {1} ticks.", result, tick2-tick1); } } ``` OUTPUT: Answer is 144.703125. Dllimport took 250000 ticks. RunCScode.cs ``` using System; using System.Runtime.InteropServices; struct test { public double a; public double b; public double c; } class testclass { double Mycsfunction (double input) { test one; one.a = input; one.b = one.a * one.a; one.c = one.b * one.a; return one.c; } static void Main() { double x = 5.25; double result = 0.0; testclass ex = new testclass(); long tick1 = DateTime.Now.Ticks; for(int y = 100000; y > 0; y--) { result = ex.Mycsfunction(x); } long tick2 = DateTime.Now.Ticks; Console.WriteLine("Answer is {0}. Straight CS took {1} ticks.", result, tick2-tick1); }} ``` OUTPUT: Answer is 144.703125. Straight CS took 50000 ticks. ADDITIONAL: After trying various methods I came to the same conclusion as this guy Techniques of Calling Unmanaged Code , although he tried more methods than I did. Conclusion: Straight simple function calls are not worth it (especially when they are looped). Putting the loop inside the unmanaged function should certainly help. Very large functions could be worthwhile. No matter how many different ways you try it, marshalling is not an efficient technology.

Original source