“fixed”到底是做什么的?

3

我有一段代码,看起来像这样,在不安全的上下文中:

 ValidatePartialOperation(array, startingOffset, runLength);

 fixed (double* _op = array)
 {
     double* op = _op + startingOffset;
     callSomething(op, runLength);
 }

我在几个不同的地方都有这样的复制粘贴。但是我不喜欢在多个地方使用这种验证和指针算术,所以我想将逻辑组合成一个单行,看起来像:

 double* op = preCall(array, startingOffset, runLength);
 callSomething(op, runLength);
 postCall(array);

甚至更好的是:
 using (double* op = preCall(array, startingOffset, runLength))
 {
     callSomething(op, runLength);
 }

但无论发生什么情况,我都不能承受从“修复”版本中损失性能。

我现在的计划是模仿修复语句正在执行的操作,但我实际上不知道那是什么。可能是一些带有固定操作的try-catch块吧?

5个回答

5
当然,您可以这样做。不过我不知道它是否能满足您的性能需求,您应该进行测量并找出答案。
要在不使用 "fixed" 语句的情况下固定一个数组并获取指针,您可以使用 "GCHandle" 对象。调用 "GCHandle.Alloc" 并传入数组以及 "pinned" 句柄类型,您将得到一个 "IntPtr",可以安全地将其转换为指针。直到在 "GCHandle" 上调用 "Free" 之前,数组将保持固定,因此确保不要丢失对 "GCHandle" 的跟踪。只要该句柄存在,垃圾收集器的表现就会受到影响。
但是,我的建议是使用 "fixed" 语句。那就是它的目的所在。

我刚刚尝试过这个,代码看起来非常棒,但似乎增加了显著的开销(慢了约50% ,或者在我的测试用例中额外增加了1秒,而正常情况下只需要3秒)。在剖析器中查看,似乎 GCHandle.Free 导致了大部分的额外开销。那么我猜 'fixed' 使用了不同的机制,对吗? - Jay Lemmon
@JayLemmon:在IL中,“fixed”会发出特殊的代码,直接告诉运行时本地变量是指针引用。至于JIT如何处理,我就不清楚了。 - Eric Lippert
嗯...看着这个例子中的IL代码:https://dev59.com/SF3Ua4cB1Zd3GeqP9iPo,似乎实际的固定发生在一个特别标记的本地变量保留地址上。大概GC聪明到足以查找这些特殊的本地变量并通过这种方式进行固定。由于我不能直接在C#中使用这些机制,除了使用fixed之外,似乎我只能使用那种语法 :/ 可能有一种方法可以黑客一些IL并注入它,但我不想那么努力。 - Jay Lemmon
@JayLemmon 为什么你不能像 Eric 提到的那样使用 GCHandle.Alloc() 呢?请问。 - svick
@svick:因为显然自己分配句柄会更慢。 - Eric Lippert

4

为了未来的读者,我认为我已经完全弄清楚了,虽然有一些有根据的猜测。

基本上看起来 fixed 是调用 C++/CLI pin_ptr 的 C# 方法。这些是必须作为栈上的局部变量声明的特殊变量。它们似乎没有直接与 GC 通信,因此它们非常轻量级。相反,当 GC 运行时,它足够聪明,可以扫描所有活动线程的调用堆栈,并检查是否有任何函数的变量是这些特殊 pinning 指针。如果是这样,无论它们指向什么,都会被标记为固定,不会在垃圾回收期间在内存中移动。

相比之下,GCHandle.Alloc(obj, GCHandleType.Pinned) 实际上与 GC 通信,并将对象放入不移动对象列表中。这意味着每次使用 GCHandle.Alloc 然后是 Free,都会向列表添加和删除元素并执行操作。Pinning 指针是完全被动的机制,不需要执行额外的工作。这也解释了为什么无法更改固定指针的值:它指向的托管对象只有在固定指针指向它时才被保证固定。如果固定指针指向不同的对象,那么现在就会固定 。如果将固定指针设置为 null,即使是短暂的一刻,它也不再固定任何东西,并且您迄今为止所做的所有指针数学都将无效。

这解释了当我尝试切换到 GCHandles 时性能下降的原因。因此,fixed 不仅是最佳工具,而且是唯一的工具,至少在性能很重要时如此。即使语法有时很笨拙。


0

使用fixed可以防止垃圾回收器在内存压缩期间重新定位您的对象。如果不使用它,则您的对象可能随时移动,指针将变为无效。


你如何在不使用“fixed”的情况下获取指针呢? - svick

0
不,没有这样的事情。就像MSDN所说的那样:Fixed语句可以防止垃圾回收器移动内存,因此您的固定指针可以保持有效,直到它需要为止。如果您正在使用某些非托管资源,则这一点非常重要。据我目前的了解,您无法用任何usingtry/catch/finally或其他任何东西来替代它。

0

fixed 将变量固定,防止垃圾回收器在 fixed 块的持续时间内移动对象。

根据 文档

C# 编译器只允许在 fixed 语句中将指针分配给托管变量。

因此,在您的代码中似乎无法避免使用 fixed 语句。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接