Как создать последовательность до 200-500 тысяч чисел всего за несколько микросекунд

Мне нужно создать последовательность чисел в кратчайшие сроки, в идеале в диапазоне микросекунд. Общая длина этой последовательности обычно находится в диапазоне 20-100 тысяч чисел, но иногда мне нужно создать последовательность из 200-300 тысяч чисел. На данный момент я реализовал свой код на R (просто потому, что достаточно хорошо знаю язык). Предполагая, что моя последовательность должна начинаться с 1 и заканчиваться до 25 с шагом 0,0001, в R я бы сделал

s <- seq(1, 25, by = 0.0001)

Несмотря на молниеносную скорость, это занимает несколько миллисекунд.

> library(microbenchmark)
> microbenchmark(seq(1, 25, by = 0.0001))
Unit: milliseconds
                   expr    min      lq     mean median     uq     max neval
 seq(1, 25, by = 1e-04) 1.6098 2.14505 3.741858 2.2324 2.3554 83.8912   100

Вот мои вопросы:

1- можно ли ускорить генерацию последовательности в R? в идеале ‹ 0,5 миллисекунды?

2- в какой-то момент мне нужно будет реализовать код на другом (более быстром) языке, сможет ли какой-либо язык генерировать такие последовательности менее чем за 100-200 микросекунд?


r seq
person pisistrato    schedule 08.02.2020    source источник
comment
Я считаю, что ответ на оба вопроса положительный, можно сделать seq.default быстрее. 1) В вашем случае использования у вас есть только аргументы from, to и by, seq.default больше. И если вы заранее знаете, что from < to && by > 0 вы сможете упростить исходный код. 2) seq.default написан на чистом R, взгляните на пакет Rcpp для способов использования C++ из R. Или напишите общую библиотеку на C, C++ и загружайте ее при необходимости, например, включив в пакет R.   -  person Rui Barradas    schedule 08.02.2020


Ответы (2)


С помощью Rcpp можно улучшить скорость на ~60%. Хотя, все же не "несколько" мкс.

library(Rcpp)
library(microbenchmark)

cppFunction(
  "
  NumericVector cpp_seq(double &from, double &to, double &by){
    int length = abs((to - from) / by);

    NumericVector out(length + 1);
    int pos = 0;

    for (double i = from; i < to; i += by){
      out[pos] = i;
      pos +=1;
    }

    out[length] = to;
    return out;
  }
  "
)

x <- cpp_seq(1, 25, 0.0001)

microbenchmark(
  seq = seq(1, 25, 0.0001),
  cpp_seq = cpp_seq(1, 25, 0.0001), 
  times = 1000
)

# Unit: microseconds
#     expr      min        lq     mean   median       uq      max neval
#     seq  2425.694 2630.6595 4262.708 2847.697 3256.618 99978.10  1000
# cpp_seq   670.949  794.6625 1239.710  903.714 1005.736 12892.09  1000

Оба вектора одинаковы, если учитывать ошибки с плавающей запятой:

x_R <- seq(1, 25, 0.0001)
x_cpp <- cpp_seq(1, 25, 0.0001)

identical(round(x_R, 9), round(x_cpp, 9))
# [1] TRUE
person loki    schedule 08.02.2020
comment
это будет делать это сейчас, спасибо. Как я уже сказал, в какой-то момент мне придется перейти на другой язык. Я предполагаю, что язык, поддерживающий параллелизм, был бы хорошим выбором. Не эксперт в этой теме, но, возможно, способ сделать это быстрее - разделить диапазон от-до на куски и выполнить операцию параллельно на поддиапазонах... - person pisistrato; 08.02.2020

seq.int() на 30-40% быстрее..

microbenchmark::microbenchmark(
  seq = seq(1, 25, by = 0.0001),
  seq.int = seq.int(1, 250000, by = 1) / 10000
)

# Unit: milliseconds
#    expr    min      lq     mean median      uq     max neval
#     seq 2.0749 2.16525 3.375812 2.2222 2.31935 95.9553   100
# seq.int 1.3184 1.35860 1.458503 1.3855 1.42940  6.9268   100

Кроме того, остерегайтесь ошибок с плавающей запятой!

sprintf("%.100f", seq(0,0.01,0.0001) )
  [1] "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
  [2] "0.0001000000000000000047921736023859295983129413798451423645019531250000000000000000000000000000000000"
  [3] "0.0002000000000000000095843472047718591966258827596902847290039062500000000000000000000000000000000000"
  [4] "0.0003000000000000000279290479632265942200319841504096984863281250000000000000000000000000000000000000"
  [5] "0.0004000000000000000191686944095437183932517655193805694580078125000000000000000000000000000000000000"
  [6] "0.0005000000000000000104083408558608425664715468883514404296875000000000000000000000000000000000000000"
  [7] "0.0006000000000000000558580959264531884400639683008193969726562500000000000000000000000000000000000000"
person Wimpel    schedule 08.02.2020
comment
Я прокомментировал это - обратите внимание также, что «int» в seq.int() означает внутреннее, а не целое, поэтому вам не нужно использовать целые числа и делить, дробные последовательности в порядке. - person 27 ϕ 9; 08.02.2020
comment
@ H1, так что я вижу, не читал комментарии ..... вы можете добавить свой комментарий в качестве ответа, если хотите, поэтому я могу удалить свой ... Я предполагал, что деление предотвратит работу с плавающей запятой ошибки.. но это кажется неправильным.... - person Wimpel; 08.02.2020
comment
спасибо за указание на проблему с плавающей запятой, не думал об этом. К счастью, сейчас меня интересуют первые 4 десятичных знака, но это может легко измениться в будущем, и я бы потратил немного времени, прежде чем осознать это. - person pisistrato; 08.02.2020