1. 前言

此文章是官方文档的翻译,由于官方文档中文版是机器翻译的,有些部分有疏漏和错误,所以本人进行了翻译供大家学习,如有问题欢迎指正。

参考资料

memory-and-spans --- Microsoft

2. 简介

.NET 包含多个相互关联的类型,它们表示任意内存的连续的强类型区域。 这些方法包括:

  • System.Span<T>

    • 用于访问连续的内存区域
    • 得到该类型的实例:
      • 1个T类型的数组
      • 1个String
      • 1个使用 stackalloc 分配的缓冲区
      • 1个指向非托管内存的指针
    • 实例必须存储在堆栈(stack)上,因此有很对限制
      • 类的字段不能是此类型
      • 不能在异步操作中使用
  • System.ReadOnlySpan<T>

    • Span<T> 结构体的不可变版本
  • System.Memory<T>

    • 连续的内存区域的包装器
    • 实例创建
      • T 类型数组
      • String
      • 内存管理器
      • 实例可以存储在托管堆(managed heap)上,所以它没有 Span<T> 的限制
  • System.ReadOnlyMemory<T>

    • Memory<T> 结构的不可变版本。
  • System.Buffers.MemoryPool<T>

    • 它将强类型内存块从内存池分配给所有者

      • IMemoryOwner<T> 实例可以通过调用 MemoryPool<T>.Rent 从池中租用
      • 通过调用 MemoryPool<T>.Dispose() 将其释放回池中
  • System.Buffers.IMemoryOwner<T>

    • 表示内存块的所有者,管理其生命周期
  • MemoryManager<T>

    • 一个抽象基类,可用于替换 Memory<T> 的实现,以便 Memory<T> 可以由其他类型(如安全句柄(safe handles))提供支持
    • MemoryManager<T> 适用于高级方案。
  • ArraySegment<T>

    • 是数组的包装,对应数组中,从特定索引开始的特定数量的一系列元素
  • System.MemoryExtensions

    • 用于将String、数组和数组段(ArraySegment<T>)转换为 Memory<T> 块的扩展方法集

System.Span<T>System.Memory<T> 及其对应的只读类型被设计为:

  • 避免不必要地复制内存或在托管堆上进行内存分配
  • 通过 Slice 方法或这些类型的的构造函数创建它们, 并不涉及复制底层缓冲(underlying buffers): 只更新相关引用和偏移
    • 形象的说就是,只更新我们可以访问到的内存的位置和范围,而不是将这些内存数据复制出来

备注:

对于早期框架,Span<T>Memory<T>System.Memory NuGet 包中提供。

使用 memory 和 span

  • 由于 memory 和 span 相关类型通常用于在处理 pipeline 中存储数据,因此开发人员在使用 Span<T>Memory<T> 和相关类型时要务必遵循一套最佳做法。 Memory<T>Span<T> 使用准则中介绍了这些最佳做法。

3. Memory<T>和Span<T>使用准则

  • Span<T>ReadOnlySpan<T>

    • 是可由托管或非托管内存提供支持的轻量级内存缓冲区
  • Memory<T> 及其相关类型
    • 由托管和非托管内存提供支持
    • Span<T> 不同,Memory<T> 可以存储在托管堆上

Span<T>Memory<T> 都是可用于 pipeline 的结构化数据的缓冲区。

  • 它们设计的目的是将某些或所有数据有效地传递到 pipeline 中的组件,这些组件可以对其进行处理并修改(可选)缓冲区
  • 由于 Memory<T> 及其相关类型可由多个组件或多个线程访问,因此开发人员必须遵循一些标准使用准则才能生成可靠的代码

3.1. 所有者, 消费者和生命周期管理

由于可以在各个 API 之间传送缓冲区,以及由于缓冲区有时可以从多个线程进行访问,因此请务必考虑生命周期管理。 下面介绍三个核心概念:

  • 所有权:

    • 缓冲区实例的所有者负责生命周期管理,包括当不再使用缓冲区时将其销毁
    • 所有缓冲区都拥有一个所有者
    • 通常,所有者是创建缓冲区或从工厂接收缓冲区的组件
    • 所有权也可以转让;
      • 组件 A 可以将缓冲区的控制权转让给组件 B,此时组件 A 就无法再使用该缓冲区,组件 B 将负责在不再使用缓冲区时将其销毁。
  • 消费:
    • 允许缓冲区实例的消费者通过读取和写入来使用缓冲区实例
    • 缓冲区一次可以拥有一个消费者,除非提供了某些外部同步机制
    • 缓冲区的活跃消费者不一定是缓冲区的所有者
  • 租约:
    • 租约是指允许特定组件在一个时间长度范围内成为缓冲区消费者

以下伪代码示例阐释了这三个概念。 它包括:

  • 实例化类型为 CharMemory<T> 缓冲区的
  • 调用 WriteInt32ToBuffer 方法以将整数的字符串表示形式写入缓冲区
  • 然后调用 DisplayBufferToConsole 方法以显示缓冲区的值。
using System;

class Program
{
// Write 'value' as a human-readable string to the output buffer.
void WriteInt32ToBuffer(int value, Buffer buffer); // Display the contents of the buffer to the console.
void DisplayBufferToConsole(Buffer buffer); // Application code
static void Main()
{
var buffer = CreateBuffer();
try
{
int value = Int32.Parse(Console.ReadLine());
WriteInt32ToBuffer(value, buffer);
DisplayBufferToConsole(buffer);
}
finally
{
buffer.Destroy();
}
}
}
  • 所有者

    • Main 方法创建缓冲区(在此示例中为 Span<T> 实例),因此它是其所有者。 因此,Main 将负责在不再使用缓冲区时将其销毁。
  • 消费者
    • WriteInt32ToBufferDisplayBufferToConsole
    • 一次只能有一个消费者
      • 先是 WriteInt32ToBuffer ,然后是 DisplayBufferToConsole
    • 这两个消费者都不拥有缓冲区
    • 此上下文中的“消费者”并不意味着以只读形式查看缓冲区;如果提供了以读/写形式查看缓冲区的权限,则消费者可以像 WriteInt32ToBuffer 那样修改缓冲区的内容
  • 租约
    • WriteInt32ToBuffer 方法在方法调用的开始时间和方法返回的时间之间会租用(能消费的)缓冲区
    • DisplayBufferToConsole 在执行时会租用缓冲区,方法返回时将解除租用
    • 没有用于租约管理的 API,“租用”是概念性内容

3.2. Memory<T> 和所有者/消费者模型

.NET Core 支持以下两种所有权模型:

  • 支持单个所有权的模型

    • 缓冲区在其整个生存期内拥有单个所有者。
  • 支持所有权转让的模型
    • 缓冲区的所有权可以从其原始所有者(其创建者)转让给其他组件,该组件随后将负责缓冲区的生存期管理
    • 该所有者可以反过来将所有权转让给其他组件等

使用 System.Buffers.IMemoryOwner<T> 接口显式的管理缓冲区的所有权。

  • IMemoryOwner<T> 支持上述这两种所有权模型
  • 具有 IMemoryOwner<T> 引用的组件拥有缓冲区
  • 以下示例使用 IMemoryOwner<T> 实例反映 Memory<T> 缓冲区的所有权。
using System;
using System.Buffers; class Example
{
static void Main()
{
IMemoryOwner<char> owner = MemoryPool<char>.Shared.Rent(); Console.Write("Enter a number: ");
try {
var value = Int32.Parse(Console.ReadLine()); var memory = owner.Memory; WriteInt32ToBuffer(value, memory); DisplayBufferToConsole(owner.Memory.Slice(0, value.ToString().Length));
}
catch (FormatException) {
Console.WriteLine("You did not enter a valid number.");
}
catch (OverflowException) {
Console.WriteLine($"You entered a number less than {Int32.MinValue:N0} or greater than {Int32.MaxValue:N0}.");
}
finally {
owner?.Dispose();
}
} static void WriteInt32ToBuffer(int value, Memory<char> buffer)
{
var strValue = value.ToString(); var span = buffer.Span;
for (int ctr = 0; ctr < strValue.Length; ctr++)
span[ctr] = strValue[ctr];
} static void DisplayBufferToConsole(Memory<char> buffer) =>
Console.WriteLine($"Contents of the buffer: '{buffer}'");
}

也可以使用 using 编写此示例:

using System;
using System.Buffers; class Example
{
static void Main()
{
using (IMemoryOwner<char> owner = MemoryPool<char>.Shared.Rent())
{
Console.Write("Enter a number: ");
try {
var value = Int32.Parse(Console.ReadLine()); var memory = owner.Memory;
WriteInt32ToBuffer(value, memory);
DisplayBufferToConsole(memory.Slice(0, value.ToString().Length));
}
catch (FormatException) {
Console.WriteLine("You did not enter a valid number.");
}
catch (OverflowException) {
Console.WriteLine($"You entered a number less than {Int32.MinValue:N0} or greater than {Int32.MaxValue:N0}.");
}
}
} static void WriteInt32ToBuffer(int value, Memory<char> buffer)
{
var strValue = value.ToString(); var span = buffer.Slice(0, strValue.Length).Span;
strValue.AsSpan().CopyTo(span);
} static void DisplayBufferToConsole(Memory<char> buffer) =>
Console.WriteLine($"Contents of the buffer: '{buffer}'");
}

在此代码中:

  • Main 方法保持对 IMemoryOwner<T> 实例的引用,因此 Main 方法是缓冲区的所有者
  • WriteInt32ToBufferDisplayBufferToConsole 方法接受 ``Memory 参数作为公共 API。 因此,它们是缓冲区的消费者。 并且它们同一时间仅有一个消费者

尽管 WriteInt32ToBuffer 方法用于将数据写入缓冲区,但 DisplayBufferToConsole 方法并不如此。

  • 若要反映此情况,方法参数类型可改为 ReadOnlyMemory<T>

3.3. “缺少所有者” 的Memory<T> 实例

无需使用 IMemoryOwner<T> 即可创建 Memory<T> 实例。 在这种情况下,缓冲区的所有权是隐式的,并且仅支持单所有者模型。 可以通过以下方式达到此目的:

  • 直接调用 Memory<T> 构造函数之一,传入 T[],如下面的示例所示
  • 调用 String.AsMemory 扩展方法以生成 ReadOnlyMemory<char> 实例
using System;

class Example
{
static void Main()
{
Memory<char> memory = new char[64]; Console.Write("Enter a number: ");
var value = Int32.Parse(Console.ReadLine()); WriteInt32ToBuffer(value, memory);
DisplayBufferToConsole(memory);
} static void WriteInt32ToBuffer(int value, Memory<char> buffer)
{
var strValue = value.ToString();
strValue.AsSpan().CopyTo(buffer.Slice(0, strValue.Length).Span);
} static void DisplayBufferToConsole(Memory<char> buffer) =>
Console.WriteLine($"Contents of the buffer: '{buffer}'");
}
  • 最初创建 Memory<T> 实例的方法是缓冲区的隐式所有者。 无法将所有权转让给任何其他组件, 因为没有 IMemoryOwner<T> 实例可用于进行转让

    • 也可以假设运行时的垃圾回收器拥有缓冲区,全部的方法只消费缓冲区

3.4. 使用准则

因为拥有一个内存块,但打算将其传递给多个组件,其中一些组件可能同时在特定的内存块上运行,所以建立使用Memory<T>Span<T>的准则是很必要的,因为:

  • 所有者释放它之后,一个组件还可能会保留对该存储块的引用。
  • 两个组件可能并发的同时在缓冲区上进行操作,从而破坏了缓冲区中的数据。
  • 尽管Span<T>堆栈分配性质优化了性能,而且使Span<T>成为在内存块上运行的首选类型,但它也使Span<T>受到一些主要限制
    • 重要的是要知道何时使用Span<T>以及何时使用 Memory<T>

下面介绍成功使用 Memory<T> 及其相关类型的建议。 除非另有明确说明,否则适用于 Memory<T>Span<T> 的指南也适用于 ReadOnlyMemory<T>ReadOnlySpan<T>

规则 1:对于同步 API,如有可能,请使用 Span<T>(而不是 Memory<T>)作为参数。

Span<T>Memory<T> 更多功能:

  • 可以表示更多种类的连续内存缓冲区
  • Span<T> 还提供比 Memory<T> 更好的性能
  • 无法进行 Span<T>Memory<T> 的转换
  • 可以使用 Memory<T>.Span 属性将 Memory<T> 实例转换为 Span<T>
    • 如果调用方恰好具有 Memory<T> 实例,则它们不管怎样都可以使用 Span<T> 参数调用你的方法

使用类型 Span<T>(而不是类型 Memory<T>)作为方法的参数类型还可以帮助你编写正确的消费方法实现。 你将自动进行编译时检查,以确保不会企图访问此方法租约之外的缓冲区

有时,必须使用 Memory<T> 参数(而不是 Span<T> 参数),即使完全同步也是如此。 所依赖的 API 可能仅接受 Memory<T> 参数。 这没有问题,但当使用同步的 Memory<T> 时,应注意权衡利弊

规则 2:如果缓冲区应为只读,则使用 ReadOnlySpan<T> 或 ReadOnlyMemory<T>

在前面的示例中,DisplayBufferToConsole 方法仅从缓冲区读取数据;它不修改缓冲区的内容。 方法签名应进行修改如下。

void DisplayBufferToConsole(ReadOnlyMemory<char> buffer);

事实上,如果我们结合 规则1 和 规则2 ,我们可以做得更好,并重写方法签名如下:

void DisplayBufferToConsole(ReadOnlySpan<char> buffer);

DisplayBufferToConsole 方法现在几乎适用于每一个能够想到的缓冲区类型:

  • T[]、使用 stackalloc 分配的存储 等等
  • 甚至可以向其直接传递 String

规则 3:如果方法接受 Memory<T> 并返回 void,则在方法返回之后不得使用 Memory<T> 实例。

这与前面提到的“租约”概念相关。 返回 void 的方法对 Memory<T> 实例的租用将在进入该方法时开始,并在退出该方法时结束。 请考虑以下示例,该示例会基于控制台中的输入在循环中调用 Log。

using System;
using System.Buffers; public class Example
{
// implementation provided by third party
static extern void Log(ReadOnlyMemory<char> message); // user code
public static void Main()
{
using (var owner = MemoryPool<char>.Shared.Rent())
{
var memory = owner.Memory;
var span = memory.Span;
while (true)
{
int value = Int32.Parse(Console.ReadLine());
if (value < 0)
return; int numCharsWritten = ToBuffer(value, span);
Log(memory.Slice(0, numCharsWritten));
}
}
} private static int ToBuffer(int value, Span<char> span)
{
string strValue = value.ToString();
int length = strValue.Length;
strValue.AsSpan().CopyTo(span.Slice(0, length));
return length;
}
}

如果 Log 是完全同步的方法,则此代码将按预期运行,因为在任何给定时间只有一个活跃的内存实例消费者。 但是,请想象Log具有此实现。

// !!! INCORRECT IMPLEMENTATION !!!
static void Log(ReadOnlyMemory<char> message)
{
// Run in background so that we don't block the main thread while performing IO.
Task.Run(() =>
{
StreamWriter sw = File.AppendText(@".\input-numbers.dat");
sw.WriteLine(message);
});
}

在此实现中,Log 违反租约,因为它在 return 之后仍尝试在后台使用 Memory<T> 实例。 Main 方法可能会在 Log 尝试从缓冲区进行读取时更改缓冲区数据,这可能导致消费者在使用缓存区数据时数据已经被修改。

有多种方法可解决此问题:

  • Log 方法可以按以下所示,返回 Task,而不是 void。
    // An acceptable implementation.
    static Task Log(ReadOnlyMemory<char> message)
    {
    // Run in the background so that we don't block the main thread while performing IO.
    return Task.Run(() => {
    StreamWriter sw = File.AppendText(@".\input-numbers.dat");
    sw.WriteLine(message);
    sw.Flush();
    });
    }
  • 也可以改为按如下所示实现 Log:
    // An acceptable implementation.
    static void Log(ReadOnlyMemory<char> message)
    {
    string defensiveCopy = message.ToString();
    // Run in the background so that we don't block the main thread while performing IO.
    Task.Run(() => {
    StreamWriter sw = File.AppendText(@".\input-numbers.dat");
    sw.WriteLine(defensiveCopy);
    sw.Flush();
    });
    }

规则 4:如果方法接受 Memory<T> 并返回某个Task,则在Task转换为终止状态之前不得使用 Memory<T> 实例。

这个是 规则3 的异步版本。 以下示例是遵守此规则,按上面例子编写的 Log 方法:

// An acceptable implementation.
static Task Log(ReadOnlyMemory<char> message)
{
// Run in the background so that we don't block the main thread while performing IO.
return Task.Run(() => {
string defensiveCopy = message.ToString();
StreamWriter sw = File.AppendText(@".\input-numbers.dat");
sw.WriteLine(defensiveCopy);
sw.Flush();
});
}

此处的“终止状态”表示任务转换为 completed, faulted, canceled 状态。

此指南适用于返回 TaskTask<TResult>ValueTask<TResult> 或任何类似类型的方法。

规则5:如果构造函数接受Memory <T>作为参数,则假定构造对象上的实例方法是Memory<T>实例的消费者。

请看以下示例:

class OddValueExtractor
{
public OddValueExtractor(ReadOnlyMemory<int> input);
public bool TryReadNextOddValue(out int value);
} void PrintAllOddValues(ReadOnlyMemory<int> input)
{
var extractor = new OddValueExtractor(input);
while (extractor.TryReadNextOddValue(out int value))
{
Console.WriteLine(value);
}
}

此处的 OddValueExtractor 构造函数接受 ReadOnlyMemory<int> 作为构造函数参数,因此构造函数本身是 ReadOnlyMemory<int> 实例的消费者,并且该实例的所有实例方法也是原始 ReadOnlyMemory<int> 实例的消费者。 这意味着 TryReadNextOddValue 消费 ReadOnlyMemory<int> 实例,即使该实例未直接传递到 TryReadNextOddValue 方法。

规则 6:如果一个类型具有可写的 Memory<T> 类型的属性(或等效的实例方法),则假定该对象上的实例方法是 Memory<T> 实例的消费者。

这是 规则5 的变体。之所以存在此规则,是因为假定使用了可写属性或等效方法来捕获并保留输入的 Memory<T> 实例,因此同一对象上的实例方法可以利用捕获的实例。

以下示例触发了此规则:

class Person
{
// Settable property.
public Memory<char> FirstName { get; set; } // alternatively, equivalent "setter" method
public SetFirstName(Memory<char> value); // alternatively, a public settable field
public Memory<char> FirstName;
}

规则 7:如果具有 IMemoryOwner<T> 的引用,则必须在某些时候对其进行处理或转让其所有权(但不同时执行两个操作)。

  • 由于 Memory<T> 实例可能由托管或非托管内存提供支持,因此在对 Memory<T> 实例执行的工作完成之后,所有者必须调用 MemoryPool<T>.Dispose
  • 此外,所有者可能会将 IMemoryOwner<T> 实例的所有权转让给其他组件,同时获取所有权的组件将负责在适当时间调用 MemoryPool<T>.Dispose
  • 调用 Dispose 方法失败可能会导致非托管内存泄漏或其他性能降低问题
  • 此规则也适用于调用工厂方法的代码(如 MemoryPool<T>.Rent)。 调用方将成为工厂生产的 IMemoryOwner<T> 的所有者,并负责在完成后 Dispose 该实例。

规则 8:如果 API 接口中具有 IMemoryOwner<T> 参数,即表示你接受该实例的所有权。

接受此类型的实例表示组件打算获取此实例的所有权。 该组件将负责根据 规则7 进行正确处理。

在方法调用完成后,将 IMemoryOwner<T> 实例的所有权转让给其他组件,之后该组件将不再使用该实例。

重要:

构造函数接受 IMemoryOwner<T> 作为参数的类应实现接口 IDisposable,并且 Dispose 方法中应调用 MemoryPool<T>.Dispose

规则 9:如果要封装同步的 p/invoke 方法,则应接受 Span<T> 作为参数

根据 规则1,Span<T> 通常是用于同步 API 的合规类型。 可以通过 fixed 关键字固定 Span<T> 实例,如下面的示例所示。

using System.Runtime.InteropServices;

[DllImport(...)]
private static extern unsafe int ExportedMethod(byte* pbData, int cbData); public unsafe int ManagedWrapper(Span<byte> data)
{
fixed (byte* pbData = &MemoryMarshal.GetReference(data))
{
int retVal = ExportedMethod(pbData, data.Length); /* error checking retVal goes here */ return retVal;
}
}

在上一示例中,如果输入 span 为空,则 pbData 可以为 Null。 如果 ExportedMethod 方法参数 pbData 不能为 Null,可以按如下示例实现该方法:

public unsafe int ManagedWrapper(Span<byte> data)
{
fixed (byte* pbData = &MemoryMarshal.GetReference(data))
{
byte dummy = 0;
int retVal = ExportedMethod((pbData != null) ? pbData : &dummy, data.Length); /* error checking retVal goes here */ return retVal;
}
}

规则 10:如果要包装异步 p/invoke 方法,则应接受 Memory<T> 作为参数

由于 fixed 关键字不能在异步操作中使用,因此使用 Memory<T>.Pin 方法固定 Memory<T> 实例,无论实例代表的连续内存是哪种类型。 下面的示例演示了如何使用此 API 执行异步 p/invoke 调用。

using System.Runtime.InteropServices;

[UnmanagedFunctionPointer(...)]
private delegate void OnCompletedCallback(IntPtr state, int result); [DllImport(...)]
private static extern unsafe int ExportedAsyncMethod(byte* pbData, int cbData, IntPtr pState, IntPtr lpfnOnCompletedCallback); private static readonly IntPtr _callbackPtr = GetCompletionCallbackPointer(); public unsafe Task<int> ManagedWrapperAsync(Memory<byte> data)
{
// setup
var tcs = new TaskCompletionSource<int>();
var state = new MyCompletedCallbackState
{
Tcs = tcs
};
var pState = (IntPtr)GCHandle.Alloc(state); var memoryHandle = data.Pin();
state.MemoryHandle = memoryHandle; // make the call
int result;
try
{
result = ExportedAsyncMethod((byte*)memoryHandle.Pointer, data.Length, pState, _callbackPtr);
}
catch
{
((GCHandle)pState).Free(); // cleanup since callback won't be invoked
memoryHandle.Dispose();
throw;
} if (result != PENDING)
{
// Operation completed synchronously; invoke callback manually
// for result processing and cleanup.
MyCompletedCallbackImplementation(pState, result);
} return tcs.Task;
} private static void MyCompletedCallbackImplementation(IntPtr state, int result)
{
GCHandle handle = (GCHandle)state;
var actualState = (MyCompletedCallbackState)(handle.Target);
handle.Free();
actualState.MemoryHandle.Dispose(); /* error checking result goes here */ if (error)
{
actualState.Tcs.SetException(...);
}
else
{
actualState.Tcs.SetResult(result);
}
} private static IntPtr GetCompletionCallbackPointer()
{
OnCompletedCallback callback = MyCompletedCallbackImplementation;
GCHandle.Alloc(callback); // keep alive for lifetime of application
return Marshal.GetFunctionPointerForDelegate(callback);
} private class MyCompletedCallbackState
{
public TaskCompletionSource<int> Tcs;
public MemoryHandle MemoryHandle;
}

注:

Memory<T>.Pin 方法返回内存句柄,且垃圾回收器将不会移动此处内存,直到释放该方法返回的 MemoryHandle 对象为止。这使您可以检索和使用该内存地址。

内存包装类 Memory 和 Span 相关类型的更多相关文章

  1. 【C# IO 操作 】内存包装类 Memory <T>和 Span<T> 相关类型

    简介 .NET 包含多个相互关联的类型,它们表示任意内存的连续的强类型区域. 这些方法包括: System.Span<T> 用于访问连续的内存区域 得到该类型的实例: 1个T类型的数组 1 ...

  2. [译]C# 7系列,Part 10: Span<T> and universal memory management Span<T>和统一内存管理

    原文:https://blogs.msdn.microsoft.com/mazhou/2018/03/25/c-7-series-part-10-spant-and-universal-memory- ...

  3. Android 内存管理 &Memory Leak & OOM 分析

    转载博客:http://blog.csdn.net/vshuang/article/details/39647167 1.Android 进程管理&内存 Android主要应用在嵌入式设备当中 ...

  4. (译)内存沉思:多个名称相关的神秘的SQL Server内存消耗者。

    原文出处:https://blogs.msdn.microsoft.com/sqlmeditation/2013/01/01/memory-meditation-the-mysterious-sql- ...

  5. 从五大结构体,带你掌握鸿蒙轻内核动态内存Dynamic Memory

    摘要:本文带领大家一起剖析了鸿蒙轻内核的动态内存模块的源代码,包含动态内存的结构体.动态内存池初始化.动态内存申请.释放等. 本文分享自华为云社区<鸿蒙轻内核M核源码分析系列九 动态内存Dyna ...

  6. 内存泄漏(Memory Leak)

    什么情况下会导致内存泄露(Memory Leak)? Android 的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M.因此我们所能利用 的内存空间是有限的.如果我们的 ...

  7. linux内核剖析(十一)进程间通信之-共享内存Shared Memory

    共享内存 共享内存是进程间通信中最简单的方式之一. 共享内存是系统出于多个进程之间通讯的考虑,而预留的的一块内存区. 共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程 ...

  8. 内存泄漏 Memory Leaks 内存优化 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  9. sql server 性能调优 资源等待之内存瓶颈的三种等待类型

    原文:sql server 性能调优 资源等待之内存瓶颈的三种等待类型 一.概述 这篇介绍Stolen内存相关的主要三种等待类型以及对应的waittype编号,CMEMTHREAD(0x00B9),S ...

随机推荐

  1. Language Guide (proto3) | proto3 语言指南(九)Oneof结构

    Oneof - Oneof结构 如果消息包含多个字段,并且最多只能同时设置一个字段,则可以使用oneof功能强制执行此行为并节省内存. oneof字段与常规字段类似,但oneof共享内存中的所有字段除 ...

  2. Javascript 基础知识整理

    Javascript的作用 表单验证,减轻服务器压力 添加页面动画效果 动态更改页面内容 Ajax网络请求(异步加载数据) -它属于前端的核心,主要用来控制和重新调整DOM,通过修改DOM结构,从而达 ...

  3. Spark Dataset DataFrame空值null,NaN判断和处理

    Spark Dataset DataFrame空值null,NaN判断和处理 import org.apache.spark.sql.SparkSession import org.apache.sp ...

  4. Mysql,Oracle与Java字段类型映射关系

    Mysql,Oracle与Java字段类型映射关系 参考相关博文 MySQL/Oracle字段类型 Java字段类型 最大长度 BIT java.lang.Boolean 1 BLOB java.la ...

  5. 复制虚拟机,链接网络问题:没有找到合适的设备:没有找到可用于链接System eth0 的

    http://my.oschina.net/coolfire368/blog/292742 1./etc/udev/rules.d/70-persistent-net.rules 修改也成,修改时留下 ...

  6. OpenStack (neutron 网络服务)

    neutron介绍 提供 OpenStack 虚拟网络服务,也是 OpenStack 重要的核心模块之一,该模块最开始是 Nova 的一部分,叫 nova-network,后来从 Nova 中分离出来 ...

  7. docker第一日学习总结

    查看当前所有的镜像 docker images 查看当前运行的容器 docker ps 一般容器分为后台驻留和闪退(ubuntu\busybox等)两种,对于后台驻留的,我们如果想进入这个容器(前提是 ...

  8. Birkhoff-von Neumann Crossbar 光交换网络的调度方案

    Birkhoff-von Neumann Crossbar 光交换网络的调度方案 ​ This is a summary aimed at looking for "high perform ...

  9. 纯js添加类

    1.el.setAttribute('class','abc'); <!DOCTYPE HTML><HTML><HEAD><meta charset=&quo ...

  10. AtCoder - agc043_a 和 POJ - 2336 dp

    题意: 给你一个n行m列由'#'和'.'构成的矩阵,你需要从(1,1)点走到(n,m)点,你每次只能向右或者向下走,且只能走'.'的位置. 你可以执行操作改变矩阵: 你可以选取两个点,r0,c0;r1 ...