在R中生成UUID向量的更快方法

Bob*_*bby 3 uuid 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
Run Code Online (Sandbox Code Playgroud)

本质上,我正在寻找一种R的方法,该方法设法实现此处所述的Java性能提升:Java 7或Java 6生成随机UUID的性能

它们应符合RFC 4122,但其他要求很灵活。

r2e*_*ans 5

最重要的是:不,目前没有办法在uuid不损害唯一性这一核心前提的情况下加快许多UUID的生成。(使用uuid)。

实际上,您的使用建议use.time=FALSE具有严重的不良后果(在Windows上)。见下文。

可能无法大规模获得更快的性能uuid。见下文。

uuid 在Windows上

的性能uuid::UUIDgenerate应考虑到操作系统。更具体地说,是随机性的来源。重要的是要查看性能,是的,其中:

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
Run Code Online (Sandbox Code Playgroud)

...因此使用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
Run Code Online (Sandbox Code Playgroud)

假定每次调用都希望UUID唯一,那么这很令人不安,并且是Windows上随机性不足的症状。(WSL是否为此提供出路?另一个研究机会...)

uuid 在Linux上

为了进行比较,在非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
Run Code Online (Sandbox Code Playgroud)

(我use.time=FALSE对Linux在Windows上花费的时间是在Windows上花费时间的两倍的事实感到有点好奇。)

使用SQL Server生成UUID

如果您可以访问SQL服务器(几乎可以肯定...请参见SQLite ...),那么您可以通过采用服务器的UUID生成实现来解决此规模问题,并意识到存在一些细微的差异。

(旁注:有“ V4”(完全随机),“ V1”(基于时间)和“ V1mc”(基于时间,并包括系统的mac地址)UUID。uuid如果为U4,则为V4 use.time=FALSE;否则为V1,对系统的UUID进行编码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
Run Code Online (Sandbox Code Playgroud)

SQL的使用具有一些开销,如果大规模完成,则无需花费很长时间即可克服。

  • PostgreSQL需要uuid-ossp扩展,可以通过安装

    CREATE EXTENSION "uuid-ossp"
    
    Run Code Online (Sandbox Code Playgroud)

    安装/可用后,您可以使用以下方法生成nUUID:

    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
    
    Run Code Online (Sandbox Code Playgroud)

    存在其他UUID函数。https://www.postgresql.org/docs/9.6/static/uuid-ossp.html

  • SQLite包括这样做的限制功能,但对于V4样式的UUID(长度n),此hack效果很好:

    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
    
    Run Code Online (Sandbox Code Playgroud)

    对其进行格式化会费一些力气,但充其量还是不错的。如果不坚持使用这种格式,可能会发现性能有所改善。)

  • SQL Server要求临时创建一个表(带有newsequentialid()),在其中生成序列,提取自动生成的ID,并丢弃该表。有点过头了,尤其是考虑到使用SQLite的简便性,但是要考虑YMMV。(没有提供任何代码,添加的代码并不多。)

其他注意事项

除了执行时间和足够的随机性外,围绕数据库表进行了各种讨论(目前未作讨论),这些表通过使用非连续的UUID来指示性能影响。这与索引页面等有关,不在此答案的范围之内。

但是,假设这是正确的...假设大约在同一时间插入的行(与时间相关)通常被分组在一起(直接或子分组),那么使用UUID保留当日数据是一件好事键位于同一数据库索引页中,因此V4(完全随机)UUID可能会降低大型组(和大型表)的数据库性能。因此,我个人更喜欢V1而不是V4。

其他(仍未引用)的讨论认为,在UUID中包含可直接跟踪的MAC地址是对内部信息的轻微违反。因此,我个人倾向于V1mc而不是V1。

(但是我还没有一种方法可以很好地做到这一点RSQLite,所以我依赖postgresql附近。幸运的是,我将postgresql足够用于其他事情,以便在Windows上使用docker保留实例。)