2012-02-17 5 views
15

У меня есть небольшая тестовая структура. Он выполняет цикл, который выполняет следующие операции:Ускоренный runhaskell

  1. Создайте небольшой исходный файл Haskell.

  2. Выполните это с помощью runhaskell. Программа создает различные файлы на диске.

  3. Обработка только что созданных файлов диска.

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

С одной стороны, факт, что runhaskell удается загрузить файл с диска, подделать его, проанализировать, выполнить анализ зависимостей, загрузить 20 КБ больше текста с диска, выполнить токенизацию и разобрать все это, выполнить полный вывод типа, проверить типы, desugar to Core, ссылку на скомпилированный машинный код и выполнение вещи в интерпретаторе, всего за 2 секунды времени на стене, на самом деле довольно впечатляюще, когда вы думаете об этом. С другой стороны, я все еще хочу ускорить его. ;-)

Компиляция тестера (программа, выполняющая вышеуказанный цикл) породила небольшую разницу в производительности. Компиляция 20 Кбайт библиотечного кода, связанного с ссылками на скрипты, вызвала более заметное улучшение. Но это все равно занимает около 1 секунды за вызов runhaskell.

Сгенерированные файлы Haskell имеют чуть более 1 КБ каждый, но только одна часть файла на самом деле изменяется. Возможно, компиляция файла и использование коммутатора GHC -e будет быстрее?

В качестве альтернативы, возможно, это накладные расходы на многократное создание и уничтожение многих процессов ОС, которые замедляют это? Кажется, что каждый вызов runhaskell заставляет ОС исследовать путь поиска системы, найти нужный двоичный файл, загрузить его в память (конечно же, это уже в кэш диска?), Связать его с любыми DLL-файлами и запустить его. Есть ли способ (легко) сохранить один экземпляр GHC, а не постоянно создавать и уничтожать процесс ОС?

В конечном счете, я полагаю, что всегда есть API GHC. Но, насколько я понимаю, это кошмарно сложно использовать, сильно недокументировано и подвержено радикальным изменениям в каждом выпуске MH. Задача, которую я пытаюсь выполнить, очень проста, поэтому я не хочу делать вещи более сложными, чем это необходимо.

Предложения?

Обновление: Переключение на GHC -e (то есть, теперь всего компилируются за исключением того, одно выражения выполняется) не имеет никакого значения измеримой производительности. На данный момент кажется довольно ясным, что это все ОС накладные. Мне интересно, могу ли я создать трубу от тестера до GHCi и, таким образом, использовать только один процесс ОС ...

+0

Весь ваш рабочий процесс не выглядит точно нацеленным на производительность, не так ли? Почему вы должны создать код Haskell? – leftaroundabout

+3

Очевидно, вам нужен демон GHC! : p (некоторые люди, которых я знаю, шутили о создании демона grep, чтобы избежать накладных расходов при вызове grep во время загрузки и т. д.) – ivanm

+1

+1 для обоснованной и хорошо выполненной попытки оптимизации. – delnan

ответ

9

Хорошо, у меня есть решение: Я создал единый процесс GHCI и подключен его stdin к трубе, так что я могу отправьте его выражения для интерактивной оценки.

Несколько довольно крупных программных рефакторингов позже, и весь комплект тестов теперь занимает примерно 8 секунд, а не 48 секунд. Это будет для меня! :-D

(Для того, чтобы кто-то еще пытается сделать это: За любовь к Богу, не забудьте передать переключатель -v0 в GHCi, или вы получите GHCi приветствовать баннер Жутко, если вы запустите GHCI в интерактивном режиме! даже при -v0 еще появится командная строка, но при подключении к трубе исчезает в командной строке, я предполагаю, что это полезно особенность конструкции, а не случайная авария)


конечно, половина. Причина, по которой я иду по этому странному маршруту, заключается в том, что я хочу записать stdout и stderr в файл. Используя RunHaskell, это довольно просто; просто передайте соответствующие параметры при создании дочернего процесса. Но теперь все тестовых примеров управляются одним операционным процессом, поэтому нет очевидного способа перенаправления stdin и stdout.

Решение, с которым я столкнулся, состояло в том, чтобы направить все тестовые выходные данные на один файл, а между тестами GHCi распечатал магическую строку, которая (я надеюсь!) Не появится в тестовом выходе. Затем выйдите из GHCi, сложите файл и найдите магические строки, чтобы я мог отрезать файл в подходящие куски.

+0

Можете ли вы изменить свои тестовые функции, чтобы они воспринимали ошибки и ошибки, а не записывали напрямую в stdout и stderr? – Alex

2

Если большинство исходных файлов не изменилось, вы можете использовать -fobject-code (возможно, GHC) в сочетании с -outputdir) флаг для компиляции некоторых файлов библиотеки.

+0

Как я уже сказал, я уже составил 20 КБ библиотечного кода. Это сократило время работы от 2 секунд до 1 секунды. Но я хотел бы уменьшить это, если есть простой способ сделать это. – MathematicalOrchid

+0

@MathematicsOrchid О, пропустил этот бит, извините: s – ivanm

0

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

Если вам действительно нужно работать с меняющимся кодом Haskell, вы можете попробовать следующее.

  1. При необходимости создайте набор модулей с различным содержимым.
  2. Каждый модуль должен экспортировать его основную функцию
  3. Дополнительный модуль обертки должен выполнить правильный модуль из набора на основе входных аргументов. Каждый раз, когда вы хотите выполнить один тест, вы будете использовать разные аргументы.
  4. Вся программа составлена ​​статический

Пример модуль:

module Tester where 

import Data.String.Interpolation -- package Interpolation 

submodule nameSuffix var1 var2 = [str| 
module Sub$nameSuffix$ where 

someFunction x = $var1$ * x 
anotherFunction v | v == $var2$ = v 
        | otherwise = error ("anotherFunction: argument is not " ++ $:var2$) 

|] 

modules = [ let suf = (show var1 ++ "_" ++ show var2) in (suf,submodule suf var1 var2) | var1 <- [1..10], var2 <- [1..10]] 

writeModules = mapM_ (\ (file,what) -> writeFile file what) modules 
+0

На самом деле это не сработает. Некоторые из тестовых программ могут потерпеть крах; если бы все это была одна гигантская программа, это остановило бы ее работу. Кроме того, я хочу захватить 'stdout' и' stderr' из каждого теста и записывать их в файл. Если бы не это, тогда да, я мог бы просто генерировать все это как одну гигантскую программу Haskell. Это было бы намного проще ... – MathematicalOrchid

+0

@MathematicsOrchid: вы повторно выполняете программу для каждого теста, так что, пока все компилируется, все будет в порядке. Что касается перенаправления: что не так с './testRunner testNumber123 2> stderr.txt 1> stdout.txt'? – Tener

+0

Что означает «сбой»? Вы должны уметь интегрировать все свои тесты в одну программу и вызывать их с помощью тестового бегуна верхнего уровня, который занимается перенаправлением «stdout» и «stderr» и восстановления после сбоев. – pat

0

Если тесты хорошо изолированы друг от друга, вы можете поместить весь тестовый код в одну программу и вызвать один раз runhaskell. Это может не работать, если некоторые тесты создаются на основе результатов других, или если некоторые тесты вызывают unsafeCrash.

Я полагаю, ваш сгенерированный код выглядит следующим образом

module Main where 
boilerplate code 
main = do_something_for_test_3 

Вы можете поместить код всех тестов в один файл. Каждый генератор тестового кода отвечает за запись do_something_for_test_N.

module Main where 
boilerplate code 

-- Run each test in its own directory 
withTestDir d m = do 
    cwd <- getCurrentDirectory 
    createDirectory d 
    setCurrentDirectory d 
    m 
    setCurrentDirectory cwd 

-- ["test1", "test2", ...] 
dirNames = map ("test"++) $ map show [1..] 
main = zipWithM withTestDir dirNames tests 

-- Put tests here 
tests = 
    [ do do_something_for_test_1 
    , do do_something_for_test_2 
    , ... 
    ] 

Теперь вы только нести накладные расходы на один вызов к runhaskell.

3

Возможно, вы найдете полезный код в TBC.У него разные амбиции - в частности, для тестирования тестовых шаблонов и тестовых проектов, которые не могут быть полностью скомпилированы, но могут быть расширены с помощью функции watch-directory. Тесты выполняются в GHCi, но используются объекты, успешно построенные с помощью cabal («runghc Setup build»).

Я разработал его для тестирования EDSL со сложным хакером типа, т. Е. Когда тяжелый вычислительный подъем выполняется другими библиотеками.

Я сейчас обновляю его до последней платформы Haskell и приветствую любые комментарии или исправления.