Open10

隠れたメモリアロケーションに注意

四ツ山伊吹四ツ山伊吹

もととなるコード

AI に要求を伝えて書いてもらったコード

using System;
using System.Runtime.InteropServices;

static class C
{
    static ReadOnlySpan<byte> GetBytes<T>(T value) where T : unmanaged
    {
        if (typeof(T) == typeof(bool))   return BitConverter.GetBytes(  (bool)(object)value);
        if (typeof(T) == typeof(char))   return BitConverter.GetBytes(  (char)(object)value);
        if (typeof(T) == typeof(double)) return BitConverter.GetBytes((double)(object)value);
        if (typeof(T) == typeof(short))  return BitConverter.GetBytes( (short)(object)value);
        if (typeof(T) == typeof(int))    return BitConverter.GetBytes(   (int)(object)value);
        if (typeof(T) == typeof(long))   return BitConverter.GetBytes(  (long)(object)value);
        if (typeof(T) == typeof(float))  return BitConverter.GetBytes( (float)(object)value);
        if (typeof(T) == typeof(ushort)) return BitConverter.GetBytes((ushort)(object)value);
        if (typeof(T) == typeof(uint))   return BitConverter.GetBytes(  (uint)(object)value);
        if (typeof(T) == typeof(ulong))  return BitConverter.GetBytes( (ulong)(object)value);

        return MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));
    }
}
四ツ山伊吹四ツ山伊吹

以下のいくつかの要因が重なり、なんとなく “くさい” コードに思える

  • 🤔 二重キャスト。e.g. (double)(object)
  • 🤔 型の比較のため、typeof が重複してあらわれている。e.g. typeof(T) == typeof(double)
  • 🤔 同じ型名が一行で重複してあらわれている。e.g. typeof(double) (double)(object)value
  • 🤔 if 文の意図がわかりにくい。
四ツ山伊吹四ツ山伊吹

コンパイラによって解釈されたコード

おおむね元の雰囲気を色濃く残した感じのコードとなる。

折りたたみ
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

internal static class C
{
    private static ReadOnlySpan<byte> GetBytes<[IsUnmanaged] T>(T value) where T : struct
    {
        if (typeof(T) == typeof(bool))
        {
            return BitConverter.GetBytes((bool)(object)value);
        }
        if (typeof(T) == typeof(char))
        {
            return BitConverter.GetBytes((char)(object)value);
        }
        if (typeof(T) == typeof(double))
        {
            return BitConverter.GetBytes((double)(object)value);
        }
        if (typeof(T) == typeof(short))
        {
            return BitConverter.GetBytes((short)(object)value);
        }
        if (typeof(T) == typeof(int))
        {
            return BitConverter.GetBytes((int)(object)value);
        }
        if (typeof(T) == typeof(long))
        {
            return BitConverter.GetBytes((long)(object)value);
        }
        if (typeof(T) == typeof(float))
        {
            return BitConverter.GetBytes((float)(object)value);
        }
        if (typeof(T) == typeof(ushort))
        {
            return BitConverter.GetBytes((ushort)(object)value);
        }
        if (typeof(T) == typeof(uint))
        {
            return BitConverter.GetBytes((uint)(object)value);
        }
        if (typeof(T) == typeof(ulong))
        {
            return BitConverter.GetBytes((ulong)(object)value);
        }
        return MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));
    }
}
四ツ山伊吹四ツ山伊吹

改善案

AI に不満点を伝えて改善してもらったコード

using System;
using System.Runtime.InteropServices;

static class C
{
    static ReadOnlySpan<byte> GetBytes<T>(T value) where T : unmanaged
    {
        return value switch
        {
            bool   v => BitConverter.GetBytes(v),
            char   v => BitConverter.GetBytes(v),
            double v => BitConverter.GetBytes(v),
            short  v => BitConverter.GetBytes(v),
            int    v => BitConverter.GetBytes(v),
            long   v => BitConverter.GetBytes(v),
            float  v => BitConverter.GetBytes(v),
            ushort v => BitConverter.GetBytes(v),
            uint   v => BitConverter.GetBytes(v),
            ulong  v => BitConverter.GetBytes(v),
            _ => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1))
        };
    }
}
四ツ山伊吹四ツ山伊吹

ぱっと見でよくなったように思える

  • 😄 switch 式とパターンマッチングでなんだかエレガントな感じ!
  • 😄 コードの重複が減って見通しが良くなった!
  • 😄 コードの意図が明瞭になった!
四ツ山伊吹四ツ山伊吹

コンパイラによって解釈された改善案のコード

なにやら以前に増して object があらわれている。object obj = value

折りたたみ
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

internal static class C
{
    private static ReadOnlySpan<byte> GetBytes<[IsUnmanaged] T>(T value) where T : struct
    {
        Span<byte> span;
        if (value is bool)
        {
            object obj = value;
            bool value2 = (bool)((obj is bool) ? obj : null);
            span = BitConverter.GetBytes(value2);
        }
        else if (value is char)
        {
            object obj2 = value;
            char value3 = (char)((obj2 is char) ? obj2 : null);
            span = BitConverter.GetBytes(value3);
        }
        else if (value is double)
        {
            object obj3 = value;
            double value4 = (double)((obj3 is double) ? obj3 : null);
            span = BitConverter.GetBytes(value4);
        }
        else if (value is short)
        {
            object obj4 = value;
            short value5 = (short)((obj4 is short) ? obj4 : null);
            span = BitConverter.GetBytes(value5);
        }
        else if (value is int)
        {
            object obj5 = value;
            int value6 = (int)((obj5 is int) ? obj5 : null);
            span = BitConverter.GetBytes(value6);
        }
        else if (value is long)
        {
            object obj6 = value;
            long value7 = (long)((obj6 is long) ? obj6 : null);
            span = BitConverter.GetBytes(value7);
        }
        else if (value is float)
        {
            object obj7 = value;
            float value8 = (float)((obj7 is float) ? obj7 : null);
            span = BitConverter.GetBytes(value8);
        }
        else if (value is ushort)
        {
            object obj8 = value;
            ushort value9 = (ushort)((obj8 is ushort) ? obj8 : null);
            span = BitConverter.GetBytes(value9);
        }
        else if (value is uint)
        {
            object obj9 = value;
            uint value10 = (uint)((obj9 is uint) ? obj9 : null);
            span = BitConverter.GetBytes(value10);
        }
        else if (value is ulong)
        {
            object obj10 = value;
            ulong value11 = (ulong)((obj10 is ulong) ? obj10 : null);
            span = BitConverter.GetBytes(value11);
        }
        else
        {
            span = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));
        }
        return span;
    }
}
四ツ山伊吹四ツ山伊吹

IL

なにやら boxing/unboxing されている。box !!Tunbox.any

折りたたみ(一部)
        IL_0000: ldarg.0
        IL_0001: box !!T
        IL_0006: isinst [System.Runtime]System.Boolean
        IL_000b: brfalse.s IL_0023

        IL_000d: ldarg.0
        IL_000e: box !!T
        IL_0013: isinst [System.Runtime]System.Boolean
        IL_0018: unbox.any [System.Runtime]System.Boolean
        IL_001d: stloc.0
        // sequence point: hidden
        IL_001e: br IL_0167

        IL_0023: ldarg.0
        IL_0024: box !!T
        IL_0029: isinst [System.Runtime]System.Char
        IL_002e: brfalse.s IL_0046

        IL_0030: ldarg.0
        IL_0031: box !!T
        IL_0036: isinst [System.Runtime]System.Char
        IL_003b: unbox.any [System.Runtime]System.Char
        IL_0040: stloc.1
        // sequence point: hidden
        IL_0041: br IL_0179
四ツ山伊吹四ツ山伊吹

表面上はよくなったように見えて、パフォーマンス面では悪影響が出ている。

  • 😨 結局、二重キャストしている。
  • 😨 object 型への変数代入が生じている。e.g. object obj = value
  • 😨 boxing/unboxing が生じている。e.g. IL_000e: box !!TIL_0018: unbox.any [System.Runtime]System.Boolean
四ツ山伊吹四ツ山伊吹

顧客が本当に必要だったもの

using System;
using System.Runtime.InteropServices;

static class C
{
    static ReadOnlySpan<byte> GetBytes(  bool value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes(  char value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes(double value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes( short value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes(   int value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes(  long value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes( float value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes(ushort value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes(  uint value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes( ulong value) => BitConverter.GetBytes(value);
    static ReadOnlySpan<byte> GetBytes<T>(T value) where T : unmanaged => MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref value, 1));
}