You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

异步方法中如何避免调用Span<T>.ToArray()以减少内存分配?

异步改造后Span的无内存分配解决方案

问题出在哪

  1. Span没法在async里用:Span依赖栈或固定内存,异步方法切换状态时栈上下文会丢失,保留Span引用存在安全风险。
  2. ToArray()导致频繁分配:原代码每次循环都把Span转成字节数组,持续产生堆内存分配,GC压力大。
  3. 装箱性能坑:ISector接口让值类型SectorRawMode1被装箱,不仅拖慢性能,还让GetSpan拿到的Span指向临时栈副本,异步场景下容易出问题。

怎么改

1. 换个思路:直接写流,不返回Span

把原来返回Span的委托改成直接负责往目标流写数据,跳过转数组的步骤,直接用Stream.WriteAsync(ReadOnlyMemory<byte>)完成无分配写入。

2. 避免装箱:用泛型返回具体值类型

把ReadSectorAsync改成泛型方法,返回具体的扇区struct类型,彻底消除接口带来的装箱开销,同时保证内存引用安全。

3. 异步读用内存池:消除临时数组分配

把ISector.ReadAsync里的临时byte[]换成ArrayPool<byte>复用,彻底干掉异步读取时的内存分配。


修改后的完整代码

1. DiscExtensions类重构

using ISO9660.Logical;
using System.Buffers;

namespace ISO9660.Physical;

public static class DiscExtensions
{
    public static async Task ReadFileRawAsync<T>(this Disc disc, IsoFileSystemEntryFile file, Stream stream)
        where T : struct, ISector
    {
        await ReadFileAsync(disc, file, stream, ReadFileRawAsync<T>);
    }

    public static async Task ReadFileUserAsync<T>(this Disc disc, IsoFileSystemEntryFile file, Stream stream)
        where T : struct, ISector
    {
        await ReadFileAsync(disc, file, stream, ReadFileUserAsync<T>);
    }

    private static async Task ReadFileAsync<T>(Disc disc, IsoFileSystemEntryFile file, Stream stream, Func<IsoFileSystemEntryFile, Stream, T, ValueTask> handler)
        where T : struct, ISector
    {
        int position = (int)file.Position;

        Track track = disc.Tracks.FirstOrDefault(s => position >= s.Position)
                      ?? throw new InvalidOperationException("Failed to determine track for file.");

        int sectors = (int)Math.Ceiling((double)file.Length / track.Sector.GetUserDataLength());

        for (int i = position; i < position + sectors; i++)
        {
            // 读取具体值类型扇区,避免装箱
            T sector = await track.ReadSectorAsync<T>(i);
            // 直接写入流,无中间内存分配
            await handler(file, stream, sector);
        }
    }

    private static ValueTask ReadFileRawAsync<T>(IsoFileSystemEntryFile file, Stream stream, T sector)
        where T : struct, ISector
    {
        ReadOnlyMemory<byte> data = sector.GetData();
        return stream.WriteAsync(data);
    }

    private static ValueTask ReadFileUserAsync<T>(IsoFileSystemEntryFile file, Stream stream, T sector)
        where T : struct, ISector
    {
        ReadOnlyMemory<byte> data = sector.GetUserData();
        int remaining = (int)(file.Length - stream.Length);
        int writeSize = Math.Min(remaining, data.Length);
        return stream.WriteAsync(data[..writeSize]);
    }
}

2. ISector接口调整

using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace ISO9660.Physical;

public interface ISector
{
    int Length { get; }

    ReadOnlyMemory<byte> GetData();

    ReadOnlyMemory<byte> GetUserData();

    int GetUserDataLength();

    public static ReadOnlyMemory<byte> GetMemory<T>(scoped ref T sector, int start, int length)
        where T : struct, ISector
    {
        var span = MemoryMarshal.CreateSpan(ref sector, 1);
        var bytes = MemoryMarshal.AsBytes(span);
        // 将堆内存Span转为Memory,无额外分配
        return MemoryMarshal.AsMemory(bytes.Slice(start, length));
    }
    
    public static T Read<T>(Stream stream)
        where T : struct, ISector
    {
        var size = Unsafe.SizeOf<T>();
        Span<byte> span = stackalloc byte[size];
        stream.ReadExactly(span);
        return MemoryMarshal.Read<T>(span);
    }

    public static async Task<T> ReadAsync<T>(Stream stream)
        where T : struct, ISector
    {
        var size = Unsafe.SizeOf<T>();
        // 从内存池借缓冲区,用完归还
        byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
        try
        {
            await stream.ReadExactlyAsync(buffer.AsMemory(0, size));
            return MemoryMarshal.Read<T>(buffer.AsSpan(0, size));
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(buffer);
        }
    }
}

3. SectorRawMode1实现更新

using JetBrains.Annotations;

namespace ISO9660.Physical;

public unsafe struct SectorRawMode1 : ISector
{
    private const int UserDataLength = 2048;
    private const int UserDataPosition = 16;

    [UsedImplicitly]
    public fixed byte Sync[12];

    [UsedImplicitly]
    public fixed byte Header[4];

    [UsedImplicitly]
    public fixed byte UserData[UserDataLength];

    [UsedImplicitly]
    public fixed byte Edc[4];

    [UsedImplicitly]
    public fixed byte Intermediate[8];

    [UsedImplicitly]
    public fixed byte PParity[172];

    [UsedImplicitly]
    public fixed byte QParity[104];

    public readonly int Length => 2352;

    public ReadOnlyMemory<byte> GetData()
    {
        return ISector.GetMemory(ref this, 0, Length);
    }

    public ReadOnlyMemory<byte> GetUserData()
    {
        return ISector.GetMemory(ref this, UserDataPosition, UserDataLength);
    }

    public readonly int GetUserDataLength()
    {
        return UserDataLength;
    }
}

4. Track类的ReadSectorAsync改泛型

public async Task<T> ReadSectorAsync<T>(int index) where T : struct, ISector
{
    // 这里添加你的扇区定位逻辑,比如移动流到指定位置
    // _stream.Seek(...);
    return await ISector.ReadAsync<T>(_stream);
}

为啥这么改

  • 彻底消除内存分配:直接用ReadOnlyMemory写流,跳过ToArray()的堆分配;异步读用内存池复用缓冲区,无临时数组分配。
  • 干掉装箱开销:泛型返回具体值类型,避免ISector接口带来的装箱操作,性能提升明显。
  • 异步场景安全:用ReadOnlyMemory替代Span<T>,适配异步状态机的堆内存存储,不会出现内存引用失效的问题。

内容的提问来源于stack exchange,提问作者aybe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 02:19:54