gpt4 book ai didi

C# 代码优化导致 Interlocked.Exchange() 出现问题

转载 作者:可可西里 更新时间:2023-11-01 09:07:00 25 4
gpt4 key购买 nike

我在一些代码中遇到了一个令人沮丧的问题,并且不知道为什么会出现这个问题。

//
// .NET FRAMEWORK v4.6.2 Console App

static void Main( string[] args )
{
var list = new List<string>{ "aa", "bbb", "cccccc", "dddddddd", "eeeeeeeeeeeeeeee", "fffff", "gg" };

foreach( var item in list )
{
Progress( item );
}
}

private static int _cursorLeft = -1;
private static int _cursorTop = -1;
public static void Progress( string value = null )
{
lock( Console.Out )
{
if( !string.IsNullOrEmpty( value ) )
{
Console.Write( value );
var left = Console.CursorLeft;
var top = Console.CursorTop;
Interlocked.Exchange( ref _cursorLeft, Console.CursorLeft );
Interlocked.Exchange( ref _cursorTop, Console.CursorTop );
Console.WriteLine();
Console.WriteLine( "Left: {0} _ {1}", _cursorLeft, left );
Console.WriteLine( "Top: {0} _ {1}", _cursorTop, top );
}
}
}

没有 代码优化 的情况下运行时,结果符合预期。 _cursorLeftleft 至于 _cursorToptop 是相等的。

aa
Left: 2 _ 2
Top: 0 _ 0
bbb
Left: 3 _ 3
Top: 3 _ 3

但是当我代码优化运行它时,_cursorLeft_cursorTop 两个值都变得奇怪:

aa
Left: -65534 _ 2
Top: -65536 _ 0
bb
Left: -65533 _ 3
Top: -65533 _ 3

我发现了 2 个解决方法:

  1. _cursorLeft_cursorTop 设置为 0 而不是 -1
  2. 让 Interlocked.Exchange 分别取左边的值。 置顶

因为解决方法 #1 不符合我的需求,所以我最终使用了解决方法 #2:

private static int _cursorLeft = -1;
private static int _cursorTop = -1;
public static void Progress( string value = null )
{
lock( Console.Out )
{
if( !string.IsNullOrEmpty( value ) )
{
Console.Write( value );

// OLD - does NOT work!
//Interlocked.Exchange( ref _cursorLeft, Console.CursorLeft );
//Interlocked.Exchange( ref _cursorTop, Console.CursorTop );

// NEW - works great!
var left = Console.CursorLeft;
var top = Console.CursorTop;
Interlocked.Exchange( ref _cursorLeft, left ); // new
Interlocked.Exchange( ref _cursorTop, top ); // new
}
}
}

但是这种奇怪的行为是从哪里来的呢?
是否有更好的解决方法/解决方案?


[Matthew Watson 编辑:添加简化的重现:]

class Program
{
static void Main()
{
int actual = -1;
Interlocked.Exchange(ref actual, Test.AlwaysReturnsZero);
Console.WriteLine("Actual value: {0}, Expected 0", actual);
}
}

static class Test
{
static short zero;
public static int AlwaysReturnsZero => zero;
}

[由我编辑:]
我想到了另一个更短的例子:

class Program
{
private static int _intToExchange = -1;
private static short _innerShort = 2;

// [MethodImpl(MethodImplOptions.NoOptimization)]
static void Main( string[] args )
{
var oldValue = Interlocked.Exchange(ref _intToExchange, _innerShort);
Console.WriteLine( "It was: {0}", oldValue );
Console.WriteLine( "It is: {0}", _intToExchange );
Console.WriteLine( "Expected: {0}", _innerShort );
}
}

除非您不使用优化 或将_intToExchange 设置为ushort 范围内的值,否则您将无法识别问题。

最佳答案

您正确地诊断了问题,这是一个优化器错误。它特定于 64 位抖动(又名 RyuJIT),它在 VS2015 中首次发布。您只能通过查看生成的机器代码才能看到它。在我的机器上看起来像这样:

00000135  movsx       rcx,word ptr [rbp-7Ch]       ; Cursor.Left
0000013a mov r8,7FF9B92D4754h ; ref _cursorLeft
00000144 xchg cx,word ptr [r8] ; Interlocked.Exchange

XCHG 指令错误,它使用了 16 位操作数(cx 和字 ptr)。但是变量类型需要 32 位操作数。结果,变量的高 16 位仍然为 0xffff,使整个值变为负数。

表征此错误有点棘手,不容易隔离。获取内联的 Cursor.Left 属性 getter 似乎有助于触发错误,在幕后它访问 16 位字段。显然足以以某种方式使优化器决定 16 位交换将完成工作。以及您的解决方法代码解决它的原因,使用 32 位变量存储 Cursor.Left/Top 属性将优化器撞到一个好的代码路径。

这种情况下的解决方法非常简单,除了您找到的那个之外,您根本不需要 Interlocked,因为 lock 语句已经使代码线程安全。请在 connect.microsoft.com 上报告错误,如果你不想花时间告诉我,我会处理的。

关于C# 代码优化导致 Interlocked.Exchange() 出现问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43206775/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com