【问题标题】:What is the most elegant way to convert n-bit data stored in a matrix to integer?将存储在矩阵中的 n 位数据转换为整数的最优雅方法是什么?
【发布时间】:2022-10-03 03:15:58
【问题描述】:

我正在努力以快速的方式从二进制文件中提取信息,而不使用特殊方法,而不能在稍后阶段在另一个上下文中回收代码。

我的实际用例包括来自 GWS 的二元降水雷达数据。如果您愿意,您可以从here 中选择任何解压文件。如果您获得了实际文件,这里是我到目前为止使用的代码。基本上,我正在使用readBin() |> rawToBits() |> matrix()

file <- \"raa01-ry_10000-2207250530-dwd---bin\"

con <- file(file, \"rb\") 

# Read ascii header
meta <- readBin(con, what = raw(), n = 141, endian = \"little\") |> rawToChar()

# Read 2-byte data, dim = 900*900
data <- readBin(con, what = raw(), n = 900*900 * 2, endian = \"little\")

close(con)

# Set dimensions
dim(data) <- c(2, 900*900)

class(data)
#> [1] \"matrix\" \"array\"
typeof(data)
#> [1] \"raw\"

# Create a matrix with 16 columns
bits <- rawToBits(data) |> matrix(ncol = 16, byrow = TRUE)

class(bits)
#> [1] \"matrix\" \"array\"
typeof(bits)
#> [1] \"raw\"
dim(bits)
#> [1] 810000     16

否则,这里是head(bits) |&gt; dput() 的输出:

bits <- structure(as.raw(c(0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 
0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 
0x01)), dim = c(6L, 16L))

数据仅存储在前 12 位中,后 4 位用于标记。但也有 1 字节产品,其中所有位都用于数据存储。所以我认为我需要一些灵活性。

packBits(\"integer\") 似乎只接受 32 位数据。但是,我能够使用as.raw(0) |&gt; rep() |&gt; append() |&gt; packBits()-pipe 和apply() 在矩阵行上使用此函数将我的 12 位数据扩展到 32 位:

bits2int <- function(x) {
  
  fill <- as.raw(0) |> rep(20)
  
  append(x, fill) |> packBits(\"integer\")
}

result <- apply(bits[, 1:12], 1, bits2int)

head(result)
#> [1] 1027 1065 1065 1065 1065 1065

在线下,这种方法有效,但它需要大约。每个文件 12 秒,这太长了。考虑到 810,000 次迭代,一点也不奇怪。

想出一个可以应用于矩阵并逐列迭代执行一些as.numeric(x[,i])* 2^(i-1) 魔术并最终返回总和之类的函数可能会更有意义。所以这就是我现在卡住的地方。

但也许我只是错过了一些明显的东西,所以我对答案很好奇。

非常感谢您!

PS:您可以通过例如可视化结果matrix(result, ncol = 900) |&gt; terra::rast() |&gt; terra::plot() 如果您使用实际文件。

编辑1:

我想我会在这里提到 cmets 中给出的附加信息:

dwdradar 目前使用 Fortran 例程来导入 Radolan 数据。代码中列出了一个approach using R 以供进一步参考,但它似乎要慢得多。所以基本上,考虑到这个现有的代码,我想知道是否有办法让 R 方法 a) 更快 b) b2n(1)+b2n(2)+.... 部分更灵活地适用于 n 位数据。

编辑2:

处理完 cmets 中提供的附加材料后,我想我需要一个与 Fortran 的 IBITS() 等效的具有 positionlength 参数的可用参数。但我认为这可能是一个更具体的后续问题。现在,我将继续筛选现有的方法。

  • 在我的电脑上,初始化矩阵和按列操作从大约 10.5 秒减少到 8.5 秒
  • 您是否尝试过rdwddwdradar,或者这是一个不同的挑战?无论如何,我喜欢你在他们不在的情况下的工作流程。
  • 感谢您的指点。实际上,这就是我要问的原因。 dwdradar 使用 Fortran 例程进行导入。列出了一种使用 R 的方法,但它似乎要慢得多。所以基本上,考虑到这段代码,我想知道是否有办法让 R 方法更快,并且 `b2n(1)+b2n(2)+....` 部分更灵活以适用于 n-位数据。
  • 注意到 github brry 关注速度,效率 lists other radolan,我们看到 KWB-R-ver3 我猜 ver3 的改进最大,其次是(?)to raster ver3,(对我来说还是有点模糊),但是 fortran 例程或 kwb 方法可以让您通过 packBits 填充步骤。如前所述,fortran 比 R 更快。n 位灵活性的用例是什么?
  • 既然你显然是杂食动物,让我推荐omd 供你考虑,

标签: r matrix bit raw


【解决方案1】:

好的,这花了一些时间,因为我一开始专注于brry/ReadBinaryRadarFile,在某些时候意识到brry/dwdradar提供的代码在某种程度上有所不同,所以我不得不重新开始。

但是,让我们仔细看看当前的实现:

1) readRadarFile 调用binary_to_num(Fortran 子程序)@brry/dwdradar:

readBin(openfile, what = ”raw”, n = 900*900*2, endian = "little") 开始,main 函数似乎是IBITS 的方便包装器。似乎IBITS() 正是这里所需要的:

IBITS(I, POS, LEN):从 I 中提取长度为 LEN 的字段,从位位置 POS 开始,向左扩展 LEN 位。结果右对齐,其余位清零。

这样,可以直接提取来自位 1-12 的数据,以及存储在各个位 13、14、15、16 中的标志。

2) readRadarFile 调用 bin2num 调用b2n@brry/dwdradar:

也以readBin(openfile, what = ”raw”, n = 900*900*2, endian = "little") 开头。

R 例程可以缩小到rawToBits(data) |&gt; matrix(ncol = 16, byrow = TRUE),然后是b2n(1)+b2n(2)+…+b2n(12)b2n &lt;- function(i) as.numeric(bits[,i])*2^(i-1)

必须手动构造要提取的位置和长度,而无需对函数参数进行任何调整——从我的角度来看,这不是很方便。

3)read_binary_radolan_file_raw_v3@KWB-R/kwb.dwd:

也使用readBin(),但使用”integer” 模式而不是”raw”

ints &lt;- readBin(openfile, what = "integer", n = 900*900, size = 2, signed = FALSE, endian = "little")

因此,转换为两个字节的整数是在内部执行的。由于readBin在这里采用16位作为输入,实际数据和标志需要追溯分离。这是使用bitwAnd(ints, 0x0fff) 处理数据和bitwAnd(ints, 0xf000) 处理标志来完成的。不确定在最终创建栅格之前是否根据此处的标记信息调整数据,或者只是作为属性保留。

4)­x.radolan.parse@GeoinformationSystems/xtruso_R:

基本上,也使用readBin(what = “integer”),后处理包括基于允许的最小值/最大值生成光栅对象和删除标记值。

5) moc.online.uni-marburg.de 引用的资源似乎不向公众提供,因为 HTTP 403:禁止,目前无法评估。

6) https://gitlab.cs.fau.de/since/radolan 因对 Golang 了解不足,未评估。

基准测试包括从作为输入数据的二进制文件到作为输出数据的栅格对象的转换——哦,这超出了“矩阵中的 n 位数据到整数”的范围——而由于后处理步骤的变化(矩阵旋转,从 rvp6 到 dbZ 到降雨强度,范围的定义和创建的栅格对象的投影,...):

mbm <- microbenchmark::microbenchmark(
  
  "readRadarFile_F @ brry/dwdradar" = readRadarFile_F("raa01-ry_10000-2208041200-dwd---bin")$dat |> raster::raster(),
  "readRadarFile_R @ brry/dwdradar" = readRadarFile_R("raa01-ry_10000-2208041200-dwd---bin")$dat |> raster::raster(),
  "read_binary_radolan_file @ KWB-R/kwb.dwd" = read_binary_radolan_file("raa01-ry_10000-2208041200-dwd---bin"),
  "x.radolan.parse @ GeoinformationSystems/xtruso_R" = x.radolan.parse("raa01-ry_10000-2208041200-dwd---bin"),
  
  times = 100
)

autoplot(mbm)

mbm
#> Unit: milliseconds
#>                                              expr      min        lq      mean    median        uq      max neval
#>                   readRadarFile_F @ brry/dwdradar  27.7828  32.04745  47.73367  38.49400  41.73485 409.6813   100
#>                   readRadarFile_R @ brry/dwdradar 133.8004 144.87255 192.51376 150.62500 162.99490 566.4873   100
#>          read_binary_radolan_file @ KWB-R/kwb.dwd  41.4600  44.02860  48.17945  46.44105  50.39170  81.1589   100
#>  x.radolan.parse @ GeoinformationSystems/xtruso_R 280.3148 301.48180 357.14467 313.21170 330.93485 704.8718   100

看看中位执行时间, binary_to_num() (Fortran) 最快,大约 38 毫秒,正如预期的那样。从我的角度来看,使用IBITS() 并考虑可用参数似乎也很干净,但需要编译。如果最后没有光栅转换,子程序需要大约 6 毫秒才能完成。

至少对我来说,最大的惊喜是 KWB 方法的执行时间非常接近 Fortran 例程。尽管使用了相同的转换,但 xtruso 方法最慢,这可能是由于大量的后处理。 b2n() 在没有 xtruso-post-processing 之前是最慢的,现在可以被视为中间层。

初步结论:

  • IBITS() 的 R 实现似乎是解决此问题的一种非常干净的方法,但执行时间可能值得商榷。此外,只要没有使用 R 的现有可比方法,从头开始实施可能会很耗时。

  • readBin(what = “integer”) 回顾性需要更多数据清理,但由于raster 开销(甚至可以使用terra 减少),执行时间似乎与Fortran 子例程相当。

下一步,因为我现在必须停下来:

  • 对来自上述方法的单个 sn-ps 的组合进行基准测试。我的印象是,每种方法都有一些我认为是“最佳实践”的部分,然后还包括一些不太方便/优雅的部分。

【讨论】:

    猜你喜欢
    • 2013-06-08
    • 1970-01-01
    • 2020-09-02
    • 1970-01-01
    • 1970-01-01
    • 2012-07-09
    • 1970-01-01
    • 1970-01-01
    • 2016-08-24
    相关资源
    最近更新 更多