Задача
Я ищу быстрый (в идеале постоянное время) способ взять большой фрагмент длинного необработанного вектора в R. Например:
obj <- raw(2^32)
obj[seq_len(2^31 - 1)]
Даже с ALTREP база R занимает слишком много времени.
system.time(obj[seq_len(2^31 - 1)])
#> user system elapsed
#> 19.470 38.853 148.288
Почему?
Потому что я пытаюсь ускорить storr
на порядок ускорить drake
. Я хочу storr
, чтобы быстрее сохранять длинные исходные векторы. writeBin()
очень быстрый, но по-прежнему не может обрабатывать векторы длиной более 2 ^ 31 - 1 байт . Поэтому я хочу сохранить данные в управляемых фрагментах как , описанное здесь . Это почти работает, но создание чанков происходит слишком медленно и дублирует слишком много данных в памяти.
Идеи
Давайте создадим функцию
slice_raw <- function(obj, from, to) {
# ???
}
, что по существу эквивалентно
obj[seq(from, to, by = 1L)]
и который равен O (1) как во времени, так и в памяти. Теоретически все, что нам нужно сделать, это
- Передача
obj
в функцию C.
- Создать новый указатель на первый байт
obj
.
- Увеличить новый указатель на начало среза.
- Создайте
RAWSXP
в новом указателе соответствующей длины (менее 2 ^ 31 байт).
- Вернуть
RAWSXP
.
У меня есть опыт работы в C, но я изо всех сил пытаюсь получить полный контроль над внутренними элементами R . Я хотел бы получить доступ к указателям C внутри SEXP
s, чтобы я мог сделать арифметику базовых указателей и создать R векторов известной длины из необозначенных указателей C. Ресурсы, которые я нашел на внутренних устройствах R, похоже, не объясняют, как обернуть или развернуть указатели. Нужно ли нам Rcpp
для этого?
Следующий грубый набросок показывает, что я пытаюсь сделать.
library(inline)
sig <- c(
x = "raw", # Long raw vector with more than 2^31 - 1 bytes.
start = "integer", # Should probably be R_xlen_t.
bytes = "integer" # <= 2^31 - 1. Ideally coercible to R_xlen_t.
)
body <- "
Rbyte* result; // Just a reference. Want to avoid copying data.
result = RAW(x) + start; // Trying to do ordinary pointer arithmetic.
return asRaw(result); // Want to return a raw vector of length `bytes`.
"
slice_raw <- cfunction(sig = sig, body = body)
РЕДАКТИРОВАТЬ: еще несколько потенциальных обходных путей
Спасибо Дирку за то, что он заставил меня задуматься над этим. Для достаточно небольших данных мы можем использовать fst
для сохранения фрейма данных с одним столбцом, где столбец - это необработанный вектор, который нам действительно нужен. Это использование fst
быстрее, чем writeBin()
library(fst)
wrapper <- data.frame(actual_data = raw(2^31 - 1))
system.time(write_fst(wrapper, tempfile()))
#> user system elapsed
#> 0.362 0.019 0.103
system.time(writeBin(wrapper$actual_data, tempfile()))
#> user system elapsed
#> 0.314 1.340 1.689
Создан в 2019-06-16 пакетом Представить (v0.3.0)
К сожалению, сложно создать фреймы данных с 2 ^ 31 или более строк. Один из способов - сначала преобразовать необработанный вектор в матрицу, и мы избегаем обычного целочисленного переполнения, поскольку (2 ^ 31 - 1) ^ 2 байта - это несколько эксабайт.
library(fst)
x <- raw(2^32)
m <- matrix(x, nrow = 2^16, ncol = 2^16)
system.time(write_fst(as.data.frame(m), tempfile()))
#> user system elapsed
#> 8.776 1.459 9.519
Создан в 2019-06-16 пакетом Представить (v0.3.0)
Мы все еще оставляем saveRDS()
в пыли, но мы больше не бьем writeBin()
. Преобразование из фрейма данных в матрицу происходит медленно, и я не уверен, что оно будет хорошо масштабироваться.
library(fst)
x <- raw(2^30)
m <- matrix(x, nrow = 2^15, ncol = 2^15)
system.time(write_fst(as.data.frame(m), tempfile()))
#> user system elapsed
#> 1.998 0.408 2.409
system.time(writeBin(as.raw(m), tempfile()))
#> user system elapsed
#> 0.329 0.839 1.397
Создан в 2019-06-16 пакетом Представить (v0.3.0)
Если, как предложил Дирк, мы можем использовать R_xlen_t
для индексации строк фрейма данных, мы можем избежать преобразования чего-либо.