Генерация списка миллионов случайных элементов - PullRequest
4 голосов
/ 13 марта 2010

Как эффективно сгенерировать список из миллиона случайных элементов в схеме? Следующий код достигает максимальной глубины рекурсии с самим 0,1 млн.

(unfold (lambda(x)(= x 1000000)) (lambda(x)(random 1000)) (lambda(x)(+ x 1)) 0)

Ответы [ 6 ]

6 голосов
/ 13 марта 2010

Это действительно зависит от системы, которую вы используете, но вот простой способ сделать это в простой схеме:

(let loop ([n 1000000] [r '()])
  (if (zero? n)
    r
    (loop (- n 1) (cons (random 1000) r))))

Одно замечание о запуске этого кода как есть: если вы просто наберете его в REPL, это приведет к печати результирующего списка, и это обычно будет включать в себя использование гораздо большего объема памяти, чем хранится в списке. Так что лучше сделать что-то вроде

(define l ...same...)

Существует множество других инструментов, которые можно использовать для различной степени удобства. unfold является одним из них, а другой - for петлями, которые можно найти в PLT Scheme :

(for/list ([i (in-range 1000000)]) (random 1000))
4 голосов
/ 13 марта 2010

Я не знаю много схем, но не могли бы вы просто использовать хвостовую рекурсию (которая на самом деле просто зацикливается) вместо раскрытия (или любой другой функции более высокого порядка)?

2 голосов
/ 07 ноября 2013

Принимая Chicken-Scheme в качестве реализации, вот попытка с некоторыми результатами.

(use srfi-1)
(use extras)

(time (unfold (lambda(x)(= x 1000000))
              (lambda(x)(random 1000))
              (lambda(x)(+ x 1)) 0))

(time (let loop ([n 1000000] [r '()])
        (if (zero? n)
            r
            (loop (- n 1) (cons (random 1000) r)))))

(define (range min max body)
  (let loop ((current min) (ret '()))
    (if (= current max)
      ret
      (loop (+ current 1) (cons (body current ret) ret)))))

(time (range 0 1000000 (lambda params (random 1000))))

Результаты здесь с csc -O3 t.scm

0.331s CPU time, 0.17s GC time (major), 12/660 GCs (major/minor)
0.107s CPU time, 0.02s GC time (major), 1/290 GCs (major/minor)
0.124s CPU time, 0.022s GC time (major), 1/320 GCs (major/minor)

Как видите, версия автора намного медленнее, чем рекурсивные вызовы с простым хвостом. Трудно сказать, почему вызов раскрытия происходит намного медленнее, но я предполагаю, что это потому, что выполнение вызовов функции занимает намного больше времени.

2 другие версии очень похожи. Моя версия - почти то же самое, за исключением того, что я создаю функцию высокого порядка, которую можно использовать повторно.

В отличие от простого цикла, его можно использовать повторно для создания диапазона функций. Позиция и текущий список отправляются в функцию, если они необходимы.

Версия более высокого порядка, вероятно, лучший способ сделать это, даже если для его выполнения требуется немного больше времени. Вероятно, это также из-за вызовов функций. Его можно оптимизировать, удалив параметры, и он будет работать почти так же быстро, как названный let.

Преимущество версии более высокого порядка состоит в том, что пользователю не нужно писать сам цикл и его можно использовать с абстрактной лямбда-функцией.

Редактировать

Глядя на этот конкретный случай. Если мы хотим создать миллион элементов в диапазоне от 0 до 999, мы могли бы создать вектор фиксированной длины, равный миллиону, со значениями от 0 до 999. Перемешать вещь обратно после. Тогда весь случайный процесс будет зависеть от функции shuffle, которая не должна создавать новые значения подкачки памяти, может быть быстрее, чем генерация случайных чисел. Тем не менее, метод случайного выбора все еще полагается на случайный.

Редактировать 2

Если вам действительно не нужен список, вы можете использовать вместо него вектор.

Вот моя вторая реализация с vector-map

(time (vector-map (lambda (x y) (random 1000)) (make-vector 1000000)))
# 0.07s CPU time, 0/262 GCs (major/minor)

Как видите, это ужасно быстрее, чем использование списка.

Редактировать 3 веселья

(define-syntax bigint
  (er-macro-transformer
    (lambda (exp rename compare)
      (let ((lst (map (lambda (x) (random 1000)) (make-list (cadr exp)))))
        (cons 'list lst)))))

100000
0.004s CPU time, 0/8888 GCs (major/minor)

Возможно, это не очень хорошая идея, но я чувствовал, что это может быть интересно. Поскольку это макрос, он будет выполняться во время компиляции. Время компиляции будет огромным, но, как вы видите, улучшение скорости также огромно. К сожалению, используя курицу, я не смог составить список из миллиона. Я предполагаю, что тип, который он может использовать для создания списка, переполняется и обращается к недопустимой памяти.

Для ответа на вопрос в комментариях:

Я не профессионал Схемы. Я тоже новичок в этом, и, как я понимаю, именованный цикл или функция высокого порядка должны быть подходящим вариантом. Функция высокого порядка хороша тем, что ее можно использовать повторно. Вы можете определить

(define (make-random-list quantity maxran)
 ...)

Тогда это другая интересная часть, так как схема - это все о функциях высокого порядка. Затем вы можете заменить реализацию make-random-list на что угодно. Если вам нужно выполнение времени компиляции, определите макрос, в противном случае используйте функцию. Все, что действительно важно, - это иметь возможность использовать его повторно. Это должно быть быстро и не использовать память.

Здравый смысл говорит вам, что при меньшем выполнении это будет быстрее, хвостовые рекурсивные вызовы не должны занимать память. А когда вы не уверены, вы можете скрыть реализацию в функции, которая может быть оптимизирована позже.

2 голосов
/ 13 марта 2010

Кто-то поправит меня, если я ошибаюсь, но код Fakrudeen должен в конечном итоге быть оптимизирован, поскольку он хвостовой рекурсивен. Или это должно быть при правильной реализации развернуть. Он никогда не должен достигать максимальной глубины рекурсии.

Какую версию схемы вы используете Fakrudeen? DrScheme не давится простым миллионом случайных чисел.

2 голосов
/ 13 марта 2010

Используйте конструкцию do-loop , как описано здесь .

1 голос
/ 10 ноября 2013

MIT Scheme ограничивает стек вычислений. Учитывая размер вашей проблемы, вы, вероятно, исчерпали размер стека. К счастью, вы можете предоставить параметр командной строки для изменения размера стека. Попробуйте:

$ mit-scheme --stack <number-of-1024-word-blocks>

Существуют и другие параметры командной строки. Проверьте mit-scheme --help

Обратите внимание, что схема MIT, по моему опыту, является одной из немногих схем с ограниченным размером стека. Это объясняет, почему пробовать ваш код в других схемах часто удается.

Что касается вашего вопроса эффективности. Процедура unfold, вероятно, не реализована с помощью хвостового рекурсивного / итерационного алгоритма. Вот хвостовая рекурсивная версия с хвостовой рекурсивной версией 'list reverse in-place':

(define (unfold stop value incr n0)
  (let collecting ((n n0) (l '()))
    (if (stop n)
        (reverse! l)
        (collecting (incr n) (cons (value n) l)))))

(define (reverse! list)
  (let reving ((list list) (rslt '()))
    (if (null? list)
        rslt
        (let ((rest (cdr list)))
          (set-cdr! list rslt)
          (reving rest list)))))

Примечание:

$ mit-scheme --version
MIT/GNU Scheme microcode 15.3
Copyright (C) 2011 Massachusetts Institute of Technology
This is free software; see the source for copying conditions. There is NO warranty; not even
for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Image saved on Tuesday November 8, 2011 at 10:45:46 PM
  Release 9.1.1 || Microcode 15.3 || Runtime 15.7 || SF 4.41 || LIAR/x86-64 4.118 || Edwin 3.116

Moriturus te saluto.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...