效能是指使用行为目的和手段方面的正确性与效果方面的有利性。衡量数据处理效能的依据是计算效率、实现效果和获得效益,是对数据科学过程的效率、效果的概括性、综合性评价。总体来说,可以通过时间和空间两个维度对数据处理效能进行评价。一般来说,在保证结果真实准确的前提下,数据处理的时间越短、使用空间越少,那么数据处理的效能就越高。
时间衡量
任意特定操作、函数或一组代码的执行都需要一定的时间,在保证代码能够正确工作前提下,应该对R代码进行优化,使其执行得更快。在R中,对代码的时间进行衡量非常重要,因为这个步骤可以获知代码中运行缓慢和低效的部分,从而识别瓶颈,然后进行代码优化,进而提高程序的性能。因此,如何在R中对代码进行计时,对开发者来说是一项非常重要的技能。
在R中,能够实现计时的工具非常多,本书不会穷举所有的方法,而是对最常见而实用的方法进行介绍。这里我们统一使用Sys.sleep
来构造测试代码,Sys.sleep
是系统里面用于暂停制定时间的函数,可以让脚本运行暂停一定的秒数。首先,如果想要对某一段代码运行的时间进行衡量,可以使用tidyfst 包的pst
函数:
Thank you for using tidyfst!
To acknowledge our work, please cite the package:
Huang et al., (2020). tidyfst: Tidy Verbs for Fast Data Manipulation. Journal of Open Source Software, 5(52), 2388, https://doi.org/10.21105/joss.02388
pst ({
Sys.sleep (0.5 ) # 暂停0.5秒的时间
})
[1] "# Finished in 0.510s elapsed (0.000s cpu)"
在上面给出的结果中,首先给出的是实际系统运行时间(Wall-Clock Time),随后在括号内给出的时间是CPU运行时间(CPU Time)。CPU运行时间指的是CPU实际用于处理某个程序的时间。这个时间只计算CPU在执行这个特定程序或进程上的工作时间,不包括系统处理其他任务的时间(如输入/输出操作、磁盘操作或网络通信)的时间。实际系统运行时间是从程序开始到程序结束所经过的总时间,就像是用墙上的时钟来度量时间一样。这个时间包括了所有的等待和延迟时间,比如CPU切换到其他任务、数据从硬盘加载、网络延迟等。因此,这个时间通常会比CPU运行时间长,因为它包括了程序执行中所有可能的等待时间。
有的时候,我们认为仅仅利用一次运行的计时结果不够稳定,可以增加运行的次数。在这种情况下,使用microbenchmark 包的microbenchmark
函数。比如我们想要让代码重复5次,可以这样操作:
library (microbenchmark)
microbenchmark (Sys.sleep (0.1 ), # 暂停0.1秒的时间
times = 5 ) # 重复运行5次
Unit: milliseconds
expr min lq mean median uq max neval
Sys.sleep(0.1) 109.6687 110.5331 111.458 110.6252 111.5059 114.9572 5
得到的结果中,“Unit”部分声明了本次代码执行时间衡量的时间单位(“milliseconds”代表时间单位为微秒),expr 代表执行的代码,neval 代表代码执行的次数,而mean 和median 分别代表多次执行中时间的平均值和中位数,min 和max 则给出了执行时间的最小值和最大值。 此外,microbenchmark
函数还可以比较不同代码的运行时间长短,实现方法如下:
microbenchmark (Sys.sleep (0.1 ), # 暂停0.1秒的时间
Sys.sleep (0.2 ), # 暂停0.2秒的时间
times = 5 ) # 各自重复运行5次
Unit: milliseconds
expr min lq mean median uq max neval
Sys.sleep(0.1) 101.8236 103.7175 108.0753 109.8820 109.9186 115.0347 5
Sys.sleep(0.2) 203.1670 204.1940 206.2796 204.2952 204.7377 215.0043 5
空间衡量
一般来说,R将所有对象存储在计算机的物理内存(RAM,Random-access Memory)中进行操作。如果我们的计算机的物理内存不足以处理一些工作,那么就需要使用一些新的方法来对其进行处理。因此,了解计算环境的可用内存限制对我们而言至关重要。在使用R时值得注意的第一件事情是,您的计算机实际上有多少物理内存。通常情况下,我们可以通过查看操作系统的设置来了解这一点。举例来说,如果我们有一个内存为8GB的笔记本电脑,那么R可用的RAM量将远远小于此值,即上限是无法达到8G的。如果我们计划在这台笔记本上读入一个占用16GB内存的对象,那么我们需要换一台内存更大的计算机才有可能实现。
在R中,pryr 包提供了一系列函数来对R中的内存使用情况进行分析。首先,我们可以使用object_size
函数来测度某一个对象占用了多少内存:
Attaching package: 'pryr'
The following object is masked from 'package:tidyfst':
object_size
a = rnorm (1e5 ) # 生成10万个服从正态分布的随机数,赋值给a
object_size (a) # 查看变量a占据了多少内存
如果我们想要看整个R当前所占的总内存数量,那么可以使用mem_used
函数:
有的时候,我们需要知道经过某一步操作之后,我们R占用内存的变化是多少,可以使用mem_change
函数来完成这一步操作:
mem_change (rm (a)) # 把a变量移除后,R所占内存数量的变化
注意,如果结果带有负号,说明R所占用内存减少了,否则就是增加了。 此外,如果对一个已经保存在本地的文件,需要查看其占用内存空间,可以使用fs 包的file_size
函数进行查看,只需要把文件的路径放入即可。
综合衡量
在上面的章节中,我们分别讲述了如何在R中对一些操作完成的时间和占用的内存进行测量。实际上,这两个步骤可以同时完成。使用bench 包的mark
函数可以实现这一过程,比如我们要对一段代码运行的时间和空间花销进行测度,可以这样操作:
library (bench)
mark (a = rnorm (1e5 ))
# A tibble: 1 × 6
expression min median `itr/sec` mem_alloc `gc/sec`
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
1 a 2.54ms 2.79ms 352. 781KB 4.09
上面的代码会对表达式执行若干次,然后进行效能的衡量。在返回结果中,median 代表若干次迭代中时间花销的中位数,mem_alloc 代表在运行表达式时,R分配的内存总量;而itr/sec 则告诉我们每秒可以对该表达式执行多少次。 mark
函数不仅可以对一个表达式的效能进行测量,还可以对多个表达式的效能进行比较。一般来说,默认情况下要求不同表达式的返回值必须是一致的,但是我们可以通过把check 参数设置为FALSE来避免这一默认设置。实现方法如下:
mark (
a = rnorm (1e5 ),
b = rnorm (1e5 + 1 ),
c = rnorm (2e5 ),
check = FALSE
)
# A tibble: 3 × 6
expression min median `itr/sec` mem_alloc `gc/sec`
<bch:expr> <bch:tm> <bch:tm> <dbl> <bch:byt> <dbl>
1 a 2.55ms 2.79ms 346. 781.3KB 6.25
2 b 2.54ms 2.8ms 345. 781.3KB 4.08
3 c 5.33ms 5.67ms 173. 1.53MB 6.39
以上代码利用mark
函数对比了3个表达式的效能。
小结
在本章中,我们解释了什么是R语言中执行代码的效能,并给出一系列方法来对执行R代码时的时间和空间花销进行衡量。拥有这些工具,我们就可以较为精准地对R代码进行评估,看看其是否高效地完成了我们所需要的功能。