はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】.NETの内部実装がGitHubで公開されていたので見てみる(Contributeも可)

はじめに

先日教えていただいたのですが、どうやら.NETの内部実装を見れるGitHubRepositoryが公開されているらしいです。

This repo contains the code to build the .NET runtime, libraries and shared host (dotnet) installers for all supported platforms, as well as the sources to .NET runtime and libraries.

// DeepL翻訳
このレポには、.NETランタイム、ライブラリ、共有ホスト(dotnet)インストーラをビルドするコードと、.NETランタイムとライブラリのソースが含まれています。

github.com

なんならcontributionもできるっぽいですね。

We welcome contributions! Many people all over the world have helped make this project better.

Contributing explains what kinds of contributions we welcome
Workflow Instructions explains how to build and test
Get Up and Running on .NET Core explains how to get nightly builds of the runtime and its libraries to test them in your own projects.

// DeepL翻訳
私たちは貢献を歓迎します!世界中の多くの人々が、このプロジェクトをより良いものにするために協力してくれています。

コントリビュートでは、どのようなコントリビュートを歓迎するかを説明しています。
ワークフローの説明では、ビルドとテストの方法を説明します。
Get Up and Running on .NET Coreでは、ランタイムとそのライブラリのナイトリービルドを取得して、自分のプロジェクトでテストする方法を説明します。

.NET7で最適化されたコードを見てみる

これだけで記事が終わるのも少し寂しいので、.NET7でLINQに最適化が入ったという噂を聞いたのでコードを見てみたいと思います。

例えばEnumerable.Average()に関して言うと、配列やListであった場合はSpanを利用して計算を行うようにしたことが早くなった要因の一つのようですね。
.NET7 で LINQ の集計関数がめっちゃ高速化した話 (あるいは、ベクトル化の難しさ) - Qiita

github.com

namespace System.Linq
{
    public static partial class Enumerable
    {
        public static double Average(this IEnumerable<int> source)
        {
            if (source.TryGetSpan(out ReadOnlySpan<int> span))
            {
                // Int32 is special-cased separately from the rest of the types as it can be vectorized:
                // with at most Int32.MaxValue values, and with each being at most Int32.MaxValue, we can't
                // overflow a long accumulator, and order of operations doesn't matter.

                if (span.IsEmpty)
                {
                    ThrowHelper.ThrowNoElementsException();
                }

                long sum = 0;
                int i = 0;

                if (Vector.IsHardwareAccelerated && span.Length >= Vector<int>.Count)
                {
                    Vector<long> sums = default;
                    do
                    {
                        Vector.Widen(new Vector<int>(span.Slice(i)), out Vector<long> low, out Vector<long> high);
                        sums += low;
                        sums += high;
                        i += Vector<int>.Count;
                    }
                    while (i <= span.Length - Vector<int>.Count);
                    sum += Vector.Sum(sums);
                }

                for (; (uint)i < (uint)span.Length; i++)
                {
                    sum += span[i];
                }

                return (double)sum / span.Length;
            }

            using (IEnumerator<int> e = source.GetEnumerator())
            {
                if (!e.MoveNext())
                {
                    ThrowHelper.ThrowNoElementsException();
                }

                long sum = e.Current;
                long count = 1;

                while (e.MoveNext())
                {
                    checked { sum += e.Current; }
                    count++;
                }

                return (double)sum / count;
            }
        }
// 以下省略...

Enumerable.TryGetSpanメソッド(private)なるもので、Spanが取れたらそれを利用するという流れなようですね。
github.com

/// <summary>Validates that source is not null and then tries to extract a span from the source.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // fast type checks that don't add a lot of overhead
private static bool TryGetSpan<TSource>(this IEnumerable<TSource> source, out ReadOnlySpan<TSource> span)
    // This constraint isn't required, but the overheads involved here can be more substantial when TSource
    // is a reference type and generic implementations are shared.  So for now we're protecting ourselves
    // and forcing a conscious choice to remove this in the future, at which point it should be paired with
    // sufficient performance testing.
    where TSource : struct
{
    if (source is null)
    {
        ThrowHelper.ThrowArgumentNullException(ExceptionArgument.source);
    }

    // Use `GetType() == typeof(...)` rather than `is` to avoid cast helpers.  This is measurably cheaper
    // but does mean we could end up missing some rare cases where we could get a span but don't (e.g. a uint[]
    // masquerading as an int[]).  That's an acceptable tradeoff.  The Unsafe usage is only after we've
    // validated the exact type; this could be changed to a cast in the future if the JIT starts to recognize it.
    // We only pay the comparison/branching costs here for super common types we expect to be used frequently
    // with LINQ methods.

    bool result = true;
    if (source.GetType() == typeof(TSource[]))
    {
        span = Unsafe.As<TSource[]>(source);
    }
    else if (source.GetType() == typeof(List<TSource>))
    {
        span = CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source));
    }
    else
    {
        span = default;
        result = false;
    }

    return result;
}

確かにGetType()で型を見て、型の変換を行っているようです。(あくまでメモリ上のデータの解釈を変えるだけで、コピーが走っているわけではないです)