*apply()
函数的documentation声明:vapply
类似于sapply
,但具有预先指定的返回值类型,因此使用它可以更安全(并且 * 有时 * 更快)。[强调我的]
对我来说,为什么它会更快是有道理的--检查类型浪费的时间更少--但是,考虑到他们本可以说 “vapply()
和sapply()
一样快或更快”,但选择了不这样做,我解释了他们选择 * 有时更快 *,因为他们可能意味着 *“对于大多数任务,vapply()
平均更快,但在某些情况下,它的平均速度可能是相同的,或者在其他情况下甚至是“更慢的速度”--这对我来说似乎很奇怪。为什么它会变慢?高级R声明 'vapply()
比sapply()
' 快,这是相当明确的。
我是否误解了这一点,或者在某些情况下vapply()
比sapply()
慢,如果是这样,它们是什么?
例如,原因可能是垃圾收集的差异,或者处理某些类型的速度,或者分配内存或其他东西(这些都是胡乱猜测)。
我做过的研究:
令人惊讶的是,我在网上,在StackOverflow或其他地方都找不到解决这个问题的方法。有很多关于reference vapply和its safety的问题。在few比较中,虽然vapply()
与sapply()
一样快或更快,但有许多迭代比最慢的vapply()
迭代更快(其中apply()
明显快于lapply()
或vapply()
)。长话短说,我有点迷路了!
任何你能提供的帮助将不胜感激!
1条答案
按热度按时间af7jpaap1#
让
vapply()
快速的检查不是免费的为了设计
vapply()
比sapply()
慢的情况,让我们看看源代码。sapply()
中的大部分工作由lapply()
完成。lapply()
的C代码非常简单。相关部分是(我的评论):本质上,这创建了一个与输入列表长度相同的输出列表,迭代它以将每个元素设置为应用于输入的每个元素的用户提供的函数的结果。
sapply()
然后通过simplify2array()
运行结果。相反,
vapply()
的C代码做了更多的工作。很多这是优化,使它比sapply()
更快,例如。立即分配一个原子向量作为输出,而不是分配一个列表,然后简化为一个向量。但是,它也包含以下内容:我们告诉
vapply()
输出的长度和类型。这意味着,例如,如果我们告诉vapply()
输出是integer(1)
,它需要检查每次迭代是否产生长度为1的整数向量。一种支票很贵的情况
创建开销检查的一种方法是返回一个值,其中检查长度的开销很大。考虑一个简单的例子:
lapply()
将在这里运行得非常快。seq(1e9)
产生ALTREP
,alternate representation。这意味着它不必分配一个长度为1e9
的向量,而是分配一个小得多的对象,该对象基本上包含起始值,结束值和增量。然而,ALTREP
的文档指出:对于现有的C代码,ALTREP对象看起来像普通的R对象。
这意味着
vapply()
不知道这是一个ALTREP
,因此它需要以非常昂贵的方式检查长度(比在R中运行length()
要昂贵得多,因为R知道ALTREP
是什么)。sapply()
也必须做一些代价高昂的事情。它基本上是这样做的:这将创建一列
matrix
和1e9
行,即它将ALTREP
计算为标准整数向量,因此它在RAM中分配一个大向量。所以
vapply()
和sapply()
都必须做一些比lapply()
昂贵得多的事情。问题是:哪个更贵?对伪用例进行对标
我们来测试一下
结果
我们可以在这里看到
vapply()
比sapply()
慢得多。有一些警告:这些测试只在我的PC上进行,而且速度太慢了,我只做了三次迭代。而且,我确实不得不做一些游戏来到达这里。当向量的长度小于1e9
时,vapply()
比sapply()
快。结果图
请注意,时间是对数标度。
值得指出的是,尽管设计这种情况很有趣,但这并不典型。在绝大多数使用R的任务中,
vapply()
可能比sapply()
快得多。此外,如您所知,还有其他好处,例如vapply()
确保返回类型得到保证。