当前位置: 首页 > 编程语言 > C#

为什么这段代码不能证明读-写非素数?分享

时间:2023-04-11 02:57:31 C#

这段代码为什么不能证明读写的非素数?读这道题,我想测试一下我是否可以证明读取和写入不保证此类操作原始性的类型的非素数。私人静态双_d;[STAThread]staticvoidMain(){newThread(KeepMutating).Start();继续阅读();}privatestaticvoidKeepReading(){while(true){doubledCopy=_d;//在发布中:if(...)throw...Debug.Assert(dCopy==0D||dCopy==double.MaxValue);//永不失败}}privatestaticvoidKeepMutating(){Randomrand=newRandom();while(true){_d=rand.Next(2)==0?0D:双倍最大价值;令我惊讶的是,即使在执行了整整三分钟之后,该断言仍拒绝失败。是什么赋予了?测试不正确。测试的特定时间性质使得断言不太可能/不可能失败。概率如此之低,以至于我必须运行更长时间的测试才能触发它。CLR提供了比C#规范更强的原始保证。我的操作系统/硬件提供比CLR更强的保证。还有什么?当然,我不打算依赖规范未明确保证的任何行为,但我想更深入地研究这个问题。仅供参考,我在Debug和Release上运行了这个(if(..)throw(Debug.Assert更改为if(..)throw)在两个不同的环境中)配置文件:Windows764位+.NET3.5SP1WindowsXP32位+.NET2.0编辑:为了排除JohnKugelman评论“调试器不是Schrodinger安全”的可能性,我添加了行someList.Add(dCopy);到KeepReading方法并验证该列表不是从缓存中看到的单个过时值。编辑:根据DanBryant的建议:使用long而不是double几乎可以立即中断它。您可以尝试通过CHESS运行它,看看它是否可以强制交错来破坏测试。如果您查看x86反汇编(从调试器中可见),您可能还会看到抖动是否正在生成原始保留指令。编辑:我继续进行反汇编(强制以x86为目标)。相关路线为:doubledCopy=_d;00000039fldqwordptrds:[00511650h]0000003ffstpqwordptr[ebp-40h]_d=rand.Next(2)==0?0D:双倍最大价值;00000054movecx,dwordptr[ebp-3Ch]00000057movedx,20000005cmoveax,dwordptr[ecx]0000005emoveax,dwordptr[eax+28h]00000061calldwordptr[eax+1Ch]000000ptr[movEBP-48H],EAX00000067CMPDWORDPTR[EBP-48H],00000006BJE00000079000000790000006DNOP0000006EFLDQWORDPTRPTRDS:[002423D8H]000000000074FSTPQWIREPRT[EBP-PRTR[ebptr[ebptr]ebp-50h]0000007efldqwordptr[ebp-50h]00000081fstpqwordptrds:[00159E78h]它使用单个fstpqwordptr在两种情况下执行写操作。我的猜测是IntelCPU保证了这个操作的原始性,尽管我还没有找到任何支持这个的文档。任何可以确认这一点的x86专家?更新:如果使用Int64,它会按预期失败,它在x86CPU上使用32位寄存器而不是特殊的FPU寄存器。你可以在下面看到它:Int64dCopy=_d;00000042moveax,dwordptrds:[001A9E78h]00000047movedx,dwordptrds:[001A9E7Ch]0000004dmovdwordptr[ebp-40h],eax00000050movdwordptr[ebp-3Ch],edx更新:我很好奇如果如果我强制非双字段对齐到内存中的8个字节,我会失败,所以我将这段代码放在一起:[StructLayout(LayoutKind.Explicit)]privatestructTest{[FieldOffset(0)]publicdouble_d1;[FieldOffset(4)]publicdouble_d2;}私有静态测试_test;[STAThread]staticvoidMain(){newThread(KeepMutating).Start();staticvoidKeepReading(){while(true){doubledummy=_test._d1;双dCopy=_test._d2;//在发布中:if(...)throw...Debug。断言(dCopy==0D||dCopy==double.MaxValue);//永不失败}}privatestaticvoidKeepMutating(){Randomrand=newRandom();while(true){_test._d2=rand.Next(2)==0?0D:双倍最大价值;没有失败,生成的x86指令和之前基本一样:doubledummy=_test._d1;0000003emoveax,dwordptrds:[03A75B20h]00000043fldqwordptr[eax+4]00000046fstpqwordptr[ebp-40h]双dCopy=_test._d2;00000049moveax,dwordptrds:[03A75B20h]0000004efldqwordptr[eax+8]00000051fstpqwordptr[ebp-48h]我尝试将_d1和_d2换成dCopy/set并尝试使用FieldOffset2。所有生成相同基本指令(上面有不同的偏移量)并且几秒钟后没有失败(可能尝试数十亿次)鉴于这些结果,我谨慎地相信至少英特尔x86CPU为双加载/存储操作提供原语,对齐与否。允许编译器优化对_d的重复读取。据他所知,只要静态分析你的循环,_d永远不会改变。这意味着它可以缓存值并且永远不会重新读取该字段。为防止这种情况,您需要同步对_d的访问(即用锁定语句包围它),或将_d标记为易变的。使它易失告诉编译器它的值可能随时改变,因此它从不缓存该值。不幸的是(或幸运的是),您不能将双字段标记为易变的,这恰恰是因为您要测试的重点-原语无法访问双字段!同步访问_d会迫使编译器重新读取该值,但它也会破坏测试。那好吧!您可以尝试删除'dCopy=_d'并在断言中使用_d。这样两个线程同时读/写同一个变量。您当前的版本制作了_d的副本,它创建了一个新实例,所有实例都在同一个线程中,这是一个线程安全的操作:http://msdn.microsoft.com/en-us/library/system.double.aspxAll这种类型的成员是线程安全的。看似修改实例状态的成员实际上返回一个用新值初始化的新实例。与任何其他类型一样,对包含此类实例的共享变量的读取和写入必须由锁保护才能线程安全。但是,如果两个线程都在读/写同一个变量实例,那么:http://msdn.microsoft.com/en-us/library/system.double.aspx在所有硬件平台上分配这种类型的实例是不可能的线程安全,因为此实例的二进制表示可能太大而无法在单个原语操作中分配。因此,如果两个线程都在读/写同一个变量实例,则需要一个锁来保护它(或Interlocked.Read/Increment/Exchange.,不确定它是否适用于双打)编辑正如其他人所指出的那样,双打是一个IntelCPU读/写上的原语操作。但是,如果程序是为X86编译的并使用64位整数数据类型,则操作不是原语。如下程序所示。用double替换Int64似乎可行。PublicConstThreadCountAsInteger=2PublicthrdsWrite()AsThreading.Thread=NewThreading.Thread(ThreadCount-1){}PublicthrdsRead()AsThreading.Thread=NewThreading.Thread(ThreadCount-1){}PublicdAsInt64_SubMain()ForiAsInteger=0到thrdsWrite.Length-1thrdsWrite(i)=NewThreading.Thread(AddressOfWrite)thrdsWrite(i).SetApartmentState(Threading.ApartmentState.STA)thrdsWrite(i).IsBackground=TruethrdsWrite(i).Start()thrdsRead(i)=NewThreading.Thread(AddressOfRead)thrdsRead(i).SetApartmentState(Threading.ApartmentState.STA)thrdsRead(i).IsBackground=TruethrdsRead(i).开始()NextConsole.ReadKey()EndSubPublicSubWrite()DimrndAsNewRandom(DateTime.Now.Millisecond)WhileTrued=If(rnd.Next(2)=0,0,Int64.MaxValue)结束时EndSubPublicSubRead()WhileTrueDimdcAsInt64=dIf(dc0)And(dcInt64.MaxValue)ThenConsole.WriteLine(dc)EndIfEndWhileEndSubIMO的正确答案是#5。双是8个字节长。内存接口是64位=每个块每个时钟8字节(即,对于双通道内存,它变为16字节)。还有CPU缓存。在我的机器上,缓存行是64字节,在所有CPU上都是8的倍数。正如上面评论中提到的,即使CPU在32位模式下运行,也只需要1条指令来加载和存储double多变的。这就是为什么只要您的双变量对齐(我怀疑公共语言运行时虚拟机为您做的),双重读写就是原语。以上是C#学习教程:这段代码为什么不能证明读/写的非素数?如果所有分享的内容对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: