在 R 中生成 UUID 向量的更快方法
A faster way to generate a vector of UUIDs in R
下面的代码大约需要 15 秒才能生成 10k UUID 的向量。我将需要生成 1M 或更多,我计算这将需要 15 * 10 * 10 / 60 分钟,或大约 25 分钟。有没有更快的方法来实现这个目标?
library(uuid)
library(dplyr)
start_time <- Sys.time()
temp <- sapply( seq_along(1:10000), UUIDgenerate )
end_time <- Sys.time()
end_time - start_time
# Time difference of 15.072 secs
本质上,我正在寻找一种 R 方法,它设法实现此处描述的 Java 性能提升:Performance of Random UUID generation with Java 7 or Java 6
它们应该符合 RFC 4122,但其他要求是灵活的。
提供选项 use.time
将显着加快该过程。它可以设置为 TRUE
或 FALSE
,以确定 UUID 是否为 time-based。在这两种情况下,它都会比不指定此选项快得多。
对于 10k UUID,
library(uuid)
library(dplyr)
start_time <- Sys.time()
temp <- sapply( seq_along(1:10000), function(ign) UUIDgenerate(FALSE) )
end_time <- Sys.time()
end_time - start_time
# 10k: 0.01399994 secs
start_time <- Sys.time()
temp <- sapply( seq_along(1:10000), function(ign) UUIDgenerate(TRUE) )
end_time <- Sys.time()
end_time - start_time
# 10k: 0.01100016 secs
即使扩展到 100M,仍然比原来的 15 秒更快 run-time。
start_time <- Sys.time()
temp <- sapply( seq_along(1:100000000), function(ign) UUIDgenerate(FALSE) )
end_time <- Sys.time()
end_time - start_time
# 100M: 1.154 secs
start_time <- Sys.time()
temp <- sapply( seq_along(1:100000000), function(ign) UUIDgenerate(TRUE) )
end_time <- Sys.time()
end_time - start_time
# 100M: 3.7586 secs
前面的底线:不,目前没有办法在不损害唯一性的核心前提的情况下,用uuid
加速大量UUID的生成。 (使用 uuid
,即。)
事实上,您使用 use.time=FALSE
的建议产生了非常糟糕的后果(在 windows 上)。见下文。
有可能在规模上获得更快的性能,只是 uuid
不行。见下文。
uuid
在 Windows
uuid::UUIDgenerate
的性能应该考虑到 OS。更具体地说,随机性的来源。看性能很重要,是的,其中:
library(microbenchmark)
microbenchmark(
rf=replicate(1000, uuid::UUIDgenerate(FALSE)),
rt=replicate(1000, uuid::UUIDgenerate(TRUE)),
sf=sapply(1:1000, function(ign) uuid::UUIDgenerate(FALSE)),
st=sapply(1:1000, function(ign) uuid::UUIDgenerate(TRUE))
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# rf 8.675561 9.330877 11.73299 10.14592 11.75467 66.2435 100
# rt 89.446158 90.003196 91.53226 90.94095 91.13806 136.9411 100
# sf 8.570900 9.270524 11.28199 10.22779 12.06993 24.3583 100
# st 89.359366 90.189178 91.73793 90.95426 91.89822 137.4713 100
... 所以使用 use.time=FALSE
总是更快。 (我包含了 sapply
示例,用于与您的答案代码进行比较,以表明 replicate
永远不会变慢。除非您出于某种原因觉得需要数字参数,否则请在此处使用 replicate
。)
但是,有一个问题:
R.version[1:3]
# _
# platform x86_64-w64-mingw32
# arch x86_64
# os mingw32
length(unique(replicate(1000, uuid::UUIDgenerate(TRUE))))
# [1] 1000
length(unique(replicate(1000, uuid::UUIDgenerate(FALSE))))
# [1] 20
鉴于 UUID 旨在每次调用时都是唯一的,这令人不安,并且是 windows 上随机性不足的症状。 (WSL是否为此提供了出路?又是一个研究机会……)
uuid
在 Linux
为了比较,在 non-windows 平台上的相同结果:
microbenchmark(
rf=replicate(1000, uuid::UUIDgenerate(FALSE)),
rt=replicate(1000, uuid::UUIDgenerate(TRUE)),
sf=sapply(1:1000, function(ign) uuid::UUIDgenerate(FALSE)),
st=sapply(1:1000, function(ign) uuid::UUIDgenerate(TRUE))
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# rf 20.852227 21.48981 24.90932 22.30334 25.11449 74.20972 100
# rt 9.782106 11.03714 14.15256 12.04848 15.41695 100.83724 100
# sf 20.250873 21.39140 24.67585 22.44717 27.51227 44.43504 100
# st 9.852275 11.15936 13.34731 12.11374 15.03694 27.79595 100
R.version[1:3]
# _
# platform x86_64-pc-linux-gnu
# arch x86_64
# os linux-gnu
length(unique(replicate(1000, uuid::UUIDgenerate(TRUE))))
# [1] 1000
length(unique(replicate(1000, uuid::UUIDgenerate(FALSE))))
# [1] 1000
(linux 上的 use.time=FALSE
比 windows 花费的时间是 windows 的两倍……这让我有点好奇……)
使用 SQL 服务器生成 UUID
如果您可以访问 SQL 服务器(您几乎肯定可以访问 SQLite ...),那么您可以使用 服务器实现UUID生成,认识到有一些细微差别。
(旁注:有"V4"(完全随机)、"V1"(time-based)和"V1mc"(time-based并包括系统的mac 地址)UUID。如果 use.time=FALSE
则 uuid
给出 V4,否则给出 V1,编码系统的 mac 地址。)
在 windows 上的一些性能比较(所有时间以秒为单位):
# n uuid postgres sqlite sqlserver
# 1 100 0 1.23 1.13 0.84
# 2 1000 0.05 1.13 1.21 1.08
# 3 10000 0.47 1.35 1.45 1.17
# 4 100000 5.39 3.10 3.50 2.68
# 5 1000000 63.48 16.61 17.47 16.31
SQL 的使用有一些开销,在大规模完成时不需要很长时间就可以克服。
PostgreSQL 需要 uuid-ossp
扩展,可使用
安装
CREATE EXTENSION "uuid-ossp"
一次 installed/available,您可以生成 n
个 UUID:
n <- 3
pgcon <- DBI::dbConnect(...)
DBI::dbGetQuery(pgcon, sprintf("select uuid_generate_v1mc() as uuid from generate_series(1,%d)", n))
# uuid
# 1 53cd17c6-3c21-11e8-b2bf-7bab2a3c8486
# 2 53cd187a-3c21-11e8-b2bf-dfe12d92673e
# 3 53cd18f2-3c21-11e8-b2bf-d3c64c6ad73f
存在其他 UUID 函数。 https://www.postgresql.org/docs/9.6/static/uuid-ossp.html
SQLite 包含有限的能力,但这个 hack 对于 V4 风格的 UUID(长度 n
)来说已经足够好了:
sqlitecon <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") # or your own
DBI::dbGetQuery(sqlitecon, sprintf("
WITH RECURSIVE cnt(x) as (
select 1 union all select x+1 from cnt limit %d
)
select (hex(randomblob(4))||'-'||hex(randomblob(2))||'-'||hex(randomblob(2))||'-'||hex(randomblob(2))||'-'||hex(randomblob(6))) as uuid
from cnt", n))
# uuid
# 1 EE6B08DA-2991-BF82-55DD-78FEA48ABF43
# 2 C195AAA4-67FC-A1C0-6675-E4C5C74E99E2
# 3 EAC159D6-7986-F42C-C5F5-35764544C105
将其格式化为相同的格式有点麻烦,充其量只是一个很好的例子。如果不坚持这种格式,您可能会发现性能有小幅提升。)
SQL服务器需要临时创建一个table(带newsequentialid()
),在其中生成一个序列,拉取automatically-generated ID,以及丢弃 table。有点 over-the-top,特别是考虑到使用 SQLite 的便利性,但是 YMMV。 (没有提供代码,添加不多。)
其他注意事项
除了执行时间和 sufficient-randomness 之外,还有各种关于数据库 table 的讨论(暂时未引用),这些讨论表明使用 non-consecutive UUID 对性能的影响。这与索引页等有关,超出了本答案的范围。
但是,假设这是真的......假设大约同时插入的行(时间相关)通常组合在一起(直接或sub-grouped),那么这是一件好事将具有 UUID 键的 same-day 数据保存在同一数据库 index-page 中,因此 V4(完全随机)UUID 可能会降低大型组(和大型 tables)的数据库性能。因此,我个人更喜欢 V1 而不是 V4。
其他(仍未引用)讨论认为在 UUID 中包含 directly-traceable MAC 地址是对内部信息的轻微破坏。出于这个原因,我个人更倾向于V1mc而不是V1。
(但我还没有办法用 RSQLite
很好地做到这一点,所以我依赖附近的 postgresql
。幸运的是,我使用 postgresql 来做其他事情我在 windows 上用 docker 保留了一个实例。)
下面的代码大约需要 15 秒才能生成 10k UUID 的向量。我将需要生成 1M 或更多,我计算这将需要 15 * 10 * 10 / 60 分钟,或大约 25 分钟。有没有更快的方法来实现这个目标?
library(uuid)
library(dplyr)
start_time <- Sys.time()
temp <- sapply( seq_along(1:10000), UUIDgenerate )
end_time <- Sys.time()
end_time - start_time
# Time difference of 15.072 secs
本质上,我正在寻找一种 R 方法,它设法实现此处描述的 Java 性能提升:Performance of Random UUID generation with Java 7 or Java 6
它们应该符合 RFC 4122,但其他要求是灵活的。
提供选项 use.time
将显着加快该过程。它可以设置为 TRUE
或 FALSE
,以确定 UUID 是否为 time-based。在这两种情况下,它都会比不指定此选项快得多。
对于 10k UUID,
library(uuid)
library(dplyr)
start_time <- Sys.time()
temp <- sapply( seq_along(1:10000), function(ign) UUIDgenerate(FALSE) )
end_time <- Sys.time()
end_time - start_time
# 10k: 0.01399994 secs
start_time <- Sys.time()
temp <- sapply( seq_along(1:10000), function(ign) UUIDgenerate(TRUE) )
end_time <- Sys.time()
end_time - start_time
# 10k: 0.01100016 secs
即使扩展到 100M,仍然比原来的 15 秒更快 run-time。
start_time <- Sys.time()
temp <- sapply( seq_along(1:100000000), function(ign) UUIDgenerate(FALSE) )
end_time <- Sys.time()
end_time - start_time
# 100M: 1.154 secs
start_time <- Sys.time()
temp <- sapply( seq_along(1:100000000), function(ign) UUIDgenerate(TRUE) )
end_time <- Sys.time()
end_time - start_time
# 100M: 3.7586 secs
前面的底线:不,目前没有办法在不损害唯一性的核心前提的情况下,用uuid
加速大量UUID的生成。 (使用 uuid
,即。)
事实上,您使用 use.time=FALSE
的建议产生了非常糟糕的后果(在 windows 上)。见下文。
有可能在规模上获得更快的性能,只是 uuid
不行。见下文。
uuid
在 Windows
uuid::UUIDgenerate
的性能应该考虑到 OS。更具体地说,随机性的来源。看性能很重要,是的,其中:
library(microbenchmark)
microbenchmark(
rf=replicate(1000, uuid::UUIDgenerate(FALSE)),
rt=replicate(1000, uuid::UUIDgenerate(TRUE)),
sf=sapply(1:1000, function(ign) uuid::UUIDgenerate(FALSE)),
st=sapply(1:1000, function(ign) uuid::UUIDgenerate(TRUE))
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# rf 8.675561 9.330877 11.73299 10.14592 11.75467 66.2435 100
# rt 89.446158 90.003196 91.53226 90.94095 91.13806 136.9411 100
# sf 8.570900 9.270524 11.28199 10.22779 12.06993 24.3583 100
# st 89.359366 90.189178 91.73793 90.95426 91.89822 137.4713 100
... 所以使用 use.time=FALSE
总是更快。 (我包含了 sapply
示例,用于与您的答案代码进行比较,以表明 replicate
永远不会变慢。除非您出于某种原因觉得需要数字参数,否则请在此处使用 replicate
。)
但是,有一个问题:
R.version[1:3]
# _
# platform x86_64-w64-mingw32
# arch x86_64
# os mingw32
length(unique(replicate(1000, uuid::UUIDgenerate(TRUE))))
# [1] 1000
length(unique(replicate(1000, uuid::UUIDgenerate(FALSE))))
# [1] 20
鉴于 UUID 旨在每次调用时都是唯一的,这令人不安,并且是 windows 上随机性不足的症状。 (WSL是否为此提供了出路?又是一个研究机会……)
uuid
在 Linux
为了比较,在 non-windows 平台上的相同结果:
microbenchmark(
rf=replicate(1000, uuid::UUIDgenerate(FALSE)),
rt=replicate(1000, uuid::UUIDgenerate(TRUE)),
sf=sapply(1:1000, function(ign) uuid::UUIDgenerate(FALSE)),
st=sapply(1:1000, function(ign) uuid::UUIDgenerate(TRUE))
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# rf 20.852227 21.48981 24.90932 22.30334 25.11449 74.20972 100
# rt 9.782106 11.03714 14.15256 12.04848 15.41695 100.83724 100
# sf 20.250873 21.39140 24.67585 22.44717 27.51227 44.43504 100
# st 9.852275 11.15936 13.34731 12.11374 15.03694 27.79595 100
R.version[1:3]
# _
# platform x86_64-pc-linux-gnu
# arch x86_64
# os linux-gnu
length(unique(replicate(1000, uuid::UUIDgenerate(TRUE))))
# [1] 1000
length(unique(replicate(1000, uuid::UUIDgenerate(FALSE))))
# [1] 1000
(linux 上的 use.time=FALSE
比 windows 花费的时间是 windows 的两倍……这让我有点好奇……)
使用 SQL 服务器生成 UUID
如果您可以访问 SQL 服务器(您几乎肯定可以访问 SQLite ...),那么您可以使用 服务器实现UUID生成,认识到有一些细微差别。
(旁注:有"V4"(完全随机)、"V1"(time-based)和"V1mc"(time-based并包括系统的mac 地址)UUID。如果 use.time=FALSE
则 uuid
给出 V4,否则给出 V1,编码系统的 mac 地址。)
在 windows 上的一些性能比较(所有时间以秒为单位):
# n uuid postgres sqlite sqlserver
# 1 100 0 1.23 1.13 0.84
# 2 1000 0.05 1.13 1.21 1.08
# 3 10000 0.47 1.35 1.45 1.17
# 4 100000 5.39 3.10 3.50 2.68
# 5 1000000 63.48 16.61 17.47 16.31
SQL 的使用有一些开销,在大规模完成时不需要很长时间就可以克服。
PostgreSQL 需要
安装uuid-ossp
扩展,可使用CREATE EXTENSION "uuid-ossp"
一次 installed/available,您可以生成
n
个 UUID:n <- 3 pgcon <- DBI::dbConnect(...) DBI::dbGetQuery(pgcon, sprintf("select uuid_generate_v1mc() as uuid from generate_series(1,%d)", n)) # uuid # 1 53cd17c6-3c21-11e8-b2bf-7bab2a3c8486 # 2 53cd187a-3c21-11e8-b2bf-dfe12d92673e # 3 53cd18f2-3c21-11e8-b2bf-d3c64c6ad73f
存在其他 UUID 函数。 https://www.postgresql.org/docs/9.6/static/uuid-ossp.html
SQLite 包含有限的能力,但这个 hack 对于 V4 风格的 UUID(长度
n
)来说已经足够好了:sqlitecon <- DBI::dbConnect(RSQLite::SQLite(), ":memory:") # or your own DBI::dbGetQuery(sqlitecon, sprintf(" WITH RECURSIVE cnt(x) as ( select 1 union all select x+1 from cnt limit %d ) select (hex(randomblob(4))||'-'||hex(randomblob(2))||'-'||hex(randomblob(2))||'-'||hex(randomblob(2))||'-'||hex(randomblob(6))) as uuid from cnt", n)) # uuid # 1 EE6B08DA-2991-BF82-55DD-78FEA48ABF43 # 2 C195AAA4-67FC-A1C0-6675-E4C5C74E99E2 # 3 EAC159D6-7986-F42C-C5F5-35764544C105
将其格式化为相同的格式有点麻烦,充其量只是一个很好的例子。如果不坚持这种格式,您可能会发现性能有小幅提升。)
SQL服务器需要临时创建一个table(带
newsequentialid()
),在其中生成一个序列,拉取automatically-generated ID,以及丢弃 table。有点 over-the-top,特别是考虑到使用 SQLite 的便利性,但是 YMMV。 (没有提供代码,添加不多。)
其他注意事项
除了执行时间和 sufficient-randomness 之外,还有各种关于数据库 table 的讨论(暂时未引用),这些讨论表明使用 non-consecutive UUID 对性能的影响。这与索引页等有关,超出了本答案的范围。
但是,假设这是真的......假设大约同时插入的行(时间相关)通常组合在一起(直接或sub-grouped),那么这是一件好事将具有 UUID 键的 same-day 数据保存在同一数据库 index-page 中,因此 V4(完全随机)UUID 可能会降低大型组(和大型 tables)的数据库性能。因此,我个人更喜欢 V1 而不是 V4。
其他(仍未引用)讨论认为在 UUID 中包含 directly-traceable MAC 地址是对内部信息的轻微破坏。出于这个原因,我个人更倾向于V1mc而不是V1。
(但我还没有办法用 RSQLite
很好地做到这一点,所以我依赖附近的 postgresql
。幸运的是,我使用 postgresql 来做其他事情我在 windows 上用 docker 保留了一个实例。)