为什么 Unix/Terminal 比 R 快?
Why is Unix/Terminal faster than R?
我是 Unix 的新手,但是,我最近意识到非常简单的 Unix 命令可以非常非常快速地对大型数据集执行非常简单的操作。我的问题是为什么这些 Unix 命令相对于 R 这么快?
让我们首先假设数据很大,但不超过您计算机上的 RAM 容量。
在计算方面,我知道 Unix 命令可能比它们的 R 命令更快。但是,我无法想象这会解释整个时差。毕竟,所有基本的 R 函数,如 Unix 命令,都是用低级语言编写的,如 C/C++.
因此我怀疑速度提升与 I/O 有关。虽然我对计算机的工作原理只有基本的了解,但我确实知道要操作数据,首先要从磁盘读取数据(假设数据是本地的)。这很慢。但是,无论是使用R函数还是Unix命令来操作数据,大多数都是从磁盘中获取数据。
因此,我怀疑是如何从磁盘读取数据(如果这有意义的话)是导致时差的原因。这种直觉是正确的吗?
谢谢!
更新: 抱歉含糊不清。这是有意为之的,我希望泛泛地讨论这个想法,而不是专注于一个具体的例子。
无论如何,我将生成一个计算行数的示例
首先我会生成一个大数据集。
row = 1e7
col = 50
df<-matrix(rpois(row*col,1),row,col)
write.csv(df,"df.csv")
用 Unix 做
time wc -l df.csv
real 0m12.261s
user 0m1.668s
sys 0m2.589s
用 R 做
library(data.table)
system.time({ nrow(fread("df.csv")) })
...
user system elapsed
26.77 1.67 47.07
注意 elapsed/real > 用户 + 系统。这表明 CPU 正在等待磁盘。
我怀疑R的速度慢与读取数据有关。看来我是对的:
system.time(fread("df.csv"))
user system elapsed
34.69 2.81 47.41
我的问题是 I/O 对于 Unix 和 R 有何不同。为什么?
我不确定你在说什么操作,但一般来说,像 R 这样更复杂的处理系统使用更复杂的内部数据结构来表示被操作的数据,构建这些数据结构可能是一个很大的问题瓶颈,比 grep 等 Unix 命令倾向于操作的简单行、单词和字符慢得多。
另一个因素(取决于您的脚本的设置方式)是您是在 "streaming mode" 中一次处理一个数据,还是将所有内容读入内存。 Unix 命令往往被编写为在管道中运行,读取一小段数据(通常是一行),处理它,可能写出结果,然后继续下一行。另一方面,如果您在处理之前将整个数据集读入内存,那么即使您有足够的 RAM,分配和组织所有必要的内存也会非常昂贵。
[根据您的附加信息更新]
啊哈。所以你是要求R一次将整个文件读入内存。这在很大程度上造成了差异。再说几件事吧。
I/O。我们可以考虑三种从文件中读取字符的方法,特别是如果我们正在执行的处理方式影响了最方便的读取方式。
- 无缓冲的小型随机读取。我们一次向操作系统请求 1 个或几个字符,并在读取它们时对其进行处理。
- 无缓冲的大型块大小读取。我们向操作请求大块内存——通常大小为 1k 或 8k——并在请求下一个块之前咀嚼内存中的每个块。
- 缓冲读取。我们的编程语言为我们提供了一种方法,可以从中间缓冲区中请求尽可能多的字符,并且语言中内置的代码("library" 代码)会自动通过读取大块来保持缓冲区满- 来自操作系统的大小块。
现在,重要的是要知道操作系统更愿意读取大块大小的块。所以#1 可能比 2 和 3 慢得多。(我见过 10 或 100 的因数。)但是没有编写良好的程序使用#1,所以我们几乎可以忘记它。只要您使用 2 或 3,I/O 速度就会大致相同。 (在极端情况下,如果您知道自己在做什么,如果可以的话,使用 2 而不是 3 可以稍微提高效率。)
现在让我们谈谈每个程序处理数据的方式。 wc
基本上有 5 个步骤:
- 一次读一个字符。 (我可以向你保证它使用方法 3。)
- 每读一个字符,字符数加一。
- 如果读取的字符是换行符,则行数加一。
- 如果读取的字符是或不是单词分隔符,请更新字数。
- 最后,根据要求打印出行数、字数、and/or 个字符。
正如您所见,所有这些都是 I/O 并且非常简单,基于字符的处理。 (唯一复杂的步骤是 4。作为练习,我曾经写过 wc
的一个版本,如果用户不这样做,它不会在读取循环中执行所有步骤 2、3 和 4询问所有计数。如果您调用 wc -c
或 wc -l
,我的版本确实 运行 明显更快。但显然代码要复杂得多。)
另一方面,对于 R,事情要复杂得多。首先,您告诉它读取一个 CSV 文件。因此,在读取时,它必须找到分隔行的换行符和分隔列的逗号。这大致相当于 wc
必须做的处理。但是,对于它找到的每个数字,它必须将其转换为可以有效使用的内部数字。例如,如果 CSV 文件中的某处出现序列
...,12345,...
R 将不得不读取这些数字(作为单个字符),然后执行与数学问题等价的操作
1 * 10000 + 2 * 1000 + 3 * 100 + 4 * 10 + 5 * 1
获取值 12345。
但还有更多。你要求 R 构建一个 table。 table 是一种特定的、高度规则的数据结构,它将所有数据排列成严格的行和列,以便高效查找。为了解这有多少工作量,让我们使用一个稍微牵强的假设性真实示例。
假设您是一家调查公司,您的工作是向街上路过的人询问某些问题。但是假设这些问题足够复杂,以至于你需要所有的人同时坐在教室里。 (进一步假设人们不介意这种不便。)
但首先你必须建造那个教室。你不确定会有多少人经过,所以你建了一个普通教室,有 5 排 6 张课桌,可容纳 30 人,你把课桌拖进来,人们开始排队,30 人后人们在你的文件中注意到有一个 31,那么你会做什么?你可以让他站在后面,但你有点执着于死板的行和列的想法,所以你让第 31 个人等着,然后你迅速打电话给建筑商,让他们再建一个30人的教室就在第一个旁边,现在你可以接受第31人,实际上还有29人,总共60人,但你注意到第61人。
所以你让他等一下,然后又给建筑商打电话,让他们再建 两个 教室,所以现在你有了一个漂亮的 2x2 网格30 人的教室,但人们不断涌入,很快第 121 个人出现了,但空间不够,您甚至还没有开始提出调查问题。
所以你打电话给一些知道如何做钢结构的高级建筑商,让他们在隔壁建造一座 5 层的大建筑,有 50 人的教室,每层 5 个,总共 50 x 5 x 5 = 1,250 张桌子,前 120 个人(他们一直在耐心等待)鱼贯而出,从旧房间进入新大楼,现在第 121 个人和他后面的更多人都有空间,你雇用了一些人清障车拆除旧教室并回收一些材料,人们不断涌入,很快您的新大楼中就有 1,250 人等待调查,而第 1,251 人刚刚出现。
所以你建了一座巨大的新摩天大楼,每层有 1000 张桌子,100 层楼,你拆除了旧的 5 层楼,但人还是不断涌入,你说你的大数据集有多大? 1e7 × 50?所以我认为 100 层的建筑也不够大。 (当你完成所有这些后,你唯一要问的 "survey question" 就是 "How many rows are there?")
这看起来是做作的,实际上这对于 R 在内部构建 table 以存储该数据集所必须做的事情来说并不算太糟糕。
与此同时,Bob 的折扣调查公司只能告诉你他调查了多少人,有多少人是男性和女性,以及年龄段,就在街角,人们正在排队,并且Bob 正在他的剪贴板上记下计数标记,人们在完成调查后就走开并开始他们的业务,Bob 根本没有浪费时间和金钱来建造任何教室。
我对 R 一无所知,但看看是否有办法预先构建一个空的 1e7 x 50 矩阵,然后将 CSV 文件读入其中。您可能会发现速度要快得多。 R 仍然需要做一些构建,但至少不会有任何错误的开始。
我是 Unix 的新手,但是,我最近意识到非常简单的 Unix 命令可以非常非常快速地对大型数据集执行非常简单的操作。我的问题是为什么这些 Unix 命令相对于 R 这么快?
让我们首先假设数据很大,但不超过您计算机上的 RAM 容量。
在计算方面,我知道 Unix 命令可能比它们的 R 命令更快。但是,我无法想象这会解释整个时差。毕竟,所有基本的 R 函数,如 Unix 命令,都是用低级语言编写的,如 C/C++.
因此我怀疑速度提升与 I/O 有关。虽然我对计算机的工作原理只有基本的了解,但我确实知道要操作数据,首先要从磁盘读取数据(假设数据是本地的)。这很慢。但是,无论是使用R函数还是Unix命令来操作数据,大多数都是从磁盘中获取数据。
因此,我怀疑是如何从磁盘读取数据(如果这有意义的话)是导致时差的原因。这种直觉是正确的吗?
谢谢!
更新: 抱歉含糊不清。这是有意为之的,我希望泛泛地讨论这个想法,而不是专注于一个具体的例子。
无论如何,我将生成一个计算行数的示例
首先我会生成一个大数据集。
row = 1e7
col = 50
df<-matrix(rpois(row*col,1),row,col)
write.csv(df,"df.csv")
用 Unix 做
time wc -l df.csv
real 0m12.261s
user 0m1.668s
sys 0m2.589s
用 R 做
library(data.table)
system.time({ nrow(fread("df.csv")) })
...
user system elapsed
26.77 1.67 47.07
注意 elapsed/real > 用户 + 系统。这表明 CPU 正在等待磁盘。
我怀疑R的速度慢与读取数据有关。看来我是对的:
system.time(fread("df.csv"))
user system elapsed
34.69 2.81 47.41
我的问题是 I/O 对于 Unix 和 R 有何不同。为什么?
我不确定你在说什么操作,但一般来说,像 R 这样更复杂的处理系统使用更复杂的内部数据结构来表示被操作的数据,构建这些数据结构可能是一个很大的问题瓶颈,比 grep 等 Unix 命令倾向于操作的简单行、单词和字符慢得多。
另一个因素(取决于您的脚本的设置方式)是您是在 "streaming mode" 中一次处理一个数据,还是将所有内容读入内存。 Unix 命令往往被编写为在管道中运行,读取一小段数据(通常是一行),处理它,可能写出结果,然后继续下一行。另一方面,如果您在处理之前将整个数据集读入内存,那么即使您有足够的 RAM,分配和组织所有必要的内存也会非常昂贵。
[根据您的附加信息更新]
啊哈。所以你是要求R一次将整个文件读入内存。这在很大程度上造成了差异。再说几件事吧。
I/O。我们可以考虑三种从文件中读取字符的方法,特别是如果我们正在执行的处理方式影响了最方便的读取方式。
- 无缓冲的小型随机读取。我们一次向操作系统请求 1 个或几个字符,并在读取它们时对其进行处理。
- 无缓冲的大型块大小读取。我们向操作请求大块内存——通常大小为 1k 或 8k——并在请求下一个块之前咀嚼内存中的每个块。
- 缓冲读取。我们的编程语言为我们提供了一种方法,可以从中间缓冲区中请求尽可能多的字符,并且语言中内置的代码("library" 代码)会自动通过读取大块来保持缓冲区满- 来自操作系统的大小块。
现在,重要的是要知道操作系统更愿意读取大块大小的块。所以#1 可能比 2 和 3 慢得多。(我见过 10 或 100 的因数。)但是没有编写良好的程序使用#1,所以我们几乎可以忘记它。只要您使用 2 或 3,I/O 速度就会大致相同。 (在极端情况下,如果您知道自己在做什么,如果可以的话,使用 2 而不是 3 可以稍微提高效率。)
现在让我们谈谈每个程序处理数据的方式。 wc
基本上有 5 个步骤:
- 一次读一个字符。 (我可以向你保证它使用方法 3。)
- 每读一个字符,字符数加一。
- 如果读取的字符是换行符,则行数加一。
- 如果读取的字符是或不是单词分隔符,请更新字数。
- 最后,根据要求打印出行数、字数、and/or 个字符。
正如您所见,所有这些都是 I/O 并且非常简单,基于字符的处理。 (唯一复杂的步骤是 4。作为练习,我曾经写过 wc
的一个版本,如果用户不这样做,它不会在读取循环中执行所有步骤 2、3 和 4询问所有计数。如果您调用 wc -c
或 wc -l
,我的版本确实 运行 明显更快。但显然代码要复杂得多。)
另一方面,对于 R,事情要复杂得多。首先,您告诉它读取一个 CSV 文件。因此,在读取时,它必须找到分隔行的换行符和分隔列的逗号。这大致相当于 wc
必须做的处理。但是,对于它找到的每个数字,它必须将其转换为可以有效使用的内部数字。例如,如果 CSV 文件中的某处出现序列
...,12345,...
R 将不得不读取这些数字(作为单个字符),然后执行与数学问题等价的操作
1 * 10000 + 2 * 1000 + 3 * 100 + 4 * 10 + 5 * 1
获取值 12345。
但还有更多。你要求 R 构建一个 table。 table 是一种特定的、高度规则的数据结构,它将所有数据排列成严格的行和列,以便高效查找。为了解这有多少工作量,让我们使用一个稍微牵强的假设性真实示例。
假设您是一家调查公司,您的工作是向街上路过的人询问某些问题。但是假设这些问题足够复杂,以至于你需要所有的人同时坐在教室里。 (进一步假设人们不介意这种不便。)
但首先你必须建造那个教室。你不确定会有多少人经过,所以你建了一个普通教室,有 5 排 6 张课桌,可容纳 30 人,你把课桌拖进来,人们开始排队,30 人后人们在你的文件中注意到有一个 31,那么你会做什么?你可以让他站在后面,但你有点执着于死板的行和列的想法,所以你让第 31 个人等着,然后你迅速打电话给建筑商,让他们再建一个30人的教室就在第一个旁边,现在你可以接受第31人,实际上还有29人,总共60人,但你注意到第61人。
所以你让他等一下,然后又给建筑商打电话,让他们再建 两个 教室,所以现在你有了一个漂亮的 2x2 网格30 人的教室,但人们不断涌入,很快第 121 个人出现了,但空间不够,您甚至还没有开始提出调查问题。
所以你打电话给一些知道如何做钢结构的高级建筑商,让他们在隔壁建造一座 5 层的大建筑,有 50 人的教室,每层 5 个,总共 50 x 5 x 5 = 1,250 张桌子,前 120 个人(他们一直在耐心等待)鱼贯而出,从旧房间进入新大楼,现在第 121 个人和他后面的更多人都有空间,你雇用了一些人清障车拆除旧教室并回收一些材料,人们不断涌入,很快您的新大楼中就有 1,250 人等待调查,而第 1,251 人刚刚出现。
所以你建了一座巨大的新摩天大楼,每层有 1000 张桌子,100 层楼,你拆除了旧的 5 层楼,但人还是不断涌入,你说你的大数据集有多大? 1e7 × 50?所以我认为 100 层的建筑也不够大。 (当你完成所有这些后,你唯一要问的 "survey question" 就是 "How many rows are there?")
这看起来是做作的,实际上这对于 R 在内部构建 table 以存储该数据集所必须做的事情来说并不算太糟糕。
与此同时,Bob 的折扣调查公司只能告诉你他调查了多少人,有多少人是男性和女性,以及年龄段,就在街角,人们正在排队,并且Bob 正在他的剪贴板上记下计数标记,人们在完成调查后就走开并开始他们的业务,Bob 根本没有浪费时间和金钱来建造任何教室。
我对 R 一无所知,但看看是否有办法预先构建一个空的 1e7 x 50 矩阵,然后将 CSV 文件读入其中。您可能会发现速度要快得多。 R 仍然需要做一些构建,但至少不会有任何错误的开始。