Imagine we have a simple class, a wrapper around some array of structs (better data locality etc.):

Now, I would like to have an efficient access to every element. Obviously, a trivial indexer would be inefficient here, as it would return a copy of the given array element (a struct):

Luckily, since C# 7.0 we can “ref return” to efficiently return a reference to a given array element which is super nice (refer to my article about ref for more info):

Here, 99.9999% of devs will stop and will be satisfied with the semantics and performance results. But… if we know we will call it tremendously often, can we do better?!

First of all, let’s see what is being JITted by the .NET Core x64 runtime (5.0rc) when accessing 9th element (index is 8):

To those who know assembler a little, it may be clear what is going on here. But let’s make a short summary:

  • we see a little of “stack frame” creation here (sub/add rsp) – could we get rid of it in such a simple method?
  • we see bound check in line 4 (cmp the index to 8) to check if we are accessing an array with a correct index – could we get rid of it because we trust our code? 😇

Disclaimer: Getting rid of bound checks is very risky and the resulting dangers probably will overcome the performance benefits. Thus, use it only after heavy consideration, if you are sure why you need it and you can ensure caller’s code will be correct (providing valid indices).

To continue, we will be walking on thin ice of unsafe code now.

The first idea is to use Unsafe.Add to provide kind of “pointer arithmetic” – add an index-element to the first element:

The “problem” here is, it produces almost identical results because _array[0] is still a bound-checked array access (and we don’t get rid of stack frame too):

Hence, the non trivial question arises – how to get the address/ref to the first element of an array?

We could think of doing some Span-based magic (to use MemoryMarshal.GetReference):

But you can probably feel it – it produces even slower and bigger code (Span creation handling etc.) while still bound check will be there (Span is “safe”).

So, we need somehow to find a better way of getting an address of the first array’s element. The thing is, the internal structure of the array type is an implementation detail (although well-known). How can we overcome that?

The idea is… to rely on that implementation detail. This approach is being used by DangerousGetReferenceAt method from Microsoft.Toolkit.HighPerformance package maintained by Sergio Pedri. DangerousGetReferenceAt source code explains it well:

So, we are casting (reinterpreting) an array reference as a reference to some artificial RawArrayData class, which has a layout corresponding to an array layout. Thus, getting “data” reference is now just trivial. No bound checks at all!

The good news is this method has been ported to .NET 5! So, in .NET 5.0rc we can already use MemoryMarshal.GetArrayDataReference which does exactly the same thing:

Thus, without any external dependencies our code in .NET 5 may be rewritten to:

And the resulting code is indeed much more lightweight:

No bound-checks, and as an additional reward from the method simplicity – no stack frame.

Benchmarks are indeed showing a noticeable (well, in ns order of magnitude) difference:

Which simply means, we are now about 5x faster than with the initial solution!

Disclaimer #2: Approach taken here with the usage of GetArrayDataReferece is super dangerous. As Levi Broderick, one of .NET framework developers, said: “Also, read the method documentation. It does more than remove bounds checks; it also removes array variance checks. So it might not be valid to write to the ref, even if the index is within bounds. Misuse of the method will bite you in the ass, guaranteed.”  Moreover, documentation clearly states that “a reference may be used for pinning but must never be dereferenced” 😇

abbreviations

We all use libraries from NuGet tremendously often. But how many times you’ve created your own, even the simplest library that is being published there? I expect, not so often… So do I. Thus, I decided to make the super simple example of doing that, while utilizing the new and shiny GitHub Actions for all automations.

This serves many purposes, as often when I do something:

  • Minimum Viable Product (or example) of creating your own library that is automatically tested/versioned and published as a NuGet package – it requires to combine various building blocks and it turns out to be not-so-trivial. My goal would be to provide it as a kind of template for creating a new NuGet-based libraries.
  • playground for fresh open source contributors that are willing to do some contributions that will be then propagated to a regular NuGet package – without a risk and fear of destroying something
  • funny, joke-like package that, still, may be extended/improved
  • and obviously, a great place to learn all this stuff (including me)

So this is how Strings.Abbreviations has been coined. A very simple C#-based library that makes the simplest thing I could imagine – provide sets of methods that resolve popular abbreviations like BRB or IMHO. For example Strings.BRB() with return “Be right back” stringTo add some “logic”, there is an additional option to use “title casing”. So, Strings.BRB(titleCase: true) will return “Be Right Back”.

Obviously, this is trivial but it is by design! Thanks to that there is a very low entrance level for any contributor. Additionally, automation workflow is not hidden inside the complex logic and dependencies.

Initial design

So, this repository is based on a regular solution containing .NET Core library and test project. After the recent PR, there is also a trivial F#-based library (because we can!). As I said, the core part here is that Strings.Abbreviations is build, tested and published as a NuGet package with the use of Github Actions. All workflows we define are living inside the repository under .github/workflows folder. Currently there are two working workflows defined.

Building and testing

Every push and PR to master will trigger building and unit testing the whole solution. What’s super nice – thanks to that the result of the tests are available as an additional PR check. And we have a “badge” available on the top of this file (we will return to that).

Setting this was trivial, as we can use the default .NET Core workflow suggested when using Actions for the very first time. Underneath this workflow uses dotnet restore/build/test commands.

Building and publishing a NuGet package

Every push to master that bumps up Version in the Strings.Abbreviations.csproj file will trigger building and publishing a new NuGet package. Additionally, a new git tag will be pushed with the given version. It uses Publish NuGet custom Github Action which was also really easy to set up thanks to the documentation. All package data are taken from csproj file (no need for a separate .nuspec file). The only additional thing required here is to add NUGET_API_KEY secret key from the repository Settings tab.

Badges

To make our repository more professional and fancy, it is nice to add some “badges”. Readme.md file contains some. One is provided by the build-and-test workflow, and others (like current NuGet version or number of downloads) are provided by super-popular https://shields.io/ site:

What’s next?

It is not a full MVP still. There is a field of many improvements to make it a true template for potential library authors, including:

  • the next big thing to add is producing GitHub releases every time a new version is being created. I wanted to depend on version tags being already pushed by the publish-nuget workflow. Unfortunately I have an issue with that.
  • GitHub Actions for creating prerelease versions
  • some simple documentation auto-generation example, like the list of currently supported abbreviations
  • more automated tests, like code coverage or even benchmarks
  • the core “logic” improvement, like auto-generating title case versions, more abbreviations, more languages etc.
  • …? Any other good, not-to-complex automation ideas

So, in the end, please feel invited to look around and help me building it with your contributions. Any contributions to the “logic” itself is also welcomed!