为什么R语言中的逻辑(布尔)需要4个字节?

34
对于一个逻辑值向量,为什么R要分配4字节,当位向量可以每个元素只消耗1位?(有关示例,请参见this question。)现在,我意识到R还可以容纳NA值的存储,但是否可以使用附加的位向量来实现?换句话说,为什么不只使用一种便宜的二位数据结构就足够了呢?值得一提的是,Matlab对逻辑值使用1字节,尽管它不支持NA值。我不确定MathWorks为什么不满足于一位功能,更不用说二位数据结构了,但他们有时候会用花哨的营销策略...[我会在这个问题中利用“两位”最大化价值。;-)]

更新1。我认为提供的架构原因有些道理,但感觉有点事后诸葛亮。

我还没有检查32位或16位R,看看它们的逻辑大小是多少-这可能支持这个想法。

R内部手册来看,逻辑向量(LGLSXP)和整数(INTSXP)在每个平台上都是32位。我可以理解一个独立于字长的整数通用大小。同样,逻辑存储似乎也与字长无关。但是它太大了。 :)

此外,如果字长参数如此强大,那么对我来说看到Matlab(我认为它是32位Matlab)只使用1个字节似乎很奇怪 - 我想知道MathWorks是否选择更节省内存的编程复杂性和一些其他开销来找到子字对象的权衡。

此外,还有其他选择可供选择:如Brian Diggs所指出的,bit包可以使位向量变得更加容易,这对于上面问题中的问题非常有用(通过从4字节的logical值转换为位向量,任务的速度提高了8-10倍)。尽管访问内存的速度很重要,但移动30-31个额外的无信息位(从信息理论的角度来看)是浪费的。例如,可以使用像在这里描述整数使用的内存技巧 - 获取一堆额外的内存(V单元),然后在位级别处理事物(类似于bit())。为什么不这样做并为长向量保存30位(1个值,1个NA)?
就我受布尔影响的RAM和计算速度而言,我打算切换到使用bit,但这是因为在某些情况下,空间节省达到了97%。 :)
我认为这个问题的答案将来自于对R设计或内部的更深入了解的人。最好的例子是Matlab使用不同大小的逻辑,内存字大小在那种情况下不会是答案。Python可能与R类似。

另一种相关的表述方式可能是:为什么在所有平台上都是4个字节?(CHARSXP通常较小,这样做行不行?为什么不更小,只是超额分配?)(更新:使用CHARSXP的想法可能是虚假的,因为对CHARSXP的操作不如整数那么有用,例如sum。使用与字符相同的数据结构可能会节省空间,但会限制可以对其进行操作的现有方法。更合适的考虑是使用更小的整数,如下所讨论的。)


更新2. 这里有一些非常好的、启发性的答案,特别是在如何实现检索和处理布尔值以提高速度和编程效率方面。我认为Tommy的答案尤其可信,因为它似乎源于两个前提,这就解释了为什么在R中会出现这种情况:

  1. 为了支持逻辑向量的加法(注意,“逻辑”是由编程语言/环境定义的,与布尔值不同),最好重用添加整数的代码。在R的情况下,整数占用4个字节。在Matlab的情况下,最小的整数是1个字节(即int8)。这解释了为什么为逻辑变量编写不同的内容会很麻烦。[对于不熟悉R的人,它支持许多逻辑运算,例如sum(myVector)mean(myVector)等。]

  2. 遗留支持使得做一些与R和S-Plus长期以来所做的不同的事情变得非常困难。此外,我怀疑在S、S-Plus和R的早期,如果有人需要大量进行布尔运算,他们会使用C语言而不是尝试在R中使用逻辑运算。

其他答案对于如何实现更好的布尔处理非常棒 - 不要天真地认为可以获取任何单个位:最有效的方法是加载一个字,然后屏蔽不感兴趣的位,正如Dervall所描述的那样。如果为R编写专门的布尔操作代码(例如我在交叉制表问题上的问题),这是非常有用的建议:不要迭代位,而是在字级别上工作。

感谢所有人提供非常全面的答案和见解。


1
处理单个比特比处理单个字节要困难得多。 - SLaks
@SLaks 能否详细说明一下?我觉得我理解你的意思,但对于非常大的向量来说,这会浪费很多空间和计算资源。 - Iterator
1
如果你处理位,你在32位系统上最多只能使用512Mb的内存。 - James
3个回答

17

我略懂R和S-Plus,我认为R最有可能是为了与S-Plus兼容才这样做的,而S-Plus最有可能之所以这样做是因为这是最简单的方法...

基本上,逻辑向量与整数向量相同,所以sum和其他用于整数的算法在逻辑向量上几乎没有改变。

在64位S-Plus中,整数是64位的,因此逻辑向量也是如此! 这是每个逻辑值8字节...

@Iterator当然正确,逻辑向量应该用更紧凑的形式表示。 由于已经有一个1字节的raw向量类型,因此似乎非常简单,将其用于逻辑值。 而且每个值的2位当然会更好 - 我可能会将它们保持为两个单独的位向量(TRUE / FALSE和NA / Valid),如果没有NA,则NA位向量可以为NULL ...

总之,这基本上是一场梦想,因为有许多RAPI包(使用R C / FORTRAN API的软件包)存在,这些包将无法正常运行。


2
每个逻辑值8字节...请在你下次要这样做之前警告我,让我准备好氨水。 :) 顺便问一下,“RAPI”的含义是什么?我猜你是指R的C接口,对吗? - Iterator
是的,8字节的布尔类型是可恶的,而且 R 可能仍会复制它... 是的,我说的 RAPI 是指 R API,这是连接 C/FORTRAN 和 R 的接口。 - Tommy
这个传统的想法非常有道理。除了重复使用sum来进行整数加法之外,我倾向于相信这可能可以解释事情。事实上,另一个问题是这是R中提供的最小数值类型,而Matlab具有1字节整数。 - Iterator

16

我完全不了解R语言,但我怀疑原因与C语言相同,因为加载等于处理器本地字长的大小数据速度更快。

加载单个位会很慢,特别是从位域中加载。因为你必须屏蔽掉不适用于您特定查询的位。对于整个字,您只需将其加载到寄存器中即可完成。由于大小差异通常不是问题,默认实现是使用字长变量。如果用户想要其他东西,总是可以选择手动执行所需的位移操作。

关于打包,至少在某些处理器上,从非对齐地址读取会导致故障。因此,虽然您可能声明一个仅包含一个byte的结构,周围包围着两个int,但是byte的大小可能会填充为4个字节。再次强调,我不知道R语言的情况,但出于性能原因,行为可能是相同的。

在数组中寻址单个字节要更复杂,比如说你有一个数组bitfield,并且想要寻址其中的第x位,那么代码将是这样的:

bit b = (bitfield[x/8] >> (x % 8)) & 1

要获取请求的位的值,可以获得0或1。与从布尔数组进行直接数组寻址相比,该方法可以获得编号为x的值:bool a = array[x]


谢谢,这很有趣,还有其他反馈。但是,4个字节的空间浪费太大了。我理解位寻址的差异,但是什么能够证明4个字节比1个字节更合理,例如字符或短整型?在我的另一个问题中,通过从4字节逻辑转换为位向量,任务的执行速度快了很多,因为移动数据需要很长时间。我有一种感觉,R开发人员做出了权衡决策,我们还没有意识到。 - Iterator
在编程中,字符的大小很重要。由于它们通常代表更多的数据,因此保持其大小很小非常重要。在某些特定应用程序中,位向量可能会由于引用局部性等原因而获得改进的性能,但这些情况可能很少见。通常情况下,您希望在本地使用布尔值,在这种情况下,四个字节的变量速度更快。 - Dervall
这是一个非常有用的答案。虽然我只能选择一个,但似乎Tommy从上下文/历史的角度给出了最适合R中事物为什么会是这样的答案。然而,您的答案对于速度和易用性问题提供了非常有用的指导,这在进入布尔处理的实现时更有用,对于未来的专业代码非常感激。 - Iterator
1
如果您实际尝试在紧凑的位向量上执行一些真正的操作,相比于“每个值一个int”,通常会发现紧凑的位向量在性能方面占优势。在当前的处理器上,内存访问是缓慢的,位操作是快速的...而且您通常可以同时操作多个位(例如AND操作)。 - Tommy

10
其他答案已经提到了逻辑向量实现与整数占用相同空间的(可能)架构原因。我想指出bit包,它实现了一个一位(没有NA)的逻辑向量。

这是真的。我应该更加精确:在我的理解中,位向量是布尔值的“朴素”(即基线)数据结构。我很好奇为什么有人会消耗31个额外的位。更精确的答案还将指出逻辑和布尔不是同一件事情:例如R的logical()支持NA。但是我可以通过从bit()获取2个向量来实现这一点,因此每个条目仍然存在30个多余的位,这似乎是贪婪的。 - Iterator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接