Схема размещения 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))))))))