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

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

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

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

有帮助吗?

解决方案

好吧,如果你已经排除了 Nullable<T>, ,您剩下域值 - 即您将其视为 null 的神奇数字。虽然这不是 理想的, ,这也并不罕见 - 例如,许多主要框架代码处理 DateTime.MinValue 与 null 相同。这至少使损害远离共同价值观......

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

那么哪里没有 NaN, ,也许使用 .MinValue - 但请记住,如果你不小心,会发生什么邪恶的事情 使用 相同的值意味着相同的数字......

显然对于未签名的数据,您需要 .MaxValue (避免零!!!)。

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

其他提示

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

所以无论用什么来标记“未定义”:在值集的上下文中必须明确该特定值被视为“未定义”的值并且将来不会更改。

如果 Nullable 给你带来太多麻烦,就使用 NaN,或者其他什么,只要你考虑后果:选择的值代表“未定义”,并且该值将保留。

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

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

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

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

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

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

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


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


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

部分答案:

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

也可以看看 二进制浮点和.NET.

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

尝试使用带有 double + 布尔值的结构来告知是否指定了该值。

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

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

如果需要,可以定义构造函数或转换运算符 TMaybeValid<T>, , ETC。但过度使用这些东西可能会产生次优的性能。如果避免不必要的数据复制,公开字段结构可能会非常高效。有些人可能不赞成暴露场的概念,但它们的效率比属性要高得多。如果一个函数将返回一个 T 需要有一个类型的变量 T 保存其返回值,使用 MaybeValid<Foo> 只需将要返回的内容的大小增加 4 即可。相比之下,使用 Nullable<Foo> 需要该函数首先计算 Foo 然后将其副本传递给构造函数 Nullable<Foo>. 。进一步,返回一个 Nullable<Foo> 将要求任何想要使用返回值的代码必须至少向类型的存储位置(变量或临时)制作一份额外的副本 Foo 在它可以用它做任何有用的事情之前。相比之下,代码可以使用 Value 类型变量的字段 Foo 与任何其他变量一样有效。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top