Memory<T> 和 Span<T>

C# - Span 全面介绍:探索 .NET 新增的重要组成部分
Memory 和 Span 是 C# 中的高效内存管理工具,特别适用于处理大数据集或需要避免不必要的内存分配的场景。它们提供了高性能、低分配的方式来操作内存。相比传统的数组和集合,它们有以下几个关键好处:

零拷贝:Span 和 Memory 允许对现有内存块进行切片操作,而不会产生新的数组或复制数据,减少了内存分配和垃圾回收的开销。

堆栈分配:Span 可以在堆栈上分配,而不是堆上分配,这意味着它们更轻量且性能更好。

高效内存切片:你可以轻松地从现有的内存块中创建子视图,便于操作和管理数据。

示例:Memory 和 Span 的实际应用
假设我们有一个方法,它需要处理一个包含多个数据块的文件,而每个数据块需要单独进行处理。通常我们可能会用数组来操作,但每次都需要创建新的数组或切片,可能会导致性能下降。

使用传统数组:

void ProcessData(byte[] data, int chunkSize) {
    for (int i = 0; i < data.Length; i += chunkSize) {
        byte[] chunk = new byte[chunkSize];
        Array.Copy(data, i, chunk, 0, chunkSize);  // 拷贝数据
        ProcessChunk(chunk);
    }
}

void ProcessChunk(byte[] chunk) {
    // 对 chunk 进行处理
} 

这种方法在每次处理数据块时都会创建新的数组并拷贝数据,这在处理大数据集时效率低下
在这个版本中,Slice 方法仅创建现有数据的视图,不进行数据复制,显著提高了性能。

使用 Memory 进行异步操作:
在处理异步操作时,Memory 比 Span 更合适,因为 Span 是堆栈分配的,不能在异步方法中使用。

async Task ProcessDataAsync(Memory<byte> data, int chunkSize) {
    for (int i = 0; i < data.Length; i += chunkSize) {
        Memory<byte> chunk = data.Slice(i, chunkSize);  // 切片,无拷贝
        await ProcessChunkAsync(chunk);
    }
}

async Task ProcessChunkAsync(Memory<byte> chunk) {
    // 对 chunk 进行异步处理
}

在这里,Memory 可以跨越异步边界使用,适用于异步场景,而不会发生内存复制。

总结:

Span:适合同步操作,提供高效的内存切片操作。
Memory:适合异步操作,提供同样的高效切片功能,但可以在异步方法中使用。
通过 Memory 和 Span,我们可以避免不必要的内存分配和数据复制,提升程序的性能和内存利用率
拓展:
ReadOnlySpan to Memory
[MethodImpl(MethodImplOptions.AggressiveInlining)] 是一个用于 C# 编程的标签,它建议编译器对标记的方法进行内联扩展。内联扩展是一种优化技术,编译器会将方法的代码直接插入到调用该方法的地方,从而减少函数调用的开销
fixed (T* ptr = &MemoryMarshal.GetReference(span)) 是一种在 C# 中使用指针和内存管理的用法。具体来说:

fixed 关键字:用于固定内存中的对象,防止垃圾回收器在代码执行期间移动它们。这样可以确保指针指向的内存地址在 fixed 代码块内是稳定的。

T* ptr:声明一个指向类型 T 的指针 ptr。

&MemoryMarshal.GetReference(span):获取 span 的第一个元素的引用,并取其地址。MemoryMarshal.GetReference 方法返回 span 第一个元素的引用,即使 span 为空

posted @ 2024-09-08 21:29  Josen_Earth  阅读(163)  评论(0)    收藏  举报