1 / 34

Writing R Extensions using Terra

rterra. Writing R Extensions using Terra. saptarshi guha, july,2013. The Case for R Extensions. Take advantage of libraries written in other languages e.g. C,C++, and Fortran Performance of algorithms written in R is not often the speediest, especially for scalar manipulations

yagil
Download Presentation

Writing R Extensions using Terra

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. rterra Writing R Extensions using Terra saptarshi guha, july,2013

  2. The Case for R Extensions • Take advantage of libraries written in other languages e.g. C,C++, and Fortran • Performance of algorithms written in R is not often the speediest, especially for scalar manipulations • ... users recode the 'hot-spots' in C,C++, and/or Fortran And sometimes users switch to other languages (which may be for reasons other than speed)

  3. Writing R Extensions • R has a well documented C API • which has boilerplate though is 'fairly straightforward' to use • Rcpp is a C++ wrapper around the C API making the latter a pleasure to use • Rcpp also has an inline feature: the user need not compile code, Rcpp handles it automatically

  4. Writing R Extensions R's C API SEXP mymethod(SEXP in){ SEXP ab; PROTECT(ab = allocVector(STRSXP, 2)); SET_STRING_ELT( ab, 0, mkChar("foo") ); SET_STRING_ELT( ab, 1, mkChar("bar") ); UNPROTECT(1); return(ab); } Rcpp API SEXP mymethod(SEXP in){ Rcpp::CharacterVector ab(2); ab[0] = "foo"; ab[1] = "bar"; return(ab); }

  5. Why Lua (and Terra) • Not everyone is very comfortable in C (or C++) • Yet would like their code to be 'reasonably performant' • Why not write extensions in another language that is dynamic, small and fast? • and integrates nicely with C (e.g. call C libraries)

  6. Terra • Research project by Zach Devito, from the website • low-level system programming language that is designed to interoperate seamlessly with the Lua programming language • statically-typed, compiled language(via Clang) with manual memory management. Use Terra for • Write extension code in Lua, switch to Terra functions for predictable performance (C performance) when needed • Terra uses LuaJIT as the Lua engine. Can be as fast as C • Extend the syntax(build your own DSL): Terra can switch to different parsing engines

  7. Very Quick Introduction to Lua • 15 minute introduction: http://tylerneylon.com/a/learn-lua/ • A short example Dog = {} -- 1.functionDog:new() -- 2. newObj = {sound = 'woof'} -- 3. self.__index = self -- 4. self.__eq = DogsEqualFunction(a,b) -- returnsetmetatable(newObj, self) -- 5. end -- functionDog:makeSound() -- 6.print('I say ' .. self.sound) end mrDog = Dog:new() -- same as Dog.new(Dog) mrDog:makeSound() -- 'I say woof' (same as mrDog.makeSound(mrDog)) Lua is dynamic, garbage collected, has closures, lacks any form of classes (though Javascript style prototypes or C++ style classes can be implemented) and has modules (R libraries)

  8. Very Quick Introduction to Lua -- __add(a, b) for a + b-- __sub(a, b) for a - b-- __mul(a, b) for a * b-- __div(a, b) for a / b-- __mod(a, b) for a % b-- __pow(a, b) for a ^ b-- __unm(a) for -a-- __concat(a, b) for a .. b-- __len(a) for #a-- __eq(a, b) for a == b-- __lt(a, b) for a < b-- __le(a, b) for a <= b-- __index(a, b) <fn or a table> for a.b-- __newindex(a, b, c) for a.b = c-- __call(a, ...) for a(...) The above functions can be implemented for sugar e.g. a+b , ,a==b, a/b , a[i]

  9. Using rterra

  10. rterra: Examples • Load the rterra library and 'source' the Terra file • Terra functions are JIT compiled (via Clang). • Lua functions are passed through LuaJIT library(rterra) tinit(clang="/opt/clang3.3/bin/clang") terraFile(system.file("examples","tests.t",package="rterra")) In addition to terraFile: • terraStr and terra (equivalent of .Call) • and terraAddIncludePaths (like -I/usr/include in gcc), terraLinkLibrary (like gcc's -l or dyn.load), and terraAddRequirePaths (to locate custom modules)

  11. rterra: Environments Lua ttests = {} function ttests.testNameSpace(p) local myNS = R.Robj(p) local globalenv = R.Robj(R.constants.GlobalEnv) R.print(globalenv["foo"]) myNS["foo"] = "alpha-gamma" end … return ttests myNS <-new.env() myNS$foo <- 1.234 foo <- 2.45 terra("testNameSpace",myNS,table="ttests") print(list(myNS$foo, foo)) R [1] 2.45 NULL > print(list(myNS$foo, foo)) [[1]] [1] "alpha-gamma" [[2]] [1] 2.45

  12. rterra: Vectors Lua function ttests.makeIntegerVector() local z = R.Robj{type="int",with = {0,21,3,R.constants.NaINTEGER}} z[0] = 31 + z[1] z['whoami'] = "integer vector" return z end terra("makeIntegerVector",table="ttests") R [1] 52 21 3 NA attr(,"whoami") [1] "integer vector"

  13. rterra: Vectors with Attributes Lua stdio = terralib.includec("stdio.h") function ttests.intVectorWithAttr(p,n) local z = R.Robj(p) stdio.printf("Printing the attribute ... ") R.print(z['foo']) local w = R.Robj(n) local g = R.Robj{type='str',with={ w[1] }} -- z['listattr'] = R.Robj{type='list',with={w,w}} z['dooey'] = g end x <- structure(1:5,foo="stuff") y <- as.character(1:4) terra("intVectorWithAttr",x,y,table="ttests") Printing the attribute ... [1] "stuff" NULL > print(x) [1] 1 2 3 4 5 attr(,"foo") [1] "stuff" attr(,"doeey") [1] "3" R

  14. rterra: Matrices Lua require("io") function ttests.matrixTest(s) local m = R.Robj(s) local mview = R.asMatrix(m) for j=0, mview.ncols - 1 do for i = 0, mview.nrows -1 do io.write( mview[{i,j}],"\t" ) end io.write("\n") end end y <- matrix(as.numeric(1:10),ncol=2) print(y) terra("matrixTest",y,table="ttests") > print(y) [,1] [,2] [1,] 1 6 [2,] 2 7 [3,] 3 8 [4,] 4 9 [5,] 5 10 > terra("matrixTest",y,table="ttests") 1 2 3 4 5 6 7 8 9 10 R

  15. rterra: Lists and Calling R Functions Lua function ttests.createVector() local runif=R.makeRFunction("runif",3,R.getNamespace("stats")) local m = R.Robj{type="list",length = 4} m[0] = R.Robj{type='real',with = {1,2}} m[1] = R.Robj{type='real',with = {2,2}} m[2] = R.Robj{type='real',with = {3,2}} m[3] = R.Robj(runif(R.Robj{type='real',with= {10}}.sexp, R.Robj{type='real',with = {-1}}.sexp, R.Robj{type='real',with = {1}}.sexp)) return m end terra("createVector",table="ttests") [[1]] [1] 1 2 [[2]] [1] 2 2 [[3]] [1] 3 2 [[4]] [1] 0.6907007 0.2870886 0.8696195 0.7024621 0.5748302 0.6567022 0.4824151 [8] 0.5233279 0.4983562 0.5210542 R

  16. rterra: How is Terra Involed? • Use Lua to construct Terra functions. • These Terra functions are inlined when used in other Terra functions for a,b in pairs({ {"NilValue"},{"NaSTRING"},{"GlobalEnv"},{"EmptyEnv"},{"BaseEnv"},{"UnboundValue"}, {"NaN",double},{"PosInf",double},{"NegInf",double}, {"NaREAL",double}, {"NaINTEGER",int}, {"NaLOGICAL", int}}) do local fn = b[1] local s s = symbol(b[2] or R.SEXP, "argument") R[ "is" .. fn] = terra([s]) return([s] == R.constants.[fn]) end end

  17. Performance of rterra

  18. rterra: Performance - Gibbs Sampler • Gibbs Sampler did the rounds for speed issues • Terra can import C header files with some caveats e.g. not all #defines are imported gsl = terralib.includecstring [[ #include <gsl/gsl_rng.h> #include <gsl/gsl_randist.h> const gsl_rng_type* get_mt19937(){ return gsl_rng_mt19937; } ]] terralib.linklibrary "libgsl.so" terralib.linklibrary("libgslcblas.so") terralib.linklibrary("gibstest.so")

  19. rterra: Performance - Gibbs Sampler • Lua version function doGibbsJIT(p) local p1 = R.Robj(p) local N,thin = p1[0], p1[1] local r = R.Robj{type='real',length = N*2} local mr = R.asMatrix(r, {N,2}) local rng = gsl.initgsl_rng() local x,y = 0.0,0.0 for i=0,N-1 do for j=0, thin-1 do x=gsl_ran_gamma(rng,3.0,1.0/(y*y+4)); y=1.0/(x+1)+gsl.gsl_ran_gaussian(rng,1.0/cmath.sqrt(2*x+2)) mr[{i,0}],mr[{i,1}] = x,y end end freegsl_rng(rng) return r end

  20. rterra: Performance - Gibbs Sampler • Terra version GSL_RNG_INIT = function(rng) return( quote rng = gsl.gsl_rng_alloc([&gsl.gsl_rng_type] (gsl.get_mt19937())) ffi.gc(rng,gsl.gsl_rng_free) end) end terra _doGibbsTerra(r:&double, N:int, thin:int ) var rng : &gsl.gsl_rng = nil [GSL_RNG_INIT(rng)] -- splicing (like a macro) for i=0,N do for j=0, thin do var x,y = 0.0,0.0 x=gsl.gsl_ran_gamma(rng,3.0,1.0/(y*y+4)); y=1.0/(x+1)+gsl.gsl_ran_gaussian(rng,1.0/cmath.sqrt(2*x+2)) r[i ],r[i+N] = x,y end end end function doGibbsTerra(p) -- call this from R local p1 = R.Robj(p) local N,thin = p1[0], p1[1] local r = R.Robj{type='real',length = N*2} _doGibbsTerra( r.ptr, N,thin) return r end

  21. rterra: Performance - Gibbs Sampler Terra Multi-threaded version local struct A { N: int; thin: int; r:&double; } terra _doGibbsTerra1(r:&double, N:int, thin:int ,numthreads:int) var p = { N = N, thin=thin, r=r} --anonymous struct var threadpool = glib.g_thread_pool_new(_driver,&p,numthreads,0,nil) var integers = [&int](stdlib.malloc(sizeof(int)*N)) for i=0,N do integers[i] = i glib.g_thread_pool_push(threadpool,integers+i,nil) end glib.g_thread_pool_free (threadpool,0,1) stdlib.free(integers) end Structs can have methods attached to them , similar to Lua's metatables

  22. rterra: Performance - Gibbs Sampler N = 50,000 thin = 300

  23. rterra: Performance - Vector Ops • Weighted Mean: Write this in terra (same as C) or LuaJIT (close to C) • For very large vector size or repeated computations we can use vector ops (SSE)

  24. rterra: Performance - Vector Ops terra uvwsum(a : R.SEXP,w:R.SEXP) var a1 = Rt.newReal(a,false) var w1 = Rt.newReal(w,false) var s,ws = 0.0,0.0 for i=0,w1.length do ws = ws + w1:get(i) s = s + w1:get(i)*a1:get(i) end return Rt.newScalarReal(s/ws) end terra wsum(a : R.SEXP,w:R.SEXP) var a1 = Rt.newReal(a,false) var w1 = Rt.newReal(w,false) var regular = w1.length and (- [vecsizel]) var wacc : vector(double, vecsizel) = 0.0 var sacc : vector(double, vecsizel) = 0.0 for index =0, regular,[vecsizel] do var t1 = terralib.attrload([&vector(double,vecsizel)]( w1.ptr + index),{ align = 8 }) var t2 = terralib.attrload([&vector(double,vecsizel)]( a1.ptr + index),{ align = 8 }) wacc = wacc + t1 sacc = sacc + t1*t2 end var wrest ,srest = 0.0,0.0 for i=regular, w1.length do wrest = wrest + w1:get(i) srest = srest + w1:get(i)*a1:get(i) end for i =0, [vecsizel] do wrest = wrest + wacc[i] srest = srest + sacc[i] end return Rt.newScalarReal(srest/wrest) end

  25. rterra: Performance - Vector Ops N = 70MM

  26. rterra: Performance - Cosine Distance • LuaJIT need not be always C speed - 2x-3x quite possible (with nested loops) function doCosineL(mat0) --unoptimized local mat = R.asMatrix(R.newReal{fromSexp = mat0}) local retval = R.Robj{type=’numeric’,length = mat.nrows*mat.nrows} retval["dim"] = R.Roj{type=’int’,with = {mat.nrows, mat.nrows}} local rm = R.asMatrix(retval) for row1 = 0, mat.nrows-1 do for row2=0, mat.nrows-1 do local s,a1,a2 = 0.0, 0.0,0.0 for i=0, mat.ncols-1 do s = s + mat[ {row1,i} ] * mat[ {row2,i} ] a1 = a1 + mat[ {row1,i} ]^2 a2 = a2 + mat[ {row2,i} ]^2 end rm[ {row1,row2} ] = s / (cmath.sqrt(a1*a2)) end end return retval end

  27. rterra: Performance - Cosine Distance N=1500;m <- matrix(as.numeric(1:N^2),ncol=N)

  28. rterra: Lua + QT require 'qtcore' require 'qtgui' function plotData3(a) local scene = QGraphicsScene.new() local aa = R.Robj(a) for i = 0,#aa-1 do local plotElement = aa[i] local plotType = ffi.string(plotElement[0][0]) if plotType == "circle" then local x,y, radius = plotElement[1][0],plotElement[2][0],plotElement[3][0]*5 scene:addEllipse(x,y,radius,radius,pen, brush) elseif plotType == "line" then local x1,y1= plotElement[1][0], plotElement[2][0] local x2,y2=plotElement[3][0],plotElement[4][0] scene:addLine(x1,y1,x2,y2,pen) elseif plotType == "text" then local x,y= plotElement[2][0], plotElement[3][0] local rot = plotElement[4][0] local txt = ffi.string(plotElement[1][0]) local m = scene:addText(txt, font) m:setPos(x,y) m:setRotation(-rot) end end window:setScene(scene) window:show() end

  29. rterra: Lua + QT library(RGraphicsDevice) dev <- myDevice() plot(runif(1000)) ctl <- dev$plots() terraFile(system.file("examples","rqt.t",package="rterra")) terra("qtinit",table="rqt") terraFile(system.file("device","runner.t",package="rterra")) terra("plotData3",ctl[[1]]) Works nicely with R's eventloop

  30. terra: Language Extensions Terra can change to a custom parser mid way e.g. print(sum1,2,3done) let lp = optimize begin minimize productSum(xs,ys) subject to { 0 <= sum(xs[1:2]) == 1, sum(xs) <= 1, sum(xs[1:100]) <= 100, } end One could use this to create a custom DSL • Bugs • Stan

  31. rterra: Summary • Terra is low level and still in research but very usable • Knowing Terra does not mean you can forget C • Segfaults can happen - you are close to the 'metal' rterra • still in development • Iterative style of development is a pleasure • modify code in terra file • source it and run again - no compile stage • If required, I code in Lua. When performance is not good enough, switch to Terra • all in the same file: seamless • Not the equivalent of rJava!

  32. Appendix: Tracing LuaJIT LuaJIT can trace the steps involved in JIT compile stage. See • http://luajit.org/running.html • http://stackoverflow.com/questions/7167566/luajit-2-optimization-guide require'jit.dump' require'jit.v' jit.v.start() jit.dump.start() function moo() local x=0; for i=1,1e6 do x=x+i end; print(x) end moo()

  33. rterra: How is Terra Involed? • Example: Calling R functions from Lua/Terra function R.makeRFunction(fname, len,namespace) local nspace if not namespace == nil then local z1 = Rbase.Rf_ScalarString(Rinternals.Rf_mkChar(namespace)) local z2 = Rbase.Rf_lang2(Rinternals.Rf_install("getNamespace"),z1) nspace = Rbase.Rf_eval(z2,R.constants.GlobalEnv) else nspace = R.constants.GlobalEnv end local langcall = Rbase["Rf_lang" .. (len+1)] local fncall if type(fname)=="string" then fncall = Rbase.Rf_install(fname) elseif Rbase.TYPE(fname) == R.types.FUNSXP then fncall = fname else Rbase.Rf_error("What type of function did you give me?") end local params = {} for i = 1,len do params[i] = symbol(R.SEXP,"argument" ..i) end return terra([params]) var result = Rbase.Rf_eval( langcall( fncall, [params]), nspace) return result end end

More Related