15

我正在编写适用于一系列数字数据的算法,有时,该系列中的一个值需要为空。但是,由于此应用程序对性能至关重要,因此我避免使用可空类型。我已经对算法进行了性能测试,专门比较了使用可空类型和不可空类型的性能,在最好的情况下,可空类型会慢 2 倍,但通常要差得多。

最常用的数据类型是 double,目前选择的 null 替代方案是 double.NaN。但是我知道这不是 NaN 值的确切预期用途,所以我不确定这是否有任何我无法预见的问题以及最佳实践是什么。

我有兴趣找出以下数据类型的最佳空替代方案:double/float、decimal、DateTime、int/long(尽管其他数据类型非常受欢迎)

编辑:我想我需要澄清我对性能的要求。通过这些算法一次处理大量数字数据需要几个小时。因此,尽管例如 10ms 或 20ms 之间的差异通常是微不足道的,但在这种情况下,它确实会对所用时间产生重大影响。

4

6 回答 6

20

好吧,如果您已经排除了Nullable<T>,那么您将得到域值 - 即您将其视为 null 的幻数。虽然这并不理想,但也并不罕见——例如,许多主框架代码都将DateTime.MinValue其视为 null。这至少使损害远离共同价值观......

编辑以仅突出显示没有 NaN 的地方

因此,在没有 的地方NaN,也许可以使用.MinValue- 但请记住,如果您不小心使用了相同的值,表示相同的数字,会发生什么坏事......

显然,对于您需要的无符号数据.MaxValue(避免零!!!)。

就个人而言,我会尝试Nullable<T>更安全地表达我的意图......也许有一些方法可以优化你的Nullable<T>代码。而且-当您在所有需要的地方检查了幻数时,也许它不会比Nullable<T>?

于 2009-05-18T07:50:26.937 回答
5

在这个特定的边缘情况下,我有点不同意 Gravell:一个 Null 变量被认为是“未定义”,它没有值。因此,无论使用什么来表示都可以:即使是幻数,但是对于幻数,您必须考虑到幻数在将来突然变成“有效”值时总会困扰您。使用 Double.NaN,您不必为此担心:它永远不会成为有效的替身。虽然,您必须考虑到双打序列意义上的 NaN 只能用作“未定义”的标记,但显然,您也不能将其用作序列中的错误代码。

因此,无论使用什么来标记“未定义”:必须在值集的上下文中明确该特定值被认为是“未定义”的值,并且将来不会改变。

如果 Nullable 给您带来太多麻烦,请使用 NaN 或其他任何方法,只要您考虑后果:选择的值表示“未定义”并且将保留。

于 2009-05-18T08:04:42.123 回答
5

我正在开发一个使用 NaN 作为null值的大型项目。我对此并不完全满意-出于与您类似的原因:不知道会出什么问题。到目前为止,我们还没有遇到任何实际问题,但请注意以下几点:

NaN 算术- 虽然在大多数情况下,“NaN 提升”是一件好事,但它可能并不总是您所期望的。

比较- 如果您希望 NaN 比较相等,则值的比较会变得相当昂贵。现在,测试浮点数是否相等并不简单,但是排序 (a < b) 可能会变得非常丑陋,因为 nan 有时需要比正常值更小,有时更大。

代码感染- 我看到很多算术代码需要对 NaN 进行特定处理才能正确。因此,出于性能原因,您最终会得到“接受 NaN 的函数”和“不接受 NaN 的函数”。

其他非有限NaN 是唯一的非有限值。应该牢记...

禁用时,浮点异常不是问题。直到有人启用它们。真实故事:ActiveX 控件中 NaN 的静态初始化。听起来并不可怕,直到您将安装更改为使用 InnoSetup,它使用 Pascal/Delphi(?) 内核,默认情况下启用 FPU 异常。我花了一段时间才弄清楚。

所以,总而言之,没什么大不了的,尽管我不想经常考虑 NaN。


我会尽可能频繁地使用 Nullable 类型,除非它们是(被证明是)性能/资源限制。一种情况可能是带有偶尔 NaN 的大型向量/矩阵,或者默认 NaN 行为正确的大量命名单个值。


或者,您可以将索引向量用于向量和矩阵、标准“稀疏矩阵”实现或单独的布尔/位向量。

于 2009-05-18T08:15:39.980 回答
3

部分答案:

Float 和 Double 提供 NaN(不是数字)。NaN 有点棘手,因为根据规范,NaN != NaN。如果您想知道一个数字是否为 NaN,则需要使用 Double.IsNaN()。

另请参阅二进制浮点和 .NET

于 2009-05-18T08:14:00.030 回答
0

调用 Nullable 的成员或属性之一(装箱)时,可能会发生显着的性能下降。

尝试使用带有双精度 + 布尔值的结构来判断是否指定了值。

于 2009-05-18T10:01:42.403 回答
0

Nullable<T>可以通过定义自己的结构来避免与相关的一些性能下降

struct MaybeValid<T>
{
    public bool isValue;
    public T Value;
}

如果需要,可以定义构造函数或转换运算符 from TtoMaybeValid<T>等,但过度使用这些东西可能会产生次优性能。如果避免不必要的数据复制,暴露字段结构可能会很有效。有些人可能不赞成暴露字段的概念,但它们可以比属性更有效。如果将返回 a 的函数T需要一个类型的变量T来保存其返回值,则使用 aMaybeValid<Foo>只需将要返回的事物的大小增加 4。相比之下,使用 aNullable<Foo>将要求函数首先计算Foo,然后将其副本传递给Nullable<Foo>. 此外,返回一个Nullable<Foo>将要求任何想要使用返回值的代码必须至少将一个额外的副本复制到类型的存储位置(变量或临时),Foo然后才能对其执行任何有用的操作。相比之下,代码可以像使用任何其他变量一样有效地使用Value类型变量的字段。Foo

于 2012-12-22T04:24:27.680 回答