对于包含无效值的数据集,我应该使用浮点的 NaN 还是浮点 + bool?

Posted

技术标签:

【中文标题】对于包含无效值的数据集,我应该使用浮点的 NaN 还是浮点 + bool?【英文标题】:Should I use floating point's NaN, or floating point + bool for a data set that contains invalid values? 【发布时间】:2012-03-23 08:53:07 【问题描述】:

我有大量数据要处理,需要对每个数据集进行数学密集型运算。其中大部分类似于图像处理。但是,由于此数据是直接从物理设备读取的,因此许多像素值可能是无效的。

这使得 NaN 表示非数字值和在算术运算上传播的属性非常引人注目。但是,它似乎也需要关闭一些优化,例如 gcc 的 -ffast-math,而且我们需要是跨平台的。我们当前的设计使用了一个简单的结构,其中包含一个浮点值和一个表示有效性的布尔值。

虽然看起来NaN was designed with this use in mind, 其他人认为是more trouble than it is worth。有没有人根据他们对 IEEE754 的更深入体验以及性能方面的建议提出建议?

【问题讨论】:

设计你的程序,使这个实现细节仅限于一个小组件;然后用 NaN 写一个,用 bool 写一个并比较。 我有两个项目的经验。在一个中它工作得很好,因为我们总是使用一个 NaN。另一方面,事情失控了,因为人们开始编码其他东西。 “未初始化”、“不可用”、“超出范围”等。 @PlasmaHH:后者显然是错误的(这就是为什么你有+INF-INF)。 @MSalters:显然。但这并不能阻止一些人这样做。每当您无法完全控制时,您还必须考虑其他人会如何理解您的想法。 在 Intel 和 AMD 上进行测试(未来可能还包括 CPU)。 NaN 的处理是一种特殊情况,不同实现的速度不一致。 【参考方案1】:

BRIEF:为了最严格的可移植性,不要使用 NaN。使用单独的有效位。例如。像Valid这样的模板。但是,如果您知道您只能在 IEEE 754-2008 机器上运行,而不是 IEEE 754-1985(见下文),那么您可能会侥幸成功。

为了提高性能,在您可以访问的大多数机器上不使用 NaN 可能会更快。然而,我参与了几台机器上的 FP 硬件设计,这些机器正在提高 NaN 处理性能,因此有一种趋势是使 NaN 更快,特别是信号 NaN 应该很快就会比 Valid 更快。

详情:

并非所有浮点格式都有 NaN。并非所有系统都使用 IEEE 浮点。 IBM 十六进制浮点仍然可以在某些机器上找到 - 实际上是系统,因为 IBM 现在在更新的机器上支持 IEEE FP。

此外,IEEE 浮点本身在 IEEE 754-1985 中存在与 NaN 的兼容性问题。例如,参见***http://en.wikipedia.org/wiki/NaN:

仅限 1985 年的原始 IEEE 754 标准 (IEEE 754-1985) 描述了二进制浮点格式,并没有说明如何 信号/安静状态将被标记。在实践中,最 有效位的有效位确定 NaN 是否为 信号或安静。两种不同的实现,相反 意义,结果。 * 大多数处理器(包括 Intel/AMD x86-32/x86-64 系列、Motorola 68000 系列、AIM PowerPC 系列、ARM 系列和 Sun SPARC 系列)将信号/静音位设置为 如果 NaN 是安静的,则非零,如果 NaN 正在发出信号,则为零。 因此,在这些处理器上,该位表示“is_quiet”标志。 * 在 PA-RISC 和 MIPS 处理器生成的 NaN 中,如果 NaN 是安静的,则信号/安静位为零,如果 NaN 正在发出信号。因此,在这些处理器上,该位表示 'is_signaling' 标志。

如果您的代码可以在较旧的 HP 机器或当前的 MIPS 机器(在嵌入式系统中普遍存在)上运行,您不应该依赖固定的 NaN 编码,而应该有一个依赖机器的 #ifdef 用于您的特殊NaN。

IEEE 754-2008 对 NaN 编码进行了标准化,因此变得越来越好。这取决于您的市场。

至于性能:许多机器在执行涉及 SNaN(必须捕获)和 QNaN(不需要捕获,即可能很快)的计算时,本质上会陷入陷阱,或者以其他方式在性能上出现重大问题 - 并且正如我们所说,这在某些机器上变得越来越快。)

我可以自信地说,在较旧的机器上,尤其是较旧的 Intel 机器上,如果您关心性能,您不想使用 NaN。例如。 http://www.cygnus-software.com/papers/x86andinfinity.html 说“Intel Pentium 4 处理无穷大、NAN 和非规范化非常糟糕......如果你编写的代码以每个时钟周期一个的速率添加浮点数,然后将无穷大作为输入,性能下降。很多。大量。... NAN 甚至更慢。与 NAN 相加大约需要 930 个周期。... 非正规测量有点棘手。"

得到图片?使用 NaN 比执行普通浮点运算慢近 1000 倍?在这种情况下,几乎可以保证使用像 Valid 这样的模板会更快。

但是,请参阅“Pentium 4”?那是一个非常古老的网页。多年来,像我这样的人一直在说“QNaN 应该更快”,并且已经慢慢站稳脚跟。

最近(2009 年),Microsoft 表示 http://connect.microsoft.com/VisualStudio/feedback/details/498934/big-performance-penalty-for-checking-for-nans-or-infinity“如果您对包含大量 NaN 或无穷大的 double 数组进行数学运算,则会有一个数量级的性能损失。”

如果我有冲动,我可能会去一些机器上运行一个微基准测试。但是你应该得到图片。

这应该会改变,因为让 QNaN 变得快速并不难。但这一直是一个先有鸡还是先有蛋的问题:像我一起工作的硬件人员说“没有人使用 NaN,所以我们不会让它们快”,而软件人员不使用 NaN,因为它们很慢。不过,潮流正在慢慢改变。

哎呀,如果您使用 gcc 并希望获得最佳性能,您可以打开“-ffinite-math-only”之类的优化... 。”大多数编译器也是如此。

顺便说一句,您可以像我一样在 Google 上搜索“NaN 性能浮点数”并自己查看参考。和/或运行您自己的微基准测试。

最后,我一直假设您正在使用类似的模板

template<typename T> class Valid 
    ...
    bool valid;
    T value;
    ...
;

我喜欢这样的模板,因为它们不仅可以为FP带来“有效性跟踪”,还可以为整数(Valid)等带来“有效性跟踪”。

但是,他们可能会付出很大的代价。这些操作可能并不比在旧机器上处理 NaN 贵多少,但数据密度可能真的很差。 sizeof(Valid) 有时可能是 2*sizeof(float)。这种糟糕的密度对性能的影响可能远大于所涉及的操作。

顺便说一句,您应该考虑模板特化,以便 Valid 如果 NaN 可用且快速,则使用 NaN,否则使用有效位。

template <> class Valid<float>  
    float value; 
    bool is_valid()  
        return value != my_special_NaN; 
     

等等

无论如何,您最好使用尽可能少的有效位,并将它们打包到其他地方,而不是 Valid 接近值。例如。

struct Point  float x, y, z; ;
Valid<Point> pt;

优于(密度方面)

struct Point_with_Valid_Coords  Valid<float> x, y, z; ;

除非您使用的是 NaN - 或其他一些特殊编码。

还有

struct Point_with_Valid_Coords  float x, y, z; bool valid_x, valid_y, valid_z ;

介于两者之间 - 但您必须自己完成所有代码。

顺便说一句,我一直假设您使用的是 C++。如果 FORTRAN 或 Java ...

底线:单独的有效位可能更快、更便携。

但是 NaN 处理速度正在加快,很快有一天会足够好

顺便说一句,我的偏好是:创建一个有效的模板。然后您可以将它用于所有数据类型。如果有帮助,请将其专门用于 NaN。虽然我的生活让事情变得更快,但恕我直言,让代码干净通常更重要。

【讨论】:

我有一个指定 c++ 的标签,但也许我也应该把它放在文本中。我需要支持的最重要的平台是 Intel Atom 处理器,其次是 x86、x86-64,然后其他一切都很好。我还没有找到很多关于 atom 如何使用 NaN 执行的文档。我可能需要做一个小基准。 做基准测试并检查。但如果 Atom 能快速处理 NaN,我会(非常高兴地)感到惊讶。我在 20 个月前才离开英特尔。 即使在同一个 CPU 上,处理这些 NaN 的硬件也可能不同,具有非常不同的性能特征(FPU 与 SSE,后者应该至少一样好,甚至可能更好)。时至今日,英特尔 i5 FPU 仍然很糟糕。【参考方案2】:

如果无效数据非常普遍,那么您当然会浪费大量时间来处理这些数据。如果无效数据足够普遍,那么运行某种仅包含有效数据的稀疏数据结构可能会更好。如果不是很常见,当然可以保留一个稀疏数据结构,其中数据无效。这样你就不会为每个值浪费一个布尔值。但也许记忆对你来说不是问题......

如果您正在执行诸如将两个可能无效的数据条目相乘之类的操作,我知道使用 NaN 而不是检查两个变量以查看它们是否有效并在结果中设置相同的标志是令人信服的。

您需要多便携?您是否需要能够将其移植到仅支持定点的架构?如果是这样,我认为你的选择很明确。

就我个人而言,如果事实证明它要快得多,我只会使用 NaN。否则,如果您明确处理无效数据,我会说代码会变得更加清晰。

【讨论】:

我想说,现在在我们的项目中,便携式意味着 linux、windows、osx、x86、x86-64、arm。我们肯定有一个无法避免的浮点要求。【参考方案3】:

由于浮点数来自设备,它们的范围可能有限。您可以使用其他特殊数字而不是 NaN 来表示缺少数据,例如1e37。该解决方案是便携式的。我不知道你是否比使用 bool 标志更方便。

【讨论】:

"此解决方案是可移植的。" 仅当所选数字在所有底层 FP 硬件上完全可表示时,这使得该解决方案在定义上不是便携。 这并不重要。如果将 1E37 存储为 1E37+1,然后与 1E37 进行比较,则第二个数字会四舍五入为 1E37+1,两个四舍五入的值比较相等。 IE。该解决方案可移植到所有确定性 FP 表示,包括但不限于 IEEE754。 确实,只要您在某些情况下不通过1E37/2 + 1E37/2 或其他类似的方式生成它

以上是关于对于包含无效值的数据集,我应该使用浮点的 NaN 还是浮点 + bool?的主要内容,如果未能解决你的问题,请参考以下文章

Pandas crosstab() 函数与包含 NaN 值的数据框的混淆行为

如何将包含 float 和 nan 值的 Dataframe 转换为 datetime python?

SQL Server 错误无效的列名“NaN”

无效参数:通过编辑标签数量在摘要直方图中显示 Nan

使用 NaN 绘制/创建数据集的散点图

如何在 Python 中对包含 TRUE/FALSE 值的数据集执行聚类?