Как я могу определить, почему мой код Racket работает так медленно? - PullRequest
6 голосов
/ 17 августа 2011

Ради интереса я написал быстрый скрипт командной строки Racket для анализа старых файлов состояния Unix.Файлы Fortune - это просто гигантские текстовые файлы с одним % на пустой строке, разделяющей записи.

В качестве первого быстрого взлома я написал следующий код Racket:

(define fortunes
    (with-input-from-file "fortunes.txt"
      (λ ()
        (regexp-split #rx"%" (port->string)))))

Я думал, что это будет работать почти мгновенно.Вместо этого требуется очень много времени для запуска - порядка пары минут.Для сравнения, то, что я считаю эквивалентным Python:

with open('fortunes.txt') as f:
    fortunes = f.read().split('%')

выполняется немедленно, с эквивалентными результатами для кода Racket.

Что я здесь не так делаю?Да, есть некоторые очевидные низко висящие плоды, например, я уверен, что было бы лучше, если бы я не вылил весь файл в ОЗУ с port->string, но поведение настолько патологически плохое, что я чувствую, что долженделать что-то глупое на гораздо более высоком уровне, чем это.

Есть ли более похожий на ракетку способ сделать это с одинаково лучшей производительностью?Racket I / O действительно плох для некоторых операций?Есть ли способ профилировать мой код немного глубже, чем наивный профилировщик в DrRacket, чтобы я мог выяснить, что о данной строке вызывает проблему?

EDIT : Файл удачи, который я использую, - это FreeBSD, найденный по адресу http://fortunes.cat -v.org / freebsd / , который весит около 2 МБ.Лучшее время выполнения для Racket 5.1.3 x64 на OS X Lion было:

real    1m1.479s
user    0m57.400s
sys     0m0.691s

Для Python 2.7.1 x64 это было:

real    0m0.057s
user    0m0.029s
sys     0m0.015s

Право Элая на то, что время идетпотрачено почти целиком в regexp-split (хотя полная секунда, по-видимому, потрачено в port->string), но мне не ясно, есть ли предпочтительный, но такой же простой метод.

Ответы [ 2 ]

5 голосов
/ 17 августа 2011

Похоже, что большая часть стоимости происходит из-за запуска regexp-split в строке. Самая быстрая альтернатива, которую я нашел, - это разбиение строки байтов с последующим преобразованием результатов в строки:

(map bytes->string/utf-8
     (call-with-input-file "db"
       (λ (i) (regexp-split #rx#"%" (port->bytes i)))))

При случайной базе данных состояния ~ 2 МБ ваш код занимает около 35 с, а эта версия - 33 мс.

(Я не уверен, почему это занимает так много времени на строке, но она определенно слишком медленная.)

РЕДАКТИРОВАТЬ : Мы отследили это до ошибки эффективности. Грубое описание: когда Racket делает regexp-match для строки, он преобразует большие части строки в байтовую строку (в UTF-8) для поиска. Эта функция является основной, которая реализована в C. regexp-split использует ее многократно, чтобы найти все совпадения, и поэтому продолжает повторять это преобразование. Я ищу способ сделать что-то лучше, но пока это не исправлено, используйте вышеуказанный обходной путь.

4 голосов
/ 19 августа 2011

Теперь это исправлено в последней версии Racket для Git HEAD, см. github.com / plt / racket / commit / 8eefaba . Ваш пример теперь выполняется для меня за 0,1 секунды.

...