Span<T>, Memory<T> - slog

Span์™€ Memory์— ๋Œ€ํ•ด ๊ธฐ๋ก์„ ๋‚จ๊ธฐ๋ ค ํ•ฉ๋‹ˆ๋‹ค. ์ฒ˜์Œ ๋“ฑ์žฅํ–ˆ์„ ๋•Œ ์ต์ˆ™ํ•ด์ง€๋ ค๊ณ  ๋…ธ๋ ฅํ•˜๋‹ค๊ฐ€ ์‹คํŒจํ–ˆ๋Š”๋ฐ, ์ต์ˆ™ํ•ด์ง„๋‹ค๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์Šต๊ด€, ๋น„๊ด€๋ฆฌ๋ฉ”๋ชจ๋ฆฌ์˜ ํšจ๊ณผ์ ์ธ ์ ‘๊ทผ, ๋ช…์„ธ ํ‘œํ˜„์œผ๋กœ ๊ด€๋ จ ๋ฒ„๊ทธ๋ฅผ ์ตœ์†Œํ•˜ ํ•จ ๋“ฑ ๋‹ค์–‘ํ•œ ์žฅ์ ์ด ๊ธฐ๋Œ€๋˜๋ฏ€๋กœ, ๊ด€๋ จ ์ •๋ณด๋“ค์„ ์ˆ˜์ง‘ ํ•œ ํ›„ ์‹ค์Šต์„ ํ†ตํ•ด ์ตํ˜€๋‚˜๊ฐ€๋ด…์‹œ๋‹ค

์ •์„ฑํƒœ๋‹˜์ด ์ •๋ฆฌํ•œ ๋‚ด์šฉ์„ ๊ณต์œ  ํ•ฉ๋‹ˆ๋‹ค. ์ •์„ฑํƒœ๋‹˜์˜ ๊ธ€์€ ์ฒ ์ €ํžˆ ๊ฒ€์ฆํ•˜์—ฌ ์ž‘์„ฑ๋œ ๊ธ€์ด๊ธฐ ๋•Œ๋ฌธ์— ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

.NET Framework: 758. C# 7.2 - Span (sysnet.pe.kr)
.NET Framework: 759. C# - System.Span ์„ฑ๋Šฅ (sysnet.pe.kr)
.NET Framework: 768. BenchmarkDotNet์œผ๋กœ Span ์„ฑ๋Šฅ ์ธก์ • (sysnet.pe.kr)
.NET Framework: 995. C# - Span์™€ Memory (sysnet.pe.kr)

jacking75๋‹˜์ด ๋ฒˆ์—ญํ•˜์‹  ๋‚ด์šฉ๋„ ์ข‹์Šต๋‹ˆ๋‹ค.

Span ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•  5๊ฐ€์ง€ ์ด์œ  - jacking75

Span์€ ref struct์ด๊ธฐ ๋•Œ๋ฌธ์— ์Šคํƒ์—๋งŒ ํ• ๋‹นํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์˜ ์„ฑํƒœ๋‹˜ ์ •๋ฆฌ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

  1. ํ‰์†Œ์—๋Š” ์„ฑ๋Šฅ์„ ์œ„ํ•ด Span๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , 2) ๊ฐ„ํ˜น ํ•ด๋‹น ๋ฒ„ํผ๋ฅผ ๋‹ค๋ฅธ ํƒ€์ž…์˜ ํ•„๋“œ๋กœ ๋“ค๊ณ  ์žˆ์–ด์•ผ ํ•  ๋•Œ Memory๋ฅผ ์‚ฌ์šฉํ•˜๋‹ค๊ฐ€, 3) ๋‹ค์‹œ ๊ทธ๊ฒƒ์„ ์ ‘๊ทผํ•ด์•ผ ํ•  ๋•Œ๋Š” Span๋กœ ์บ์‹œํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ

Span๋ฐ Memory๋Š” ์ธ์ ‘ํ•œ ์ž„์˜ ๋ฉ”๋ชจ๋ฆฌ ์˜์—ญ์— ๋Œ€ํ•ด ํ˜•์‹ ๋ฐ ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์ „ ํ‘œํ˜„์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

์ด์— ๋ฐ˜ํ•ด ReadOnlySequence๋Š” ๋ถˆ์—ฐ์†์ ์ธ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ•ฉ์ณ์„œ ์ˆœ์ฐจ์ ์ธ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๋‚ด์šฉ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. :grinning: :grinning:

์ข‹์•„์š” 1

๋„์‹์œผ๋กœ ์ž˜ ์„ค๋ช…๋œ ์ข‹์€ ๊ธ€์„ ๋งํฌ ํ•ฉ๋‹ˆ๋‹ค. ๋‚ด์šฉ ์žˆ์œผ๋ฏ€๋กœ ์ฐฌ์ฐฌํžˆ ์‹œ๊ฐ„์„ ๋‘๊ณ  ์ฝ์œผ์‹œ๋ฉด ๋„์›€์ด ๋  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

New NET Core 2.1 Flagship Types: Span and Memory (codemag.com)

์ข‹์•„์š” 1

์ •์„ฑํƒœ๋‹˜์˜
.NET Framework: 768. BenchmarkDotNet์œผ๋กœ Span ์„ฑ๋Šฅ ์ธก์ • (sysnet.pe.kr)

๋ฒค์น˜๋งˆํฌ ์ฝ”๋“œ๋ฅผ ์ด์šฉํ•œ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

image

Span๋Š” ๋น„๊ด€๋ฆฌ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋ฐฐ์—ด์ฒ˜๋Ÿผ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์žฅ์ ๋„ ์žˆ์ง€๋งŒ, ๋ฐ”์ดํŠธ ๋ฐฐ์—ด์€ ๋น„์šฉ์—†์ด ์ชผ๊ฐค ์ˆ˜ ์—†์ง€๋งŒ, Span๋Š” ํ•„์š”ํ•œ ์˜์—ญ๋งŒ ์ž˜๋ผ ์“ธ ์ˆ˜ ์žˆ๊ณ  ๋น„์šฉ ๋˜ํ•œ ์—†๋‹ค๋Š”๊ฒŒ ์žฅ์ ์ธ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

(byte[], offset, length) buffer ๋ณด๋‹ค๋Š” Span buffer๊ฐ€ ์ฝ”๋“œ๋„ ๊น”๋”ํ•˜๊ณ  ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•  ์—ฌ์ง€๊ฐ€ ์ ๋‹ค๋Š” ๊ฒƒ์ด์ฃ .

C#์˜ ์‚ฌ์šฉ์ž ์ง€์ • ์ด์ง„ ์ง๋ ฌํ™” - :eyeglasses: ์ฝ์„ ๊ฑฐ๋ฆฌ - ๋‹ท๋„ท๋ฐ๋ธŒ (dotnetdev.kr)

์ด ๊ธ€์ด ์ €์—๊ฒ ์‹ค์งˆ์ ์œผ๋กœ ๋„์›€์ด ๋์Šต๋‹ˆ๋‹ค. ๊ฐœ๋…์ ์œผ๋กœ ์ดํ•ดํ•˜๋Š” ๊ฒƒ๊ณผ ์†์ด ๋‚˜๊ฐ€๋Š” ๊ฒƒ์€ ์กฐ๊ธˆ ๋‹ค๋ฅธ ๊ฐ๊ฐ์ธ ๊ฒƒ ๊ฐ™๋„ค์š”. ๋งˆ์น˜ ์˜์–ด์˜ ๋…ํ•ด์™€ ํšŒํ™” ๊ฐ™์ด์š”.

์ฐธ๊ณ ํ•˜์—ฌ Span ํ™•์žฅ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด ๋ดค์Šต๋‹ˆ๋‹ค. ๊ฐ™์ด ๋ณด์‹œ์ฃ .

public static class SpanExtension
{
    public static ReadOnlySpan<byte> AsReadOnlyBytes<T>(this ref T @this)
        where T : struct
    {
        var span = MemoryMarshal.CreateReadOnlySpan(ref @this, 1);
        return MemoryMarshal.Cast<T, byte>(span);
    }

    public static ReadOnlySpan<byte> AsReadOnlyBytes<T>(this T[] @this)
        where T : struct
    {
        return MemoryMarshal.Cast<T, byte>(@this);
    }

    public static Span<byte> AsBytes<T>(this ref T @this)
        where T : struct
    {
        var span = MemoryMarshal.CreateSpan(ref @this, 1);
        return MemoryMarshal.Cast<T, byte>(span);
    }

    public static Span<byte> AsBytes<T>(this T[] @this)
        where T : struct
    {
        return MemoryMarshal.Cast<T, byte>(@this);
    }
}

ํ™•์žฅ์„ ๋ณด์‹œ๋ฉด MemoryMarshal์˜ ๊ธฐ๋Šฅ์„ ์ด์šฉํ•œ ๊ฒƒ ๋ฟ์ด์ง€๋งŒ, ๊ฐ•๋ ฅํ•ฉ๋‹ˆ๋‹ค. ์ด ํ™•์žฅ์„ ์ด์šฉํ•ด ๋‹ค์Œ๊ณผ ๊ฐ™์ด Stream์— ์“ฐ๊ฑฐ๋‚˜ ์ฝ๋Š” ์ฝ”๋“œ๋ฅผ ๋‹จ์ˆœํ™” ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

    public void Write(Stream s)
    {
        s.Write(์ฒด๊ฒฐ์‹œ๊ฐ.AsReadOnlyBytes());
        s.Write(์ฒด๊ฒฐ๊ฐ€.AsReadOnlyBytes());
        s.Write(์ฒด๊ฒฐ์ˆ˜๋Ÿ‰.AsReadOnlyBytes());
        s.Write(๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰.AsReadOnlyBytes());
        s.Write(๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰.AsReadOnlyBytes());
    }

    public static bool Read(Stream s, ref ์‹ค์‹œ๊ฐ„์‹œ์„ธ v)
    {

        var length = s.Read(v.์ฒด๊ฒฐ์‹œ๊ฐ.AsBytes());
        if (length == 0)
            return false;
        
        s.Read(v.์ฒด๊ฒฐ๊ฐ€.AsBytes());
        s.Read(v.์ฒด๊ฒฐ์ˆ˜๋Ÿ‰.AsBytes());
        v.๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰ = new decimal[5];
        s.Read(v.๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰.AsBytes());
        v.๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰ = new decimal[5];
        s.Read(v.๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰.AsBytes());

        return true;
    }

AsReadOnlyBytes()๋ฅผ ํ†ตํ•ด ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น ์—†์ด ํ•ด๋‹น ํ•„๋“œ๋ฅผ ReadOnlySpan๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์“ฐ๊ธฐ๋ฅผ ํ•ฉ๋‹ˆ๋‹ค.
๋”์šฑ ์žฌ๋ฐŒ๋Š” ๊ฒƒ์€ AsBytes()๋ฅผ ํ†ตํ•ด ํ•ด๋‹น ํ•„๋“œ์˜ ๋ฉ”๋ชจ๋ฆฌ ์œ„์น˜๋ฅผ Span๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฐ”๋กœ ์ฝ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ˜ธ๊ฐ€๋Š” ๋ฐฐ์—ด์ธ๋ฐ ํ• ๋‹น์„ ๋ณ„๋„๋กœ ํ•ด์•ผ ํ•˜๋Š” ์ด์œ ๋Š” C#์˜ ๋ฐฐ์—ด์€ ์ฐธ์กฐ๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. 'fixedโ€™๋ฅผ ์“ฐ๋ฉด ๋˜๋Š”๋ฐ 'unsafeโ€™๋ผ ์ผ๋ฐ˜์ ์œผ๋กœ๋Š” ์ž˜ ์•ˆ์“ฐ๋Š”๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค

์Šฌ๋ผ์ด์‹ฑ์˜ ๊ฐ•๋ ฅํ•จ์„ ์„ค๋ช…ํ•œ ๊ธ€์ž…๋‹ˆ๋‹ค.

์ž„์‹œ์‹ค์‹œ๊ฐ„์‹œ์„ธ 1Gbytes (500๋งŒ๊ฐœ)๋ฅผ ์ฝ๋Š” ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์ดˆ๋‹น ํ•œ๊ฐœ์”ฉ ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ํ•  ๋•Œ, ๋Œ€๋žต 60์ผ ์ •๋„์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. ํฅ๋ฏธ๋กœ์šด ๊ฒƒ์€ List๋กœ ๋ชฉ๋ก์„ ์ทจํ•ฉํ•˜๋”๋ผ๋„ T[]๊ณผ ํฐ ์ฐจ์ด๊ฐ€ ๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. List๊ฐ€ ์ตœ์ ํ™” ๋˜์–ด ์žˆ๋‹ค๋Š” ๊ฒƒ์ด๊ฒ ์ฃ 

void ์ž„์˜์‹œ์„ธ๋ชฉ๋ก_์ฝ๊ธฐ()
{
    var filename = "์‹ค์‹œ๊ฐ„์‹œ์„ธ.dat";
    using var fs = File.OpenRead(filename);
    var length = fs.Length;
    var itemCount = length / Marshal.SizeOf<์‹ค์‹œ๊ฐ„์‹œ์„ธ>();
    Console.WriteLine($"File Length: {length}");
    Console.WriteLine($"Count: {itemCount}");

    var v = new ์‹ค์‹œ๊ฐ„์‹œ์„ธ();
    //var ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก = new List<์‹ค์‹œ๊ฐ„์‹œ์„ธ>();
    var ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก = new ์‹ค์‹œ๊ฐ„์‹œ์„ธ[itemCount];

    var count = 0;
    var sw = Stopwatch.StartNew();
    while (count < itemCount)
    {
        //var result = ์‹ค์‹œ๊ฐ„์‹œ์„ธ.Read(fs, ref ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก[count]);
        var result = ์‹ค์‹œ๊ฐ„์‹œ์„ธ.Read(fs, ref v);
        if (result == false)
            break;
        //์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก.Add(v);
        ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก[count] = v;

        count++;
    }
    sw.Stop();

    // List<T> : 2571 ms
    // T[], ์Šคํƒ ์ž„์‹œ๊ฐ’ ์ €์žฅ ํ›„ ๋ณต์‚ฌ : 2354 ms
    // T[], ์ง์ ‘ ์ €์žฅ : 2339 ms
    Console.WriteLine($"Elapsed Time: {sw.ElapsedMilliseconds}");
}

์กฐ๊ธˆ ๋” ๋‹ค๋ฅธ ์ ‘๊ทผ์„ ํ•ด๋ดค์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ, MemoryMappedFile์„ ์ด์šฉํ•ด Span<byte>์„ ํš๋“ํ•  ์ˆ˜ ๋งŒ ์žˆ๋‹ค๋ฉด, ์ข€ ๋” ํšจ์œจ์ ์ธ ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ๋ฅผ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์„๊บผ๋ผ ์ƒ๊ฐํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ™•์ธํ•œ ๊ฒฐ๊ณผ, ์–ด์ฉ” ์ˆ˜ ์—†์ด unsafe์˜์—ญ์„ ์‚ฌ์šฉํ•ด์•ผ ํ–ˆ๊ณ , decimal์ด ๊ณ ์ • ๋ฐฐ์—ด์ด ์•ˆ๋œ๋‹ค๋Š” ์‚ฌ์‹ค๋„ ์•Œ๊ฒŒ ๋˜์—ˆ์ง€๋งŒ, ์–ด์จŒ๋“  ์ƒ๋‹นํžˆ ๋น ๋ฅธ ์†๋„๋กœ ๋ชฉ๋ก(Span<์‹ค์‹œ๊ฐ„์‹œ์„ธ2>)์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

void ์ž„์˜์‹œ์„ธ๋ชฉ๋ก_์ฝ๊ธฐ2()
{
    var filename = "์‹ค์‹œ๊ฐ„์‹œ์„ธ.dat";
    var fileLength = new FileInfo(filename).Length;

    using var mmf = MemoryMappedFile.CreateFromFile(filename, FileMode.Open);
    using var accessor = mmf.CreateViewAccessor();

    Span<byte> memory;
    unsafe
    {
        byte* ptr = null;
        accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
        ptr += accessor.PointerOffset;

        //memory = new Span<byte>(ptr, (int)accessor.SafeMemoryMappedViewHandle.ByteLength);
        memory = new Span<byte>(ptr, (int)fileLength);
    }


    var ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก = MemoryMarshal.Cast<byte, ์‹ค์‹œ๊ฐ„์‹œ์„ธ2>(memory);
    foreach (var item in ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก)
    {
        ;
    }

    accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct ์‹ค์‹œ๊ฐ„์‹œ์„ธ2
{
    public long ์ฒด๊ฒฐ์‹œ๊ฐ;
    public decimal ์ฒด๊ฒฐ๊ฐ€;
    public decimal ์ฒด๊ฒฐ์ˆ˜๋Ÿ‰;
    public fixed int _๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰[4 * 5];
    public fixed int _๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰[4 * 5];
}

TLS๋กœ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ ๊ณต์œ  ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์œผ๋กœ Span<T>์— ๋Œ€ํ•œ ๊ณต๋ถ€๋Š” ๋งˆ๋ฌด๋ฆฌ ํ•˜๊ณ , ๋‹ค์Œ๋ฒˆ์— Memory<T>๋ฐ Memory<T>์™€ ๊ด€๋ จ๋œ .NET ๊ธฐ๋Šฅ๋“ค์„ ์‚ดํŽด๋ณผ ์˜ˆ์ •์ž…๋‹ˆ๋‹ค.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

// ๋ชจ๋“  ์˜ˆ์ œ๋Š” Top-level-statements(TLS)๋กœ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค.

// ์ œ๋ชฉ: ๋Œ€์šฉ๋Ÿ‰ ํŒŒ์ผ ๋ฐ์ดํ„ฐ๋ฅผ .NET C#์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•
// ์ฃผ์ œ: ์•„์ฃผ ํฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ ค๊ณ  ํ•  ๋•Œ .NET์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ€์žฅ ํšจ๊ณผ์ ์ธ ๋ฐฉ๋ฒ•์„ ์ฐพ๋Š”๋‹ค.
// ์ „์ œ: 1) ์ฃผ์‹ ์‹ค์‹œ๊ฐ„ ์‹œ์„ธ ๋ชฉ๋ก์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•œ๋‹ค. ์ด ์‹œ์„ธ๋Š” ์ž„์˜๋กœ ์•ฝ 1Gbytes๋ฅผ ์ƒ์„ฑํ•ด๋‘”๋‹ค.
//       2) ํ•˜๋‚˜์˜ ์‹œ์„ธ๋Š” ์ •์  ๊ธธ์ด๋ฅผ ๊ฐ–๋Š”๋‹ค.
// ์ง„ํ–‰: 0) ์ž„์˜์˜ ์‹ค์‹œ๊ฐ„ ์‹œ์„ธ ์ •๋ณด๋ฅผ ํŒŒ์ผ๋กœ ์ €์žฅํ•œ๋‹ค. (1Gbytes)
//       1) ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์ธ FileStream์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
//       X 2) ๊ทน์ ์ธ ์†๋„ ํ–ฅ์ƒ์„ ์œ„ํ•ด ์‹ค์‹œ๊ฐ„ ์‹œ์„ธ๋ฅผ ๋ฉ”๋ชจ๋ฆฌ๋กœ ์˜ฌ๋ฆฐ ํ›„ Memory<T>๋ฅผ ์ด์šฉํ•ด์„œ ๊ตฌํ˜„ํ•œ๋‹ค.
//       3) MemoryMappedFile์„ ์ด์šฉํ•ด์„œ ๊ตฌํ˜„ํ•œ๋‹ค.


์ž„์˜์‹œ์„ธ๋ชฉ๋ก_๋งŒ๋“ค๊ธฐ();
์ž„์˜์‹œ์„ธ๋ชฉ๋ก_์ฝ๊ธฐ2();


void ์ž„์˜์‹œ์„ธ๋ชฉ๋ก_๋งŒ๋“ค๊ธฐ()
{
    var filename = "์‹ค์‹œ๊ฐ„์‹œ์„ธ.dat";
    if (File.Exists(filename) == true)
        return;

    var now = DateTime.Now;
    var v = new ์‹ค์‹œ๊ฐ„์‹œ์„ธ
    {
        ์ฒด๊ฒฐ์‹œ๊ฐ = now.ToBinary(),
        ์ฒด๊ฒฐ๊ฐ€ = 15000,
        ์ฒด๊ฒฐ์ˆ˜๋Ÿ‰ = 100,
        ๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰ = new decimal[] { 1, 2, 3, 4, 5 },
        ๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰ = new decimal[] { 11, 22, 33, 44, 55 }
    };

    // Unsafe.Sizeof<T>๋กœ๋„ ์‚ฌ์ด์ฆˆ๋ฅผ ์•Œ ์ˆ˜ ์žˆ์œผ๋‚˜, ์ฐธ์กฐํ˜•์˜ ๊ฒฝ์šฐ ์ฐธ์กฐ ์‚ฌ์ด์ฆˆ๋„ ๊ณ„์‚ฐํ•ด์•ผ ํ•˜๋ฏ€๋กœ Marshal.SizeOf<T>๋ฅผ ์‚ฌ์šฉ ํ•จ
    var count = 1000 * 1000 * 1000 / Marshal.SizeOf<์‹ค์‹œ๊ฐ„์‹œ์„ธ>();

    Console.WriteLine($"Marshal SizeOf: {Marshal.SizeOf<์‹ค์‹œ๊ฐ„์‹œ์„ธ>()} bytes");
    Console.WriteLine($"Unsafe SizeOf: {Unsafe.SizeOf<์‹ค์‹œ๊ฐ„์‹œ์„ธ>()} bytes");
    Console.WriteLine($"Count: {count}");

    using var fs = File.OpenWrite(filename);
    for (var i = 0; i < count; i++)
    {
        v.์ฒด๊ฒฐ์‹œ๊ฐ += 1;
        v.์ฒด๊ฒฐ์ˆ˜๋Ÿ‰ += 1;

        v.Write(fs);
    }

    fs.Dispose();

    var fi = new FileInfo(filename);
    Console.WriteLine($"File Length : {fi.Length} bytes");
}

#pragma warning disable CS8321 // ๋กœ์ปฌ ํ•จ์ˆ˜๊ฐ€ ์„ ์–ธ๋˜์—ˆ์ง€๋งŒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ
void ์ž„์˜์‹œ์„ธ๋ชฉ๋ก_์ฝ๊ธฐ()
#pragma warning restore CS8321 // ๋กœ์ปฌ ํ•จ์ˆ˜๊ฐ€ ์„ ์–ธ๋˜์—ˆ์ง€๋งŒ ์‚ฌ์šฉ๋˜์ง€ ์•Š์Œ
{
    var filename = "์‹ค์‹œ๊ฐ„์‹œ์„ธ.dat";
    using var fs = File.OpenRead(filename);
    var length = fs.Length;
    var itemCount = length / Marshal.SizeOf<์‹ค์‹œ๊ฐ„์‹œ์„ธ>();
    Console.WriteLine($"File Length: {length}");
    Console.WriteLine($"Count: {itemCount}");

    var v = new ์‹ค์‹œ๊ฐ„์‹œ์„ธ();
    //var ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก = new List<์‹ค์‹œ๊ฐ„์‹œ์„ธ>();
    var ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก = new ์‹ค์‹œ๊ฐ„์‹œ์„ธ[itemCount];

    var count = 0;
    var sw = Stopwatch.StartNew();
    while (count < itemCount)
    {
        //var result = ์‹ค์‹œ๊ฐ„์‹œ์„ธ.Read(fs, ref ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก[count]);
        var result = ์‹ค์‹œ๊ฐ„์‹œ์„ธ.Read(fs, ref v);
        if (result == false)
            break;
        //์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก.Add(v);
        ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก[count] = v;

        count++;
    }
    sw.Stop();

    // List<T> : 2571 ms
    // T[], ์Šคํƒ ์ž„์‹œ๊ฐ’ ์ €์žฅ ํ›„ ๋ณต์‚ฌ : 2354 ms
    // T[], ์ง์ ‘ ์ €์žฅ : 2339 ms
    Console.WriteLine($"Elapsed Time: {sw.ElapsedMilliseconds}");
}

/// <summary>
/// MemoryMappedFile๋กœ Span<byte> -> Span<์‹ค์‹œ๊ฐ„์‹œ์„ธ>๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ง์ ‘ ์ ‘๊ทผ
/// </summary>
void ์ž„์˜์‹œ์„ธ๋ชฉ๋ก_์ฝ๊ธฐ2()
{
    var filename = "์‹ค์‹œ๊ฐ„์‹œ์„ธ.dat";
    var fileLength = new FileInfo(filename).Length;

    using var mmf = MemoryMappedFile.CreateFromFile(filename, FileMode.Open);
    using var accessor = mmf.CreateViewAccessor();

    Span<byte> memory;
    unsafe
    {
        byte* ptr = null;
        accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
        ptr += accessor.PointerOffset;

        //memory = new Span<byte>(ptr, (int)accessor.SafeMemoryMappedViewHandle.ByteLength);
        memory = new Span<byte>(ptr, (int)fileLength);
    }


    var ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก = MemoryMarshal.Cast<byte, ์‹ค์‹œ๊ฐ„์‹œ์„ธ2>(memory);
    foreach (var item in ์‹ค์‹œ๊ฐ„์‹œ์„ธ๋ชฉ๋ก)
    {
        ;
    }

    accessor.SafeMemoryMappedViewHandle.ReleasePointer();
}

/// <summary>
/// C#์˜ ๋ฐฐ์—ด์€ ์ฐธ์กฐ์ด๋ฏ€๋กœ, MemoryMappedFile์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๊ณ ์ • ๋ฐฐ์—ด๋กœ ์ ‘๊ทผ
/// </summary>
[StructLayout(LayoutKind.Sequential, Pack = 1)]
unsafe struct ์‹ค์‹œ๊ฐ„์‹œ์„ธ2
{
    public long ์ฒด๊ฒฐ์‹œ๊ฐ;
    public decimal ์ฒด๊ฒฐ๊ฐ€;
    public decimal ์ฒด๊ฒฐ์ˆ˜๋Ÿ‰;
    public fixed int _๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰[4 * 5];
    public fixed int _๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰[4 * 5];
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct ์‹ค์‹œ๊ฐ„์‹œ์„ธ
{
    public long ์ฒด๊ฒฐ์‹œ๊ฐ;
    public decimal ์ฒด๊ฒฐ๊ฐ€;
    public decimal ์ฒด๊ฒฐ์ˆ˜๋Ÿ‰;
    // Marshal.Sizeof ๊ณ„์‚ฐ ์šฉ๋„
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public decimal[] ๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰;
    // Marshal.Sizeof ๊ณ„์‚ฐ ์šฉ๋„
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public decimal[] ๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰;

    public void Write(Stream s)
    {
        s.Write(์ฒด๊ฒฐ์‹œ๊ฐ.AsReadOnlyBytes());
        s.Write(์ฒด๊ฒฐ๊ฐ€.AsReadOnlyBytes());
        s.Write(์ฒด๊ฒฐ์ˆ˜๋Ÿ‰.AsReadOnlyBytes());
        s.Write(๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰.AsReadOnlyBytes());
        s.Write(๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰.AsReadOnlyBytes());
    }

    public static bool Read(Stream s, ref ์‹ค์‹œ๊ฐ„์‹œ์„ธ v)
    {

        var length = s.Read(v.์ฒด๊ฒฐ์‹œ๊ฐ.AsBytes());
        if (length == 0)
            return false;
        
        s.Read(v.์ฒด๊ฒฐ๊ฐ€.AsBytes());
        s.Read(v.์ฒด๊ฒฐ์ˆ˜๋Ÿ‰.AsBytes());
        v.๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰ = new decimal[5];
        s.Read(v.๋งค์ˆ˜ํ˜ธ๊ฐ€์ž”๋Ÿ‰.AsBytes());
        v.๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰ = new decimal[5];
        s.Read(v.๋งค๋„ํ˜ธ๊ฐ€์ž”๋Ÿ‰.AsBytes());

        return true;
    }
}


/// <summary>
/// Span ํ™•์žฅ
/// 
/// MemoryMarshal.Cast<T, byte>(span)์˜ ๊ฒฝ์šฐ MemoryMarshal.AsBytes(span)์„ ๋Œ€์‹  ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
/// </summary>
public static class SpanExtension
{
    public static ReadOnlySpan<byte> AsReadOnlyBytes<T>(this ref T @this)
        where T : struct
    {
        var span = MemoryMarshal.CreateReadOnlySpan(ref @this, 1);
        return MemoryMarshal.Cast<T, byte>(span);
    }

    public static ReadOnlySpan<byte> AsReadOnlyBytes<T>(this T[] @this)
        where T : struct
    {
        return MemoryMarshal.Cast<T, byte>(@this);
    }

    public static Span<byte> AsBytes<T>(this ref T @this)
        where T : struct
    {
        var span = MemoryMarshal.CreateSpan(ref @this, 1);
        return MemoryMarshal.Cast<T, byte>(span);
    }

    public static Span<byte> AsBytes<T>(this T[] @this)
        where T : struct
    {
        return MemoryMarshal.Cast<T, byte>(@this);
    }
}
์ข‹์•„์š” 1

Span<T>์€ ์Šคํƒ์—๋งŒ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์–ด์„œ ์†๋„์™€ ์Šค๋ ˆ๋“œ ์•ˆ์ •์„ฑ์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์—ˆ๋˜ ๋Œ€์‹  ๊ด€๋ฆฌํž™์— ์ ์žฌํ•  ์ˆ˜ ์—†๋Š” ๋‹จ์ ์ด ์žˆ๋Š”๋ฐ, Memory<T>๋Š” struct์œผ๋กœ ๊ด€๋ฆฌํž™์— ์ ์žฌ๊ฐ€ ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์—ฐ์†๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๊ณ„์† ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ํž™ ๋ฉ”๋ชจ๋ฆฌ์— ์žˆ์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์€ ๋‹ค์ค‘ ์Šค๋ ˆ๋“œ์—์„œ Memory<T>์— ์ ‘๊ทผ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ด์•ผ๊ธฐ ์ธ๋ฐ์š”, ๊ทธ๋ž˜์„œ Memory<T>์—๋Š” ์†Œ์œ ์ž/์†Œ๋น„์ž ๋ชจ๋ธ์ด ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค.

Memory ๋ฐ Span ์‚ฌ์šฉ ์ง€์นจ | Microsoft Docs

๊ทœ์น™ #1: ๋™๊ธฐ API์˜ ๊ฒฝ์šฐ ๊ฐ€๋Šฅํ•˜๋ฉด Memory ๋Œ€์‹  Span๋ฅผ ๋งค๊ฐœ ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
๊ทœ์น™ #2: ๋ฒ„ํผ๊ฐ€ ์ฝ๊ธฐ ์ „์šฉ์ด์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ReadOnlySpan ๋˜๋Š” ReadOnlyMemory ์‚ฌ์šฉ ์„ ์ฐธ์กฐํ•˜์„ธ์š”.
๊ทœ์น™ #3: ๋ฉ”์„œ๋“œ๊ฐ€ Memory๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  void๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฐ˜ํ™˜๋œ ํ›„์—๋Š” Memory ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.
๊ทœ์น™ #4: ๋ฉ”์„œ๋“œ๊ฐ€ Memory๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  Task๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฒฝ์šฐ, Task๊ฐ€ ํ„ฐ๋ฏธ๋„ ์ƒํƒœ๋กœ ์ „ํ™˜๋œ ํ›„์—๋Š” Memory ์ธ์Šคํ„ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋ฉ๋‹ˆ๋‹ค.
๊ทœ์น™ #5: ์ƒ์„ฑ์ž๊ฐ€ Memory๋ฅผ ๋งค๊ฐœ ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์ƒ์„ฑ๋œ ๊ฐœ์ฒด์˜ ์ธ์Šคํ„ด์Šค ๋ฉ”์„œ๋“œ๊ฐ€ Memory ์ธ์Šคํ„ด์Šค์˜ ์†Œ๋น„์ž๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.
๊ทœ์น™ #6: ์„ค์ • ๊ฐ€๋Šฅํ•œ Memory ํ˜•์‹์˜ ์†์„ฑ(๋˜๋Š” ๋™๋“ฑํ•œ ์ธ์Šคํ„ด์Šค ๋ฉ”์„œ๋“œ)์ด ํ˜•์‹์— ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๊ฐœ์ฒด์˜ ์ธ์Šคํ„ด์Šค ๋ฉ”์„œ๋“œ๋Š” Memory ์ธ์Šคํ„ด์Šค์˜ ์†Œ๋น„์ž๋กœ ๊ฐ„์ฃผ๋ฉ๋‹ˆ๋‹ค.
๊ทœ์น™ #7: IMemoryOwner ์ฐธ์กฐ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ์ผ์ • ์‹œ์ ์—์„œ ์‚ญ์ œํ•˜๊ฑฐ๋‚˜ ํ•ด๋‹น ์†Œ์œ ๊ถŒ์„ ์ด์ „ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค(๋‘˜ ๋‹ค๋Š” ์•„๋‹˜).
๊ทœ์น™ #8: API ๋…ธ์ถœ ์˜์—ญ์— IMemoryOwner ๋งค๊ฐœ ๋ณ€์ˆ˜๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ์ธ์Šคํ„ด์Šค์˜ ์†Œ์œ ๊ถŒ์„ ํ—ˆ์šฉํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.
๊ทœ์น™ #9: ๋™๊ธฐ P/Invoke ๋ฉ”์„œ๋“œ๋ฅผ ๋ž˜ํ•‘ํ•˜๋Š” ๊ฒฝ์šฐ API๊ฐ€ Span๋ฅผ ๋งค๊ฐœ ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๊ทœ์น™ #10: ๋น„๋™๊ธฐ P/Invoke ๋ฉ”์„œ๋“œ๋ฅผ ๋ž˜ํ•‘ํ•˜๋Š” ๊ฒฝ์šฐ API๊ฐ€ Memory๋ฅผ ๋งค๊ฐœ ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์Œ. ๊ทœ์น™์ด ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ๊ตฐ์š”. ์ด๋Ÿด๋•Œ๋Š” ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ๋™์‹œ์„ฑ์„ ์–ธ์–ด์ ์œผ๋กœ ์ง€์›ํ•˜๋Š” D Language๋‚˜ Rust ์–ธ์–ด๊ฐ€ ๋ถ€๋Ÿฝ๋‹ค๋Š” ์ƒ๊ฐ์„ ํ•ด๋ด…๋‹ˆ๋‹ค.