Схема размещения Chez: --program vs --script

Рассмотрим этот бит кода Chez Scheme:

(импорт (chezscheme))

(определить (список-перечислить ls val proc)
  (пусть цикл ((ls ls) (возврат? #f) (val val))
    (если (или (ноль? ls)
            вернуть?)
        вал
        (вызов со значениями (lambda () (proc val (car ls)))
          (лямбда (возвращение? val)
            (loop (cdr ls) return? val))))))

(определить (список-индекс ls proc)
  (перечислите список
                  0
                  (лямбда (я эльт)
                    (if (proc elt)
                        (значения #t i)
                        (значения #f (+ i 1))))))

(определите 100000)

(определить данные (йота n))

(время (данные списка индексов (лямбда (elt) (= elt (- n 1)))))

Запустить его:

~ $ circuit --script ~/scratch/_list-enumerate-distribution-test-chez-a.sps 
(время (список-индекс данных...))
    нет коллекций
    3 мсек прошло процессорное время
    4 мс в реальном времени
    Выделено 8 байт

Ничего себе, он сообщает, что было выделено только 8 байтов.

Давайте запустим его снова, используя --program вариант вместо --script:

$ $ --program ~/scratch/_list-enumerate-allocation-test-chez-a.sps 
(время (список-индекс данных...))
    нет коллекций
    3 мсек прошло процессорное время
    3 мс в реальном времени
    Выделено 800000 байт

Yikes, выделено 800000 байт.

В чем разница?

издание

1 ответ

Вот записка от Кента Дибвига в ответ:


Это интересный вопрос.

При запуске с параметром --script, который использует семантику REPL, переменные, определенные в сценарии, такие как list-enumerate и list-index, являются изменяемыми, что препятствует межпроцедурной оптимизации, включая встраивание. Однако при запуске с --program переменные являются неизменяемыми, что позволяет выполнять межпроцедурную оптимизацию.

В этом случае --program позволяет компилятору встроить list-enumerate в тело list-index и, в свою очередь, лямбда-выражение в теле list-index в тело list-enumerate. Конечный результат является условным выражением в выражении производителя вызова со значениями. Это заставляет компилятор создавать замыкание для потребителя, чтобы избежать дублирования кода в ветвях then и else условного выражения. Это закрытие создается каждый раз в цикле list-enumerate, что приводит к дополнительным накладным расходам. Так часто идут оптимизации. В основном вы выигрываете, но иногда вы проигрываете. Хорошая новость заключается в том, что он перевешивает преимущества, даже в вашей программе. Я поместил вызов list-index в цикл (измененный код приведен ниже) и обнаружил, что при использовании --program код работает примерно на 30% быстрее.

Кент


(импорт (chezscheme))

(определить (список-перечислить ls val proc)
  (пусть цикл ((ls ls) (возврат? #f) (val val))
    (если (или (ноль? ls)
            вернуть?)
        вал
        (вызов со значениями (lambda () (proc val (car ls)))
          (лямбда (возвращение? val)
            (loop (cdr ls) return? val))))))

(определить (список-индекс ls proc)
  (перечислите список
                  0
                  (лямбда (я эльт)
                    (if (proc elt)
                        (значения #t i)
                        (значения #f (+ i 1))))))

(определите 100000)

(определить данные (время (йота n)))

(позволять ()
(определить runalot
  (лямбда (я думаю)
    (пусть цикл ([i i])
      (пусть ([x (thunk)])
        (если (fx= i 1)
            Икс
            (цикл (fx- i 1)))))))

(время
  (Runalot 1000
    (лямбда ()
      (данные индекса списка (лямбда (elt) (= elt (- n 1))))))))
Другие вопросы по тегам