内容

1R正如我们所知

1.1内存管理:更改时复制;垃圾收集

更改时复制

  • 看起来就像R变量不共享任何内存

    X <- y <- 1:5 X
    ## [1] 1 2 3 4 5
    y
    ## [1] 1 2 3 4 5
    x[1] <- 2L x
    ## [1] 2 2 3 4 5
    y
    ## [1] 1 2 3 4 5
    x <- 1:5 fun = function(z) {z[1] = 2L;Z} fun(x)
    ## [1] 2 2 3 4 5
    x
    ## [1] 1 2 3 4 5
    • 不同于许多编程语言
    • 对用户来说非常方便——他们不必考虑“远距离更改”。
  • R使用“命名”概念,而不是严格的“引用计数”

    • 计算内存中某个位置的引用次数…
    • 但只能数到2
    • 所以' 2 '意味着在内存位置历史的某一点上,2或更多的符号指向了那个位置。
  • 在行动

    x = 1:5 .内部(inspect(x))
    ## @480c138 13 INTSXP g0c3 [NAM(2)] (len=5, tl=0) 1,2,3,4,5
    • @41de7d8:内存地址。
    • 13 INTSXP: integer ' S-expression '(稍后详细介绍)。
    • (南(1)):一个符号(x)引用内存中的位置。
    x = y = 1:5 .内部(inspect(x))
    ## @48f2f18 13 INTSXP g0c3 [MARK,NAM(2)] (len=5, tl=0) 1,2,3,4,5
    (检查(y))
    ## @48f2f18 13 INTSXP g0c3 [MARK,NAM(2)] (len=5, tl=0) 1,2,3,4,5
    • xy指向内存中的相同位置
    • (南(2)):(至少)两个符号引用位置
  • ' copy -on-change ':当更新一个向量时,如果Nam () == 2

    ##复制更改x = y = 1:5 .Internal(inspect(x))
    ## @3f94c98 13 INTSXP g0c3 [NAM(2)] (len=5, tl=0) 1,2,3,4,5
    x[1] = 2L .Internal(inspect(x))
    ## @3cf5db0 13 INTSXP g0c3 [NAM(1)] (len=5, tl=0) 2,2,3,4,5
    (检查(y))
    ## @3f94c98 13 INTSXP g0c3 [NAM(2)] (len=5, tl=0) 1,2,3,4,5
    x = 1:5 .内部(inspect(x))
    ## @3bf3838 13 INTSXP g0c3 [NAM(2)] (len=5, tl=0) 1,2,3,4,5
    x[1] = 2L .Internal(inspect(x))
    ## @3aef798 13 INTSXP g0c3 [NAM(1)] (len=5, tl=0) 2,2,3,4,5

垃圾收集(大致描述)

  • S-expressions (饱和度指数),由系统分配到一个可用的s表达式池
  • X <- 1将SEXP从已分配到“正在使用”池,级别0 (0 ing0c4(检查(x))
  • rm (x),一个符号不再引用SEXP,它是一个可重用的候选符号。
  • gc ()(手动,或更典型的自动触发时R可用池中SEXP已用完)

    • 访问g0池中的所有SEXP并标记为“未引用”
    • 访问所有正在使用的符号,并将其SEXP标记为“正在使用”
    • (第二次)访问g0池中的所有SEXP。将未引用的符号移动到“可用”池,将“正在使用”移动到g1池。
  • 注意:SEXP属于“代”和类

    Y = 1 fun = function() {x <- 1;2} fun();Gc() #扫描x, y;收集x,保存y
    ## [1] 2
    ## used (Mb) gc触发器(Mb) max used (Mb) ## Ncells 503648 26.9 940480 50.3 940480 50.3 ## Vcells 939506 7.2 1941543 14.9 1199408 9.2
    有趣的();Gc() #扫描并收集x,不访问y
    ## [1] 2
    ## used (Mb) gc触发器(Mb) max used (Mb) ## Ncells 503747 27.0 940480 50.3 940480 50.3 ## Vcells 939776 7.2 1941543 14.9 1199408 9.2
    • G0生成是最近分配的,并且最频繁地检查是否适合垃圾收集——通常是在函数中分配的符号,不再在作用域中;收藏的时机成熟了。
    • G1在越来越多的垃圾收集中幸存了下来——包或全局符号很少有资格被收集。
    • 净效应:g0节点富集,可收集的SEXP。
  • 资源:R内部手册,例如写障碍卢克·蒂尔尼餐厅笔记memory.c

性能的影响

  • 分配和复制的成本相对较高
  • 一些常用的使用模式涉及大量的复制!

    • c ()rbind ()cbind ()循环往复
    • 修改data.frame ()列表()或S4对象。

1.1.1练习

使用internal(检查())跟踪分配到的内存x在下面的循环中。

X <- integer() n <- 5 for (i in 1:n) X <- c(X, i)

答:

N = 5 x = integer();.Internal(inspect(x)) for (i in 1:n) {x <- c(x, i) .Internal(inspect(x))} x = integer();.Internal(inspect(x)) for (i in 1:n) {x[i] = i .Internal(inspect(x))} x
  • 如果一个整数占用大约8个字节的内存,并且循环中的每次迭代都将当前分配复制到新分配中,那么循环中复制了多少字节n迭代?

  • 解释的输出tracemem ()类中的值更新时的内存分配data.frame ().有多少data.frame被复制了吗?

    df <- mtcars tracemem(df) #整体data.frame
    ## [1] "<0x45779e0>"
    tracemem(df[[1]]) #指定列
    ## [1] "<0x1766760>"
    df [1]
    ## [1]
    Df [1,1] = 22.1
    ## tracemem[0x45779e0 -> 0x44fcd68]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group。block process_group withCallingHandlers process_file <匿名> <匿名> vweave_rmarkdown <匿名> <匿名> do。调用匿名> <# #tracemem[0x44fcd68 -> 0x447ce60]: [<-.data.frame [<- eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group.block process_group withCallingHandlers process_file   vweave_rmarkdown   do.call  ## tracemem[0x447ce60 -> 0x447d058]: [<-.data.frame [<- eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group.block process_group withCallingHandlers process_file   vweave_rmarkdown   do.call  ## tracemem[0x1766760 -> 0x264a210]: [<-.data.frame [<- eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group.block process_group withCallingHandlers process_file   vweave_rmarkdown   do.call 
    Df [2,2] = 22.2
    ## tracemem[0x447d058 -> 0x4464e78]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group。block process_group withCallingHandlers process_file <匿名> <匿名> vweave_rmarkdown <匿名> <匿名> do。调用匿名> <# #tracemem[0x4464e78 -> 0x4464f20]: [<-.data.frame [<- eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group.block process_group withCallingHandlers process_file   vweave_rmarkdown   do.call  ## tracemem[0x4464f20 -> 0x4465070]: [<-.data.frame [<- eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group.block process_group withCallingHandlers process_file   vweave_rmarkdown   do.call 
  • 将此与修改类似大小的矩阵时的内存分配进行比较

    m <- m1 <- matrix(0, nrow(mtcars), ncol(mtcars)) tracemem(m) #矩阵是一个具有dms属性的向量
    ## [1] "<0x477afd0>"
    M [1,1] = 22.1
    ## tracemem[0x477afd0 -> 0x45b3820]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group。block process_group withCallingHandlers process_file <匿名> <匿名> vweave_rmarkdown <匿名> <匿名> do。调用匿名> <
    M [2,1] = 22.2
  • 以及“向量化”更新

    Df <- mtcars tracemem(Df);tracemem (df [[1]])
    ## [1] "<0x45779e0>"
    ## [1] "<0x1766760>"
    Df [1:2, 1] <- c(22.1, 22.2)
    ## tracemem[0x45779e0 -> 0x3f57e58]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group。block process_group withCallingHandlers process_file <匿名> <匿名> vweave_rmarkdown <匿名> <匿名> do。调用匿名> <# #tracemem[0x3f57e58 -> 0x3f57f00]: [<-.data.frame [<- eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group.block process_group withCallingHandlers process_file   vweave_rmarkdown   do.call  ## tracemem[0x3f57f00 -> 0x3f58050]: [<-.data.frame [<- eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group.block process_group withCallingHandlers process_file   vweave_rmarkdown   do.call  ## tracemem[0x1766760 -> 0x3d04bd0]: [<-.data.frame [<- eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group.block process_group withCallingHandlers process_file   vweave_rmarkdown   do.call 
    m <- m1 <- matrix(0, nrow(mtcars), ncol(mtcars)) tracemem(m) #矩阵是一个具有dms属性的向量
    ## [1] "<0x4615310>"
    M [1:2, 1] <- c(22.1, 22.2)
    ## tracemem[0x4615310 -> 0x3cd6510]: eval eval withVisible withCallingHandlers handle timing_fn evaluate_call evaluate in_dir block_exec call_block process_group。block process_group withCallingHandlers process_file <匿名> <匿名> vweave_rmarkdown <匿名> <匿名> do。调用匿名> <

1.2正确、健壮、简单、快速:实用的技术

这是一个简单的函数,实现得很糟糕

F <- function(n) {x <- integer() for (i in 1:n) x <- c(x, i) x}

正确的

  • 最重要的!
  • 相同的()-非常严格
  • all.equal ()-允许数值逼近
  • 单元测试!

    库(testthat) test_that("f()是正确的",{expect_same (f(5), 1:5)})

健壮的

  • 边缘情况,例如长度为0的迭代,NA在这里,我们添加了一些额外的测试,并将它们放在一个函数中,以便于重用。

    测试<-函数(f) {test_that("f()是正确的和健壮的",{expect_alike (f(5), 1:5) expect_alike (f(1), 1L) expect_alike (f(0), integer(0))})}
  • 我们的代码没有通过单元测试,因为1: n失败与N == 0

    tryCatch(tests(f), error = conditionMessage)
    ##[1]“测试失败:'f()是正确的和健壮的'\n* f(0)不等于整数(0)。\n长度不同:2 vs 0\n"
  • 修改后的代码,对常见用例具有健壮性

    F1 =函数(n) {x <- integer() for (i in seq_len(n)) x = c(x, i) x}测试(F1)

  • system.time (),但可能会有相当大的运行变化
  • 微基准测试

    库(微基准)res <-微基准(f1(100), f1(200), f1(400), f1(800), f1(1600)) plot(res)

  • 因为复制,比例是二次方的。解决方案?

    F2 <- function(n) {sapply(seq_len(n), function(i) i)} tests(F2) #失败!N == 0
    F2a <-函数(n) {if (n == 0) {integer()} else {sapply(seq_len(n),函数(i) i)}}测试(F2a)
    f3 <-函数(n) {vapply(seq_len(n),函数(i) i,整数(1))}test_that("f()是正确的",{expect_same (f3(5), 1:5) expect_same (f3(1), 1L) expect_same (f3(0),整数(0))})测试(f3)
    F4 <-函数(n) {x <-整数(n) for (i in seq_len(n)) x[[i]] = i x}测试(F4)
    F5 <- seq_len tests(F5)
    n < - 1000微基准测试(f1 (n), f2a (n), f3 (n), f4 (n), f5 (n))

1.3符号解析和计算

环境

  • 当前环境

    环境()
    ## 
  • 令人惊讶的是R

    Env <- new.env() Env [["foo"]] <- 123 Env [["foo"]]
    ## [1] 123
    Get ("foo", env) #等价(不是真的!)
    ## [1] 123
    ls (env)
    ##[1]“foo”
  • 键值对哈希表
  • 键被称为的名字象征R
  • 是常量时间的查找

  • “引用”语义(几乎所有其他在R是' copy-on-change '语义)

    另一个< - env env[[“酒吧”]]< - 456另一个[[“酒吧”]]
    ## [1] 456
  • 所有环境都有一个“父”环境

    parent.env (env)
    ## 
    • 特殊的环境:emptyenv ()
    • 父环境在构造期间设置(参数为new.env ()
  • 环境可以作为链表链接在一起

    顶部<-新。Env (parent = emptyenv()) mid <- new。Env (parent = top) bot <- new。环境(父=中)
  • 符号(键)查找

    前[[“顶级”]]< - 1年中[["中期"]]< - 2机器人[[“bot”]]< - 3所示
  • 在参考环境中没有找到的符号将在父环境中查找

    (“机器人”,机器人)
    ## [1]
    (“中期”,机器人)
    ## [1] 2
    tryCatch({get("mid", bot, inherit =FALSE) #限制搜索当前环境},错误=条件消息)
    ##[1] "对象'mid' not found"
    bot[["mid"]]] # NULL;'[['限制当前环境
    # #空
  • 赋值给当前环境

    Bot [["mid"]] <- 5 Bot [["mid"]]
    ## [1]
    中期[["中期"]]
    ## [1] 2
  • 功能与环境密切相关

    Fun <- function() {environment()} Fun() #环境定义函数体
    ## 
    Fun() #新函数,新环境
    ## 
  • 函数中的环境会发生什么变化?

    • 当不再被引用时,垃圾回收(稍后!
  • 如果一个函数体有一个环境,就必须有一个父环境

    Fun <- function() {env <- environment() list(env, parent.env(env))} Fun ()
    ([1]) # # # # < x4bb9690环境:0 > ## ## [[ 2]] # # <环境:R_GlobalEnv >
    • 函数的父环境是否是函数所在的环境,或函数所在的环境定义?
    Fun <- function() {fun1 <- function() {env1 <- environment() #由fun1创建的环境par1 <- parent.env(env1) #父环境列表(env1 = env1, par1 = par1)} env <- environment() #由Fun par创建的环境<- parent.env(env)列表(env = env, par = par, fun1=fun1)} Fun ()
    ## $env ##  ## ## $par ##  ## ## $fun1 ## function () ## {# env1 <- environment() ## par1 <- parent.env(env1) ## list(env1 = env1, par1 = par1) ##} ## 
    • 函数的环境的父类就是环境它在其中被定义(与它的命名环境不同)

    • 查找、赋值(<-)和赋值到父环境(<<-

    Fun = function() {x0 <- 0 x1 <- 0 fun1 <- function() {y <- 1 #创建局部变量y x1 <<- 2 #赋给父环境(s)中第一个出现'x'的地方z <<- 3 #父环境(s)中没有z,因此在top环境中创建c(x0 = x0, x1 = x1, y = y, z = z)} result <- fun1() list(x0 = x0, x1 = x1, result = result)}
  • 这叫做词法作用域

  • 什么时候使用词法范围?

包名称空间、导入和search ()

  • search()路径

    search ()
    ##[1]”。GlobalEnv“包:微基准”##[3]“包:测试”“包:BiocStyle”##[5]“包:stats”“包:图形”##[7]“包:grDevices”“包:utils”##[9]“包:数据集”“包:方法”##[11]“自动加载”“包:基础”
  • 库(BiocGenerics):装载和装载包裹

    1. 包命名空间为加载进入水流R会话

    2. 包含从包中导出的符号的环境为附加R搜索路径

  • “附加”一个包意味着.GlobalEnv的父文件指向新包导出的符号环境。新包导出的符号环境指向先前由所指向的包.GlobalEnv

  • 包由NAMESPACE组成

    • 命名空间是一个环境。
    • NAMESPACE的父类是一个包含导入符号的环境。
    • 具有导入符号的环境的父环境是基本环境。
    • 基本环境的父类是.GlobalEnv
    • 等等,沿着搜索路径。
  • 包中的符号解析遵循词法范围

    • 在函数中解析,然后打包命名空间,然后导入环境,然后导入基础.GlobalEnv,那么……

评价,懒惰和不规范的评价

1.3.1练习

练习:银行账户

  • 恭喜你,你拥有一家银行!下面是一个函数,它创建一个帐户,并提供对该帐户的访问

    账户<-函数(){余额<- 0可用<-函数(){}存款<-函数(amt){}提取<-函数(amt){}列表(可用=可用,存款=存款,提取=提取)}

答:

账户<-函数(){余额<- 0可用<-函数(){余额}存款<-函数(amt){余额<<-余额+ amt余额}提现<-函数(amt){如果(amt >余额)停止(“资金不足”)存款(-amt)}列表(可用=可用,存款=存款,提现=提现)}
  • 实现以下测试用例,使用词汇作用域的概念:

    library(testthat) test_that("I understand lexical scope", {acct1 <- account() expect_equal(acct1$deposit(10), 10) expect_equal(acct1$deposit(12), 22) expect_equal(acct1$available(), 22) expect_equal(acct1$withdraw(20), 2) expect_error(acct1$withdraw(20), "资金不足")})
  • 如果你有了第二个客户会怎么样?

    Test_that("函数创建本地作用域",{acct1 <- account();Acct1 $deposit(10) acct2 <- account();Acct2 $deposit(20) expect_equal(acct1$available(), 10) expect_equal(Acct2 $available(), 20)})

答:

账户<-函数(){余额<- 0可用<-函数(){余额}存款<-函数(amt){余额<<-余额+ amt乐趣}提取<-函数(amt){如果(amt >余额)停止(“资金不足”)存款(-amt)}乐趣=列表(可用=可用,存款=存款,提取=提取)乐趣}

练习:银行账户2

  • 需要对实现进行哪些更改才能允许

    Test_that ("endomorphisms rock", {expect_equal(account()$deposit(10)$withdraw(5)$available(), 5)})

练习:package-local选项

  • 实现用户可以通过函数设置的“package-local”选项。通过使用本地()创建并填充包含包局部变量和访问器函数的环境get ()而且设置()检索/赋值变量,例如:宽容.这里是一个开始,还有一些单元测试

    .myoptions <- local({公差= 1e-4 get = function(){公差}set = function(value){} ##…}) ##环境耐受=…

答:

.myoptions <- local({公差= 1e-4 get = function()公差列表(get = get, set = function(value) {ovalue <- get()公差<- value ovalue})}) getTolerance <- .myoptions$get settolance <- .myoptions$set
  • 这里有一些单元测试

    test_that("本地选项可以设置",{expect_equal(getTolerance(), 1e-4) ## setTolerance()返回之前的值,以方便重置expect_equal(otol <- setTolerance(1e-5), 1e-4) expect_equal(setTolerance(otol), 1e-5)})

1.4调试

F <- function(x){##…G ()} G <- function(x){##…if (log(x) < 0) {TRUE} else {FALSE}}
  • 调用回溯()后的错误。

    f(1)回溯()
  • debugonce (g):当函数被调用时,进入' browser '。看到浏览器吗?为了能做的事。变体:调试(g)...undebug (g)

    debugonce(g) f(-1) ##浏览器[2]> ##…
  • 浏览器().编辑源代码(对于复杂或包代码不太实用)

    G <- function(x){##…browser() if (log(x) < 0) {TRUE} else {FALSE}}
  • 选项(错误=恢复).当完成时,选项(错误= NULL).实际上,任何函数接受0参数是可能的recover,例如:选项(错误=回溯)

    f(-1) ##错误:所有(x >= 0)不是TRUE ## ##输入帧号,或0退出## ## 1:f(-1) ## 2: stopifnot(x >= 0)) ## ##选择:2 ##调用从:f(-1) ##浏览[1]> c ## ##输入帧号,或0退出## ## 1:f(-1) ## 2: #2: stopifnot(所有(x >= 0)) ## ##选择:
  • trace ()-对跟踪执行特别有用(例如,Tracer = quote(print(argname))),对于S4方法(参数签名=

    ## like debug trace(g, trace =browser) f(-1)
    Trace (g, quote({print(x);Print (log(x))})) for (i in runif(1000, - 1,100)) f(i) trace(g, quote(if (x < 0) browser()) for (i in runif(1000, - 1,100)) f(i)
    library(GenomicRanges) showMethods("findOverlaps") selectMethod("findOverlaps", c("GRanges", "GRanges")) trace(findOverlaps, browser, signature=c("GenomicRanges", "GenomicRanges"))

发现了一个漏洞,现在怎么办?

  • 更优雅地恢复。(从某种程度上说,这不是一个好的解决方案——宁愿避免问题,也不愿治疗症状)。

    G <- function(x){##…tryCatch({if (log(x) < 0) TRUE else FALSE},错误=函数(e) {NA})
  • 更聪明/健壮地编程-在我们的简单示例中如果()是无关的,并引入一个神秘的错误消息!

    G <- function(x){##…log(x) < 0 # NA,而不是if (NA) (!)
  • 最佳方法:提出前提条件(并更巧妙地编程)。

    f (x) < -函数{stopifnot (is.numeric (x) (x) = = 1,长度! is.na (x) x > = 0) # #……g (x)}

1.5

结构:具有特定文件和结构的简单目录

  • 描述和命名空间文件(必需)
    • 描述:描述包的键值对,例如,' Title: ', ' Version: ', ' License: ',等等。
  • R /目录
    • 包含函数的文件
    • 遵循具有一致格式的逻辑组织
  • 人/目录
    • 文件导入。采访的形式
    • 与R/ directory, R/foo并行结构。R - > R/foo。理查德·道金斯
  • 小插曲/目录
    • 一个或多个小插图,R markdown (.Rmd)或LaTeX / Sweave (.Rnw)
    • Markdown更适合于面向用户的文档。
    • 我的偏好:没有“派生”文件,例如,从Rmd生成的HTML。
  • 本月/
    • data/: R对象,例如,'。rds的文件。
    • extdata/:非r类型的数据资源,如SQL数据库。
    • …/:任何其他需要_inst_installed到R图书馆。
  • src/: C, c++, Fortran或其他“外部”代码。

创建

  • devtools::创建(“MyPackage”)

发展

  • devtools: load_all ()减少编辑-安装-运行循环。
  • devtools: use_testthat ()而且devtools::测试()testthat单元测试。
  • devtools:文档()roxygen2)查阅文件。
  • devtools::测试()(运行单元测试);devtools::检查()
  • devtools:安装()

注意:

  • 所有以上步骤(包创建,安装,编写。rd文档文件,构建,检查)都可以从零开始“轻松”完成。
  • 最后仲裁者:

    R CMD建立mypackag#源代码版本为0.99.1的包R CMD检查MyPackage_0.99.1.tar.gz

管理

2寻找内在R: C语言API和实现

2.1幕后

S-expressions

  • R“原子”向量(数字()逻辑(),等等),列表,环境,函数等等,都是用C来表示的结构体年代
  • C结构体被称为“s表达式”,由符号表示饱和度指数(我读作' S-exp ')
  • 饱和度指数定义为Rinternals.h
  • 一个饱和度指数是多态的-结构的解释取决于类型信息包含在饱和度指数本身。类型被列举为INTSXPREALSXP,……
    • STRSXP字符(),字符向量的每个元素都是aCHARSXP
    • VECSXP列表()LISTSXP是一个“对列表”,主要用于表示函数参数。
    • EXTPTRSXP:一个R中未表示的任意数据的引用R例如,c++类。
  • 所有饱和度指数包含sexpinfo,具有命名、垃圾收集、标记等状态,以及实际数据(例如,一个的整数整型()向量。
  • 宏(实际上,功能控件的访问饱和度指数数据结构。几个风格
    • 例如,长度()Rf_length ()
    • 一般来说,更喜欢Rf_ *风格。

RC

  • 〇多条路线internal ().External (). c ()打电话给(),……
  • . c ()R强制是否有效R类型,例如,整型()C类型,int *;适用于许多轻量级用途。
    • 字符()更复杂的物体更难处理。
    • 返回值必须从分配和传递R
  • 打电话给()R参数表示为饱和度指数
    • 灵活的;重点严肃发展。
    • Rcpp使用此约定,但包装的细节R饱和度指数在c++语言中。

公共API

2.2学习外语的老派方法

什么时候该担心?

  • 访问第三方库
  • 新颖的算法
  • 简单的推理
  • 拷贝免费操作

. c ()

打电话给()

2.2.1例如:uuid

  • Uuid:全局唯一标识。
  • 所提供的提高uuidc++库。
  1. 创建一个包和“src”目录。添加链接:类提供的(c++)头文件黑洞(' Boost Headers ')包。

    devtools::create("/tmp/uuid", list(linkto = "BH"))
    ##在/tmp目录下创建uuid包
    ##没有发现描述。用价值创造:
    ##包:uuid ##标题:包的功能(一行,标题大小写)##版本:0.0.0.9000 ## Authors@R: person("First", "Last", email = "first.last@example.com", role = c("aut", "cre")) ##描述:包的功能(一段)##取决于:R(>= 3.4.0) ##许可:它在什么许可下?##编码:UTF-8 ## LazyData: true ##链接到:BH
    ## *创建uuid。Rproj` from template.
    ## *添加' . rproj。用户','。Rhistory`, `.RData` to ./.gitignore
    dir.create(“/ tmp / uuid / src”)
  2. 编写单元测试

    devtools:: use_testthat(“/ tmp / uuid”)
    ## *添加测试建议
    ## *创建“tests/testthat”。
    ## *创建“tests/testthat.”R '来自template。
    cat(' context("uuid") test_that("uuid返回不同的值",{expect_true(is.character(uuid())) expect_true(长度(uuid()) == 1L) uu <- unique(unlist(复制(100,uuid()))) expect_same(长度(uu), 100L)})', file = "/tmp/uuid/tests/testthat/test_uuid. txt /uuid . txt /uuid . txt ")R”)
  3. 实现c++层

    Cat (' #include  #include  static boost::uuids::random_generator uuid_generator = boost::uuids::random_generator();Std::string uuid_generate(){返回boost::uuids::to_string(uuid_generator());} ', file = "/tmp/uuid/src/uuid.cpp")
  4. 实现从c++到的接口R的C层。

    cat(' #include  SEXP uuid(){返回mkString(uuid_generate().c_str());} ', file = "/tmp/uuid/src/uuid.cpp", append = TRUE)
  5. 实现接口CR

    cat(' #include  extern "C" {static const R_CallMethodDef callMethods[] ={{"。uuid", (DL_FUNC) &uuid, 0}, {NULL, NULL, 0}};void R_init_uuid(DllInfo *info) {R_registerRoutines(info, NULL, callMethods, NULL, NULL);}}', file = "/tmp/uuid/src/uuid.cpp", append = TRUE)
  6. 中实现最终用户APIR

    cat(" #' @useDynLib uuid, .registration = TRUE #' @export uuid <- function() .Call(.uuid), file = "/tmp/uuid/R/uuid. "R”)
  7. 文档,测试,安装,使用!

    devtools:文档(“/ tmp / uuid”)
    ##更新uuid文档
    ##加载uuid
    ##重新编译uuid
    ## '/home/mtmorgan/bin/R-3-4-branch/bin/R'——no-site-file——no-environ \ ##——no-save——no-restore——quiet CMD安装'/tmp/uuid' \ ##——library='/tmp/RtmpPZvmxO/devtools_install_20c366d77c59'——no-R \ ##——no-data——no-help——no-demo——no-inst——no-docs——no-exec \ ##——no-multiarch——no-test-load
    # #
    ##更新/tmp/uuid/DESCRIPTION下的roxygen版本
    ##写入命名空间
    devtools::测试(“/ tmp / uuid”)
    ##加载uuid
    ##测试uuid
    ## uuid:…# # # #  ========================================================================
    devtools::安装(“/ tmp / uuid”)
    ##安装uuid
    ## '/home/mtmorgan/bin/R-3-4-branch/bin/R'——no-site-file——no-environ \ ##——no-save——no-restore——quiet CMD安装'/tmp/uuid' \ ##——library='/home/mtmorgan/R/x86_64-pc-linux-gnu-library/3.4- bios -3.6' \ ##——INSTALL -tests
    # #
    ##重新加载已安装的uuid

2.2.2练习

添加以下单元测试并实现适当的功能

test_that("uuid返回多个值",{n <- 5 result <- uuid(5) expect_true(is.character(result)) expect_same (length(result), 5L) expect_same (length(unique(result)), 5L)})

从“简单”到“难”,分几个阶段来做

  1. 实现R通过添加参数来建立基础设施n对的定义R层次的uuid ().提供n使用默认值,以便原始单元测试继续工作,即devtools::测试(“/ tmp / uuid”)还是通过了最初的测试

  2. 传递参数nC.做加法nR层次的打电话给().然后修改c级uuid ()接受SEXP作为参数,因此签名为SEXP uuid(SEXP n_sexp).更新callMethods []数组,这样uuid函数有一个参数。测试源代码编译时使用的方法,例如,devtools::安装(“/ tmp / uuid”)或者再次运行单元测试。

  3. C、修改uuid ()检查实参是否为标量整数。通过使用宏来做到这一点吗IS_SCALAR ()和功能Rf_asInteger ()常数R_NaInt;查看头文件' Rinternals.h 'R.home(包括).用Rf_error ().您将添加如下代码

    bool test = IS_SCALAR(n_sexp, INTSXP) && (R_NaInt != Rf_asInteger(n_sexp));if (!test) Rf_error("'n'不能被强制为整数(1)而不是NA");int n = Rf_asInteger(n_sexp);
  4. 修改uuid ()分配一个SEXP来包含结果-一个长度的STRSXPn.这个已分配的SEXP需要防止垃圾收集,因为它是一个R内存分配与符号无关。保护是由保护()宏,它被传统地封装在返回SEXP的函数周围。

    SEXP结果=保护(Rf_allocVector(n, STRSXP));
  5. 使用C循环填充返回向量,为使用的每个元素构造CHARSXPmkChar ()和设置的第Th元素结果使用函数SET_STRING_ELT ().尽管在R在level中,向量的第一个元素是元素1,在C中,第一个元素是0。

    For (int I = 0;I < n;++i) SET_STRING_ELT(result, i, mkChar(uuid_generate().c_str()));
  6. 我们的任务完成了;使用解除()删除已分配的SEXP(在本例中为调用函数)上的保护R它本身负责保护它接收到的任何SEXP),并将结果返回给R

    解除(1);返回结果;
  7. (可选)对我们的用户来说,强迫他们R程度的参数n到一个整数(例如,它们可以提供5而不是5 l作为论证)。这可以在R使用水平as.integer (),或在C级使用以下。考虑每种方法的优点。

    n_sexp = PROTECT(Rf_coerceVector(n_sexp, INTSXP));

2.3Rcpp

  1. 使用。创建新包Rcpp: Rcpp.package.skeleton ().编辑要包含的DESCRIPTION文件黑洞链接:字段。

  2. 添加文件src / rcpp_uuid.cpp与助推相关的代码

    #include  #include  static boost::uuids::random_generator uuid_generator = boost::uuids::random_generator();Std::string uuid_generate(){返回boost::uuids::to_string(uuid_generator());}
  3. 包括Rcpp.h头,并使用Rcpp命名空间

    #包含使用命名空间Rcpp;
  4. 使用Rcpp结构实现函数,如CharacterVector;不需要担心强迫论点,保护等。装饰功能/ / [[Rcpp:出口]]属性。

    // [[Rcpp::export]] CharacterVector rcpp_uuid(int n) {CharacterVector uuids(n);For (int I = 0;I < uuids.size();++i) uuids[i] = uuid_generate();返回uuid;}
  5. R,使Rcpp处理属性;看看src / RcppExports.cpp而且R / RcppExports。R

> Rcpp: compileAttributes ()

  1. 安装和使用!

    Rcppuuid$ R CMD安装。[…] rcpppuuid $ R——vanilla -e " rcpppuuid::rcpp_uuid(3)"> rcpp_uuid::rcpp_uuid(3) [1] "cb9cc7e8-bfcb-4262-b05b-2f1c0a4bc196" [2] "7b592e1f-8341-4da0-af04-b387726af8ea" [3] "7b2ade09-6ff2-4e76-b564-31b1a626e864"

2.4c级调试很有趣(?!

设置

  • 广东发展银行在linux上,lldb在Mac;[gdb-to-lldb][gdb-lldb]命令映射
  • 编译的代码调试符号和没有优化
  • 一种方法:

    $ cat ~/。R/Makevars CFLAGS = -g -O0 CXXFLAGS = -g -O0 CXX11FLAGS = -g -O0
  • 选择:配置R用适当的CFLAGS等。

整体

  • 从GitHub获取并安装uuid包的副本

    $ git克隆https://github.com/Bioconductor/BiocAdvanced $ cd BiocAdvanced $ git checkout苏黎世-2017 $ cd inst/uuid $ R CMD安装。
  • 开始R并启动调试器

    $ R -d gdb
  • 启动R处理、加载包等,进入调试器

    (gdb) run > library(uuid) > uuid()错误在uuid():无效的类型/长度(符号/16)在向量分配
  • 进入调试器,将断点设置在R抛出错误,并继续

    > ^C (gdb) break Rf_error (gdb) continue >
  • 触发错误

    > uuid()断点1,Rf_error (format=0x643020 "invalid type/length (%s/%d) in vector allocation") at /home/mtmorgan/src/ r -3-4-branch/src/main/errors.c:821 821 {(gdb)
  • 发现在哪里您在调用堆栈中。大部分时间我们都在R的源代码,不太可能有bug(虽然不是不可能!

    (gdb) where #0 Rf_error (format=0x643020 "invalid type/length (%s/%d) in vector allocation") at /home/mtmorgan/src -3-4-branch/src/main/errors.c:821 #1 0x0000000000525dbc in Rf_allocVector3 (type=1, length=16, allocator=0x0) at /home/mtmorgan/src -3-4-branch/src/main/memory.c:2612 #2 0x000000000050e5c0 in Rf_allocVector (type=1, length=16, allocator=0x0)长度=16)at /home/mtmorgan/src/ r -3-4-branch/src/include/ rinlinedfun .h:196 #3 0x00007fffed786780 in uuid(n_sexp=0x21a16a8) at uuid.cpp:26 #4 0x000000000048b7e3 in R_doDotCall (ofun=0x7fffed7866ac , nargs=1, cargs=0x7fffffffbdd0, call=0x19f7d48) at /home/mtmorgan/src/ r -3-4-branch/src/main/dotcode.c:570
  • 导航和环顾四周

3.确认

本课程中报告的研究得到了国家人类基因组研究所和国家卫生研究院国家癌症研究所的支持,资助编号为U41HG004059和U24CA180996。

该项目已获得欧盟地平线2020研究和创新计划(资助协议编号633974)下欧洲研究委员会(ERC)的资助。