在R中使用bigint id的一些操作

每次开始讨论使用各种数据库作为数据源时,都会出现记录标识符,对象或其他内容的主题。 有时参与者可以考虑交换协议的协调几个月。 int - bigint guid ,然后围成一个圆圈。 对于批量任务,考虑到R本身没有bigint支持(容量bigint ^ 64),因此选择此类标识符的正确表示形式对性能至关重要。 有没有明显且通用的解决方法? 以下是一些可以在项目中用作石蕊测试的实际注意事项。


通常,标识符将用于三类任务:


  • 分组
  • 过滤
  • 协会。

基于此,我们将评估各种方法。


它是以前出版物的延续。


储存为string


小数据的选项非常好。 在这里,可以拾取任意长度的标识符,并且不仅支持数字标识符,还支持字母数字标识符。 另一个优势是可以保证通过任何非本地数据库协议(例如,通过网关的REST API)正确接收数据的能力。


缺点也很明显。 高内存消耗,来自数据库的更多信息量,网络级别和计算级别的性能下降。


我们使用bit64


许多只听过此程序包名称的人可能会认为这是完美的解决方案。 las,这并非完全正确。 这不仅是numeric上的附加内容(引号: ' 同样,选择很明显:R只有一种64位数据类型:双精度。通过使用双精度,
integer64继承了某些功能,例如is.atomic,长度,长度<-,名称,名称<-,dim,dim <-,dimnames,dimnames。 ' ),因此基本算术仍然有很大的扩展,并且不能保证它不会在任何地方爆炸并且不会与其他程序包发生冲突。


我们使用numeric类型


这是一个完全正确的把戏,对于那些知道从数据库int64响应中确切隐藏了什么的人来说,这是一个不错的折衷方案。 毕竟,并不是所有的64位都真正包含在那里。 通常,数字可能会远远小于2 ^ 64。


由于双精度浮点格式的特殊性,这样的解决方案是可能的。 有关详细信息,请参见流行的双精度浮点格式文章。


 The 53-bit significand precision gives from 15 to 17 significant decimal digits precision (2−53 ≈ 1.11 × 10−16). If a decimal string with at most 15 significant digits is converted to IEEE 754 double-precision representation, and then converted back to a decimal string with the same number of digits, the final result should match the original string. If an IEEE 754 double-precision number is converted to a decimal string with at least 17 significant digits, and then converted back to double-precision representation, the final result must match the original number. 

如果标识符中的小数位数为15个或更少,则可以使用numeric ,不必担心。


当您需要使用临时数据(尤其是包含毫秒的临时数据)时,同样的技巧也很好。 通过网络以文本形式传输临时数据需要花费时间,此外,在接收端,您需要运行文本解析器-> POSIXct ,这也是非常耗费资源的(有时会降低性能)。 并不是所有驱动程序都支持时区和毫秒的传输,而是二进制格式的传输。 但是,以unix时间戳记表示形式(在UTC区域中)精确到毫秒的时间传输(小数点后13位)由numeric格式很好且无损地提供了。


没有那么简单和明显


如果我们仔细看一下带有行的版本,则初始语句的明显性和分类性会有所下降。 在R中使用字符串并不是一件容易的事,甚至忽略了对齐内存块和预取的细微差别。 从书本和深入的文档来看,字符串变量本身并不存储在变量中,而是放置在全局字符串池中。 所有行。 字符串数组使用此池来减少内存消耗。 即 文本向量将是全局池中的一组行+指向该池中的记录的链接的向量。


 library(tidyverse) library(magrittr) library(stringi) library(gmp) library(profvis) library(pryr) library(rTRNG) set.seed(46572) RcppParallel::setThreadOptions(numThreads = parallel::detectCores() - 1) #              options(scipen = 10000) options(digits = 14) options(pillar.sigfig = 14) pryr::mem_used() fname <- here::here("output", "dump.csv") #  10^4,       (    +  ) m1 <- sample(stri_rand_strings(100, 64, "[a0-9]"), 10^7, replace = TRUE) #     readr::write_csv(enframe(m1, name = NULL), fname) #       m2 <- readr::read_csv(fname, col_types = "c") %>% pull(value) pryr::object_size(m2) pryr::mem_used() #      print(glue::glue("File size: {fs::file_size(fname)}. ", "Constructed from file object's (m2) size: {fs::fs_bytes(pryr::object_size(m2))}. ", "Pure pointer's size: {fs::fs_bytes(8*length(m2))}")) .Internal(inspect(m1)) .Internal(inspect(m2)) 

我们看到,即使不进入C ++级别,假设也离事实不远。 字符串向量的容量几乎与64位指针的容量一致,并且变量本身占用的空间比磁盘上的文件少得多。


 File size: 62M. Constructed from file object's (m2) size: 7.65M. Pure pointer's size: 7.63M 

向量的内容在写入之前和读取之后是相同的-分别。 向量元素指的是相同的存储块。


因此,仔细研究将文本字符串用作标识符似乎不再是一个疯狂的主意。 使用dplyrdata.table进行分组,过滤和合并的基准,对于numericcharacter标识符的读取结果大致相似,这归因于全局池,从而进一步确认了优化。 毕竟,依赖于汇编R(32/64)的大小为32位或64位的指针的工作正在进行中,而这正是numeric类型。


 #    gc() pryr::mem_used() bench::mark( string = id_df %>% group_by(id_string) %>% summarise(j = prod(j, na.rm = TRUE)), # bit64 = id_df %>% group_by(id_bit64) %>% summarise(j = prod(j, na.rm = TRUE)), numeric = id_df %>% group_by(id_numeric) %>% summarise(j = prod(j, na.rm = TRUE)), # gmp = id_df %>% group_by(id_gmp) %>% summarise(j = prod(j, na.rm = TRUE)), check = FALSE ) #    gc() pryr::mem_used() string_subset <- sample(unique(id_df$id_string), 20) numeric_subset <- sample(unique(id_df$id_numeric), 20) bench::mark( string = dplyr::filter(id_df, id_string %in% string_subset), numeric = dplyr::filter(id_df, id_numeric %in% numeric_subset), check = FALSE ) #    gc() pryr::mem_used() #        string_copy_df <- rlang::duplicate(dplyr::count(id_df, id_string)) numeric_copy_df <- rlang::duplicate(dplyr::count(id_df, id_numeric)) bench::mark( string = id_df %>% dplyr::left_join(string_copy_df, by = "id_string"), numeric = id_df %>% dplyr::left_join(numeric_copy_df, by = "id_numeric"), iterations = 10, check = FALSE ) 

顺便说一句,可以使用fs::fs_bytes(memory.limit())查看可用R内存的最大大小。


老实说,应该注意dplyr并不总是具有快速的dplyr ,请参见“通过字符列进行连接比通过因子列进行连接要慢。#1386 {Closed}” 。 该线程建议使用全局字符串池的功能,并且不比较字符串本身,而是比较指向字符串的指针。


内存管理详细信息


基本资料



结论


自然,这个问题经常以一种或另一种形式在下面的许多链接中提出。



但是,为了有意识地了解正确的做法,存在的机会和局限性,最好降低到最低的水平。 事实证明,有很多不明显的细节。 该出版物具有研究性质,因为它影响R的相当基本的方面,很少有人在日常工作中使用。 如果有重大的补充,评论或更正,了解它们将非常有趣。


以前的出版物是“将R用于实用程序任务”

Source: https://habr.com/ru/post/zh-CN466829/


All Articles