为什么R中的.Call缓慢?

时间:2017-09-28 11:59:21

标签: r haskell dll

我用Haskell创建了一个DLL。它导出两个函数:

  • readXLSX:将xlsx文件名,工作表名称作为参数,并在R列表中返回工作表的内容

  • readXLSXbig:没有参数,这与readXLSX的功能相同,但它始终读取名为Sheet 1 big.xlsx的工作表>

首先,让我们在一个小文件上尝试readXLSX

> library(readxl)
> library(microbenchmark)
> microbenchmark(
+   readxl = read_xlsx("example.xlsx", "Sheet1", col_types="list", col_names=FALSE),
+   haskell = .Call("readXLSX", "example.xlsx", "Sheet1"),
+   times=2
+ )
Unit: milliseconds
    expr      min       lq     mean   median        uq       max neval cld
  readxl 9.294365 9.294365 9.660375 9.660375 10.026385 10.026385     2   b
 haskell 2.164942 2.164942 2.593681 2.593681  3.022419  3.022419     2  a 

嗯,它比readxl更快(不足为奇,因为readxl提供了更多功能,并测试了文件是否存在等。)

现在,让我们尝试更大的文件big.xlsx

> microbenchmark(
+   readxl = read_xlsx("big.xlsx", "Sheet 1", col_types="list", col_names=FALSE),
+   H1 = .Call("readXLSXbig"),
+   H2 = .Call("readXLSX", "big.xlsx", "Sheet 1"), 
+   times=2
+ )
Unit: milliseconds
   expr        min         lq       mean     median        uq       max neval cld
 readxl  143.74213  143.74213  144.05025  144.05025  144.3584  144.3584     2  a 
     H1   33.38596   33.38596   68.60333   68.60333  103.8207  103.8207     2  a 
     H2 8845.17038 8845.17038 9149.19542 9149.19542 9453.2205 9453.2205     2   b

事实上,这个基准测试的结果变化很大(我不知道为什么),但我总是观察到相同的行为:readXLSXbigreadxl和{{1}更快很慢。

让我回想一下,readXLSXreadXLSXbig完全相同;唯一的区别是readXLSX已经提供了文件名big.xlsx和工作表名称Sheet 1

readXLSXbig

为什么会出现这样的差异? readXLSXbig :: SEXP V 'R.Vector readXLSXbig = unsafePerformIO $ do fcellmap <- xlsxSheetToFormattedCellMap "big.xlsx" "Sheet 1" formattedCellMapToRList fcellmap fcellToCellValue 0 readXLSX :: SEXP s 'R.String -> SEXP s 'R.String -> SEXP V 'R.Vector readXLSX file sheet = unsafePerformIO $ do fcellmap <- xlsxSheetToFormattedCellMap (fromSEXP file) (fromSEXP sheet) formattedCellMapToRList fcellmap fcellToCellValue 0 在大文件上需要大约9秒,而readXLSX只需要大约70毫秒。在小文件readXLSXbig上,我们看到example.xlsx需要<3毫秒,因此没有浪费9秒来读取文件名和工作表名称。

1 个答案:

答案 0 :(得分:3)

TL; DR:unsafe内容为unsafe

此代码

readXLSXbig :: SEXP V 'R.Vector
readXLSXbig = unsafePerformIO $ do ...

声明一个常量表达式,它很可能在整个程序中只被评估一次。第一个之后的每个后续调用都可以重新执行do ...操作,或者返回先前计算的值而不重新执行任何操作。

基本上,GHC正在缓存整个电子表格。

unsafePerformIO action告诉编译器我们不关心运行action的时间或运行次数,因为IO action永远不会执行重要的一面效果,并将始终返回相同的结果。我们告诉编译器&#34;我知道这可能不安全,但无论如何都要这样做 - 我将承担责任&#34;。

作为一个丑陋,丑陋的修复,你可以添加一个虚拟参数,并祈祷GHC不会缓存以前的结果。

实际的修复方法是删除unsafe内容,然后返回正确的IO (...)类型。通过这种方式,我们不会谎言到编译器,这将做正确的事情。 FFI机器仍然可以从R中调用。