0

I am doing some experimentation, and I need to atomically decrement a 16-bit (short) value in C#. The value is stored in unmanaged memory, and it is not a problem to ensure the value is aligned to a 16-bit virtual memory boundary.

I found that winnt.h (kernel32) exposes such a function: https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-interlockeddecrement16

However, when I try to p/invoke this function from my .NET application, I get the following error:

Unable to find an entry point named 'InterlockedDecrement16' in DLL 'kernel32.dll'.

The method import code looks like this:

[DllImport("kernel32.dll")]
public static extern int InterlockedDecrement16(ref short addend);

Why am I getting this error, and do you see other alternatives to an interlocked16 p/invoke when there are no compiler intrinsics for this in C# (only 32-bit and 64-bit versions are exposed on Interlocked)

Liam
  • 27,717
  • 28
  • 128
  • 190
DEHAAS
  • 1,294
  • 11
  • 17
  • You can probably achieve the same thing without using pinvoke by simply adding a `lock` around you atomic value – Liam Nov 03 '20 at 13:54
  • @Liam Yes, sorry if the question was too vague on this. The goal would be to find a solution for the decrement with better performance than a lock. – DEHAAS Nov 03 '20 at 13:57
  • Describe "better performance"? Lock should be fine. If you want it to be atomic you always need some locking to prevent a race condition? A decrement should take fractions of a tick...How fast do you want it to be?! – Liam Nov 03 '20 at 13:58
  • 2
    Also, no way is making a pinvoke call going to "improve performance". This is probably just going to slow your application down, use more memory, CPU, etc. – Liam Nov 03 '20 at 14:09
  • 1
    Seems this function is only available as compiler intrinsic and has no corresponding "real" function in kernel32 – Evk Nov 03 '20 at 14:10
  • 1
    Does this answer your question? [C# Thread safe fast(est) counter](https://stackoverflow.com/questions/13181740/c-sharp-thread-safe-fastest-counter) – Liam Nov 03 '20 at 14:13
  • 1
    I forgot about [`Interlocked.Decrement`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.interlocked.decrement?view=netcore-3.1) this is what you should be using here. a Int32 should implicitly cast to a Int16 so should be fine for use as `short` – Liam Nov 03 '20 at 14:14
  • 3
    `winnt.h` has such a function by exposing the compiler intrinsic, `kernel32` does not. This intrinsic is not exposed in the managed world; you could always wrap it with C++/CLR if you really, really needed it -- otherwise, just making the value 32-bit is the path of least resistance, as you're not really in a position to extend the runtime. There is a [really old issue](https://github.com/dotnet/runtime/issues/4209) mentioning it, but no work appears to have been done. – Jeroen Mostert Nov 03 '20 at 14:19
  • @JeroenMostert Thanks, that was also my conclusion. I need to do a CompareExchange of a bigger memory area (64-bit) for other purposes, this area needs to contain the short value, which is why I cannot merely use an int32 value instead. That would require me to invoke a CAS operation on something bigger than 64-bit, which again is exposed in winnt.h, but not in the .NET runtime as far as I can find. – DEHAAS Nov 03 '20 at 14:30
  • 1
    @Liam No, that does not answer the question, and no, Interlocked.Decrement cannot be used on a short value reference – DEHAAS Nov 03 '20 at 14:38
  • No, it can be used on an Int32 which is a valid `short`. Int32 contains all the values for a short and (as I've already said) an implicit conversion exists – Liam Nov 03 '20 at 15:59
  • 1
    @Liam: there are no conversions, explicit or otherwise, from `ref Int32` to `ref Int16` and vice versa. Think a moment about how such an operation could possibly work and you'll see it's impossible to implement an atomic update of a 16-bit value if all you have is a primitive that atomically updates a 32-bit value. Whatever C# offers in the implicit conversion department is irrelevant. You may be glossing over the "this value is stored in unmanaged memory" part -- we are not free to simply extend the value. – Jeroen Mostert Nov 03 '20 at 16:47
  • What you could do is determine the necessary byte ('lock xadd', etc. for example compiling a small C/C++ sample) and build a C# function on it, something like this: https://stackoverflow.com/questions/18836120/using-c-inline-assembly-in-c-sharp. Of course this depends on the processor. – Simon Mourier Nov 03 '20 at 18:44
  • A big thanks to everyone here. I am now looking into creating a C++/CLR wrapper for the InterlockedDecrement16 function. I am however seeing some strange performance characteristics (https://stackoverflow.com/questions/64699059/c-sharp-and-c-cli-interlocked-performance). As I cannot mark a comment as an answer, please feel free to post your insights as an answer to this question. – DEHAAS Nov 05 '20 at 14:22

0 Answers0