我一直在阅读很多关于.NET中浮点确定性的内容,即确保具有相同输入的相同代码将在不同的机器上提供相同的结果.由于.NET缺少Java的fpstrict和MSVC的fp:strict等选项,因此似乎一致认为使用纯托管代码无法绕过这个问题.C#游戏AI Wars已经决定使用定点数学,但这是一个麻烦的解决方案.
主要问题似乎是CLR允许中间结果存在于FPU寄存器中,这些寄存器具有比类型的原始精度更高的精度,从而导致不可预测的更高精度结果.CLR工程师David Notario撰写的MSDN文章解释了以下内容:
请注意,对于当前规范,它仍然是提供"可预测性"的语言选择.在每次FP操作之后,该语言可以插入conv.r4或conv.r8指令以获得"可预测的"行为. 显然,这非常昂贵,不同的语言有不同的妥协.例如,C#什么都不做,如果你想缩小,你必须手动插入(浮点)和(双)强制转换.
这表明,只需为每个表达式和计算浮点数的子表达式插入显式强制转换,就可以实现浮点确定性.有人可能会在float周围编写一个包装器类型来自动执行此任务.这将是一个简单而理想的解决方案!
然而,其他评论表明它并非如此简单.Eric Lippert最近表示(强调我的):
在某些版本的运行时中,显式转换为float会产生与不这样做不同的结果.当你明确地转换为float时,C#编译器会给运行时提供一个提示,说"如果碰巧使用这个优化,就把这个东西从超高精度模式中取出".
这对运行时的"提示"是什么?C#规范是否规定显式转换为float会导致在IL中插入conv.r4?CLR规范是否规定conv.r4指令会使值缩小到其原始大小?只有当这两者都成立时,我们才能依靠显式转换来提供浮点"可预测性",正如David Notario所解释的那样.
最后,即使我们确实能够将所有中间结果强制转换为类型的原生大小,这是否足以保证跨机器的可重复性,还是有其他因素如FPU/SSE运行时设置?
在这种情况下,使用泛型与接口有什么实际优势:
void MyMethod(IFoo f)
{
}
void MyMethod<T>(T f) : where T : IFoo
{
}
Run Code Online (Sandbox Code Playgroud)
即你能做什么MyMethod<T>,你不能在非通用版本?我正在寻找一个实际的例子,我知道理论上的差异是什么.
我知道,在MyMethod<T>T 中,T将是具体的类型,但是我只能在方法体内将它用作IFoo.那么什么才是真正的优势呢?
免责声明:我知道有两个关于const-correctness有用的问题,但是,没有人讨论过如何在C++中使用const-correctness 而不是其他编程语言.此外,我对这些问题的答案不满意.
我现在使用了一些编程语言,在C++中让我烦恼的一件事就是const-correctness的概念.在Java,C#,Python,Ruby,Visual Basic等中没有这样的概念,这似乎对C++非常具体.
在你推荐我使用C++ FAQ Lite之前,我已经阅读了它,但这并不能说服我.完全有效,可靠的程序一直用Python编写,没有const关键字或等价物.在Java和C#中,对象可以声明为final(或const),但是没有const成员函数或const函数参数.如果函数不需要修改对象,则它可以采用仅提供对象的读访问权的接口.该技术同样可以在C++中使用.在我工作的两个真实的C++系统上,几乎没有使用const,一切都运行正常.因此,对于让const污染代码库的有用性,我还远没有卖掉.
我想知道在C++中它是什么让const成为必要,而不是其他编程语言.
到目前为止,我只看到了必须使用const的一种情况:
#include <iostream>
struct Vector2 {
int X;
int Y;
};
void display(/* const */ Vector2& vect) {
std::cout << vect.X << " " << vect.Y << std::endl;
}
int main() {
display(Vector2());
}
Run Code Online (Sandbox Code Playgroud)
Visual Studio接受使用const注释掉的编译,但是使用警告C4239时,使用非标准扩展.所以,如果你想要传递临时代码,避免副本和保持标准兼容的语法简洁,你必须通过const引用,不管它.不过,这更像是一个怪癖,而不是一个根本原因.
否则,实际上不存在必须使用const的情况,除非与使用const的其他代码连接.在我看来,康斯特似乎不是一个自以为是的瘟疫,它蔓延到它接触到的一切:
const在C++中工作的原因是因为你可以把它丢弃.如果你不能把它扔掉,那么你的世界就会糟透了.如果声明一个采用const Bla的方法,则可以将它传递给非const Bla.但如果是相反的方式你不能.如果声明一个采用非const Bla的方法,则不能将它传递给const Bla.所以现在你被卡住了.所以你逐渐需要一个不是const的所有东西的const版本,你最终得到了一个阴影世界.在C++中,你可以使用它,因为与C++中的任何东西一样,无论你是否想要这个检查,它都是纯粹可选的.如果你不喜欢它,你可以打破常量.
Anders Hejlsberg(C#架构师),CLR设计选择
我在fp:strict模式下使用MSVC进入了C库的超越数学函数的汇编.他们似乎都遵循相同的模式,这就是发生的事情sin.
首先,从名为"disp_pentium4.inc"的文件中有一个调度例程.它检查变量___use_sse2_mathfcns是否已设置; 如果是这样,电话__sin_pentium4,否则打电话__sin_default.
__sin_pentium4 (在"sin_pentium4.asm"中)首先将参数从x87 fpu传送到xmm0寄存器,使用SSE2指令执行计算,然后将结果加载回fpu.
__sin_default(在"sin.asm"中)将变量保存在x87堆栈上并简单地调用fsin.
因此,在这两种情况下,操作数推x87堆栈上和它返回为好,使之透明的来电,但如果___use_sse2_mathfcns被定义,在SSE2而不是的x87实际执行的操作.
这种行为是对我来说很有趣,因为的x87超越函数是臭名昭著的具有取决于实施的行为稍有不同,而SSE2的给定代码块要经常给重现的结果.
有没有办法确定在编译或运行时是否会使用SSE2代码路径?我不是很精通编写程序集,所以如果这涉及编写任何程序集,那么代码示例将不胜感激.
我正在寻找在Atom编辑器中创建,构建,运行和调试第一个Rust应用程序的分步说明.
到目前为止,我已经安装了Atom编辑器,安装了软件包language-rust,并且毫无结果地试图在编辑器中找到一个选项来创建Rust"项目"或"包",而我的google-fu失败了.
免责声明:我在Windows上,非常熟悉Visual Studio,不熟悉类似*nix的开发环境,但渴望尝试Rust.
我正在尝试编写一个提供参数并调用函数的泛型方法,如下所示:
class MyClass {
public int Method(float arg) => 0;
}
TResult Call<T1, TResult>(Func<T1, TResult> func) =>
func(default(T1));
void Main()
{
var m = new MyClass();
var r1 = Call<float, int>(m.Method);
var r2 = Call(m.Method); // CS0411
}
Run Code Online (Sandbox Code Playgroud)
最后一行无法使用CS0411进行编译.是否有任何解决方法可以在这里使用类型推断?
使用案例:使用AutoFixture生成函数调用参数.
在.NET中测试浮点数的性能时,我偶然发现了一个奇怪的情况:对于某些值,乘法似乎比正常慢.以下是测试用例:
using System;
using System.Diagnostics;
namespace NumericPerfTestCSharp {
class Program {
static void Main() {
Benchmark(() => float32Multiply(0.1f), "\nfloat32Multiply(0.1f)");
Benchmark(() => float32Multiply(0.9f), "\nfloat32Multiply(0.9f)");
Benchmark(() => float32Multiply(0.99f), "\nfloat32Multiply(0.99f)");
Benchmark(() => float32Multiply(0.999f), "\nfloat32Multiply(0.999f)");
Benchmark(() => float32Multiply(1f), "\nfloat32Multiply(1f)");
}
static void float32Multiply(float param) {
float n = 1000f;
for (int i = 0; i < 1000000; ++i) {
n = n * param;
}
// Write result to prevent the compiler from optimizing the entire method away
Console.Write(n);
}
static void Benchmark(Action func, …Run Code Online (Sandbox Code Playgroud) 我注意到我的应用程序内存耗尽比它应该更快.它创建了许多每个几兆字节的字节数组.但是,当我查看vmmap的内存使用情况时,似乎.NET为每个缓冲区分配的内容远远超过了需要.确切地说,当分配9兆字节的缓冲区时,.NET会创建一个16兆字节的堆.剩余的7兆字节不能用于创建另一个9兆字节的缓冲区,因此.NET会创建另外的16兆字节.所以每个9MB缓冲区浪费7MB的地址空间!
这是一个示例程序,它在32位.NET 4中分配106个缓冲区后抛出OutOfMemoryException:
using System.Collections.Generic;
namespace CSharpMemoryAllocationTest
{
class Program
{
static void Main(string[] args)
{
var buffers = new List<byte[]>();
for (int i = 0; i < 130; ++i)
{
buffers.Add(new byte[9 * 1000 * 1024]);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,您可以将阵列的大小增加到16*1000*1024,并在内存不足之前仍然分配相同数量的缓冲区.
VMMap显示了这个:

另请注意,托管堆的总大小与总通信大小之间几乎有100%的差异.(1737MB vs 946MB).
有没有一种可靠的方法解决.NET上的这个问题,即我可以强制运行时分配不超过我实际需要的,或者可能用于几个连续缓冲区的更大的托管堆?
以下C#函数:
T ResultOfFunc<T>(Func<T> f)
{
return f();
}
Run Code Online (Sandbox Code Playgroud)
毫不奇怪地汇编到这个:
IL_0000: ldarg.1
IL_0001: callvirt 05 00 00 0A
IL_0006: ret
Run Code Online (Sandbox Code Playgroud)
但是等效的F#功能:
let resultOfFunc func = func()
Run Code Online (Sandbox Code Playgroud)
汇编到这个:
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldnull
IL_0003: tail.
IL_0005: callvirt 04 00 00 0A
IL_000A: ret
Run Code Online (Sandbox Code Playgroud)
(两者都处于发布模式).开头有一个额外的小窍门,我并不太好奇,但有趣的是附加ldnull和tail.说明.
我的猜测(可能是错误的)是ldnull必要的,如果函数是void这样,它仍然返回something(unit),但这并不能解释tail.指令的目的是什么.如果函数确实在栈上推送了某些东西会发生什么呢?是不是它会被一个不会弹出的额外null所困?
众所周知,Enumerable.SelectMany将序列序列展平为单个序列.如果我们想要一种能够使序列序列序列变平,等等递归的方法怎么办?
我很快就提出了一个实现使用ICollection<T>,即急切评估,但我仍然在摸索如何使用yield关键字进行懒惰评估.
static List<T> Flatten<T>(IEnumerable list) {
var rv = new List<T>();
InnerFlatten(list, rv);
return rv;
}
static void InnerFlatten<T>(IEnumerable list, ICollection<T> acc) {
foreach (var elem in list) {
var collection = elem as IEnumerable;
if (collection != null) {
InnerFlatten(collection, acc);
}
else {
acc.Add((T)elem);
}
}
}
Run Code Online (Sandbox Code Playgroud)
有任何想法吗?任何.NET语言欢迎中的示例.
c# ×7
.net ×4
c++ ×2
f# ×2
generics ×2
atom-editor ×1
cil ×1
const ×1
ieee-754 ×1
performance ×1
polymorphism ×1
recursion ×1
rust ×1
sse ×1
visual-c++ ×1
windows ×1