LINUX.ORG.RU
ФорумTalks

Знатокам лиспа (2)


0

0

Держи дядя

(defvar *buffer-size* (* 200 1024 1024))
(defvar *buffer* (make-array *buffer-size* :element-type '(unsigned-byte 8)))

(defun write-random-byte (stream)
(dotimes (i *buffer-size*)
(write-byte (random 255) stream)))

(defun write-buffer ()
(with-open-file (s "f200m"
:direction :output
:if-exists :supersede
:element-type 'unsigned-byte)
(write-random-byte s)))

;;(time (write-buffer))

(defun process-stream (stream)
(let ((summa 0) (byte 0) (med 0))
(dotimes (i *buffer-size*)
(when (setf byte (read-byte stream))
(incf summa byte)
(setf (aref *buffer* i) byte)))
(setf med (round (/ summa *buffer-size*)))
(dotimes (i *buffer-size*)
(setf (aref *buffer* i) (abs (- (aref *buffer* i) med))))))

(defun process-file ()
(with-open-file (s "f200m" :element-type 'unsigned-byte)
(process-stream s)))

(time (process-file))

32 секунды total run time на целероне 2.8. Хотя из меня лиспер как из говна пуля.


Ответ на: комментарий от Sun-ch

А, это я ступил, передал буффер eliso'у, а не slime :) В общем другая ошибка:

error opening #P"/home/user/f200m":
  No such file or directory
   [Condition of type SB-INT:SIMPLE-FILE-ERROR]

Restarts:
 0: [ABORT] Return to SLIME's top level.
 1: [TERMINATE-THREAD] Terminate this thread (#<THREAD "worker" RUNNING {ABD2111}>)

Backtrace:
  0: (SB-IMPL::SIMPLE-FILE-PERROR "error opening ~S" #P"/home/user/f200m" 2)
  1: (SB-IMPL::SIMPLE-FILE-PERROR "error opening ~S" #P"/home/user/f200m" 2)[:EXTERNAL]
  2: ((LABELS SB-IMPL::VANILLA-OPEN-ERROR))
  3: (OPEN "f200m")[:EXTERNAL]
  4: (PROCESS-FILE)
  5: (SB-IMPL::%TIME #<FUNCTION (LAMBDA NIL) {AE719DD}>)
  6: (SB-INT:SIMPLE-EVAL-IN-LEXENV (TIME (PROCESS-FILE)) #<NULL-LEXENV>)
  7: (SWANK::EVAL-REGION "(defvar *buffer-size* (* 200 1024 1024))
     (defvar *buffer* (make-array *buffer-size* :element-type '(unsigned-byte 8)))
     
     (defun write-random-byte (stream)
       (dotimes (i *buffer-size*)
         (write-byte (random 255) stream)))
     
     (defun write-buffer ()
       (with-open-file (s \"f200m\"
                          :direction :output ..)
  8: ((LAMBDA NIL))
  9: ((LAMBDA (SWANK-BACKEND::FN)) #<CLOSURE (LAMBDA NIL) {ABD598D}>)
 10: (SWANK::CALL-WITH-BUFFER-SYNTAX #<CLOSURE (LAMBDA NIL) {ABD598D}>)
 11: (SB-INT:SIMPLE-EVAL-IN-LEXENV (SWANK:INTERACTIVE-EVAL-REGION "(defvar *buffer-size* (* 200 1024 1024))
     (defvar *buffer* (make-array *buffer-size* :element-type '(unsigned-byte 8)))
     
     (defun write-random-byte (stream)
       (dotimes (i *buffer-size*)
         (write-byte (random 255) stream)))
     
     (defun write-buffer ()
       (with-open-file (s \"f200m\"
                          :direction :output ..)
 12: ((LAMBDA NIL))
 13: ((FLET #:FORM-FUN1570))
 14: ((FLET #:FORM-FUN1570))
 15: ((LAMBDA (SWANK-BACKEND::HOOK SWANK-BACKEND::FUN)) #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<CLOSURE (LAMBDA NIL) {ABD317D}>)
 16: ((LAMBDA NIL))
 17: ((FLET #:FORM-FUN1570))
 18: ((FLET #:FORM-FUN1570))
 19: ((LAMBDA (SWANK-BACKEND::HOOK SWANK-BACKEND::FUN)) #<FUNCTION SWANK:SWANK-DEBUGGER-HOOK> #<FUNCTION (LAMBDA NIL) {B8DF425}>)
 20: (SWANK::CALL-WITH-REDIRECTED-IO #<SWANK::CONNECTION {B399F81}> #<CLOSURE (LAMBDA NIL) {ABD309D}>)
 21: (SWANK::CALL-WITH-CONNECTION #<SWANK::CONNECTION {B399F81}> #<FUNCTION (LAMBDA NIL) {B8DF425}>)
 22: (SWANK::HANDLE-REQUEST #<SWANK::CONNECTION {B399F81}>)
 23: (SWANK::CALL-WITH-BINDINGS NIL #<CLOSURE (LAMBDA NIL) {ABD307D}>)
 24: ((FLET SB-THREAD::WITH-MUTEX-THUNK))
 25: ((FLET #:WITHOUT-INTERRUPTS-BODY-[CALL-WITH-MUTEX]477))
 26: (SB-THREAD::CALL-WITH-MUTEX #<CLOSURE (FLET SB-THREAD::WITH-MUTEX-THUNK) {B747D215}> #S(SB-THREAD:MUTEX :NAME "thread result lock" :%OWNER #<SB-THREAD:THREAD "worker" RUNNING {ABD2111}> :STATE 1) #<SB-THREAD:THREAD "worker" RUNNING {ABD2111}> T)
 27: ((LAMBDA NIL))
 28: ("foreign function: #x80645AC")
 29: ("foreign function: #x8052AE1")
 30: ("foreign function: #x805BAED")
 31: ("foreign function: #xB7FB84C0")

anonymous
()
Ответ на: комментарий от anonymous

Дык этот файл сначала надо сделать, родное сердце. Запусти функцию (write-buffer), получишь файл f200m размером в 200 мегов, заполненный псевдослучайными числами от 0 до 255.

Sun-ch
() автор топика
Ответ на: комментарий от fMad

Нормально все, просто исчезли отступы.

Sun-ch
() автор топика
Ответ на: комментарий от Sun-ch

Во:

CL-USER> (time (process-file))
Evaluation took:
  28.199 seconds of real time
  27.961747 seconds of total run time (27.629727 user, 0.332020 system)
  [ Run times consist of 2.432 seconds GC time, and 25.530 seconds non-GC time. ]
  99.16% CPU
  71,740,728,939 processor cycles
  4,931,780,216 bytes consed
NIL

anonymous
()
Ответ на: комментарий от anonymous

(defun process-file () (with-open-file (s "f200m" :element-type 'unsigned-byte) (process-stream s)))

так как лиспа не знаю - считаю что это - не понятно, не семантика а синтаксис. может стереотипы ?

paranormal ★★
()

Чуть-чуть оптимизнем

;; Уберем special'ы, они медленные

(defconstant +buffer-size+ (* 200 1024 1024))

(defun process-stream (stream)
  (let ((buffer (make-array +buffer-size+ :element-type '(unsigned-byte 8)))
		(summa 0) (med 0))
	(read-sequence buffer stream) ; попробуем сэкономить на сисколлах
	(dotimes (i +buffer-size+)
	  (incf summa (aref buffer i)))
	(setf med (round (/ summa +buffer-size+)))
	(dotimes (i +buffer-size+)
	  (setf (aref buffer i) (abs (- (aref buffer i) med))))))

(defun process-file ()
  (with-open-file (s "f200m" :element-type '(unsigned-byte 8))
	(process-stream s)))

(time (process-file))

;; получаем 17 секунд против 26 в оригинале

COTOHA
()
Ответ на: Чуть-чуть оптимизнем от COTOHA

И еще немножко :]

(declaim (optimize (speed 3) (space 0) (safety 0) (debug 0)))

(defconstant +buffer-size+ (* 200 1024 1024))

(defun process-stream (stream)
  (let ((buffer (make-array +buffer-size+ :element-type '(unsigned-byte 8)))
	(summa 0) (med 0))
	(declare (type fixnum summa med))
	(read-sequence buffer stream)
	(dotimes (i +buffer-size+)
	  (incf summa (aref buffer i)))
	(setf med (round (/ summa +buffer-size+)))
	(dotimes (i +buffer-size+)
	  (setf (aref buffer i) (abs (- (aref buffer i) med))))))

(defun process-file ()
  (with-open-file (s "f200m" :element-type '(unsigned-byte 8))
	(process-stream s)))

CL-USER> (time (process-file))
Evaluation took:
  1.395 seconds of real time
  1.148072 seconds of user run time
  0.244016 seconds of system run time
  [Run times include 0.008 seconds GC run time.]
  0 calls to %EVAL
  0 page faults and
  212,181,080 bytes consed.
NIL

COTOHA
()
Ответ на: комментарий от Sun-ch

Файл каждый раз перегенерился. Но лажа в последнем варианте таки есть — сумма вполне может не попасть в fixnum, просто повезло со значениями. C (unsigned-byte 64) получается 13 сек. Т. е. в два раза с ходу получается.

COTOHA
()
Ответ на: комментарий от Sun-ch

Проверил для очистки совести на 64-битном sbcl (где summa влезает в регистр). Получил 16.921058 (исходный вариант) против 2.152135.

COTOHA
()
Ответ на: комментарий от COTOHA

Чего-то здесь не так, 200 мегабайтный файл обрабатывается за 2 секунды?

Может у тебя не интеловский компьютер?

Sun-ch
() автор топика
Ответ на: комментарий от Sun-ch

На котором 2 сек - короквад абнаковенный. Ну так и обработка несложная совсем.

COTOHA
()
Ответ на: комментарий от Sun-ch

И еще уточнение — там RAID5 на 6 винтах, и /home почти пустой. Что ему эти несчастные 200 метров, тем более одним хорошо размазанным куском?

COTOHA
()
Ответ на: комментарий от COTOHA

Мда, а дядя который на лисп наезжал, в плане отсутствии бинарных массивов, похоже потух вообще.

Sun-ch
() автор топика
Ответ на: комментарий от Sun-ch

>Мда, а дядя который на лисп наезжал, в плане отсутствии бинарных массивов, похоже потух вообще.

Спокойно, Михельсон. Дядя наводит порядок в треде про Python и Scala.

У вас уже получился пример, который работает в SBCL 1.0.22? Приведите его, пожалуйста, снабдив вызовом (room) по завершении вычислений и инструкциями по запуску, если они отличаются от стандартного sbcl --load file.lisp. Спасибо.

anonymous
()
Ответ на: комментарий от anonymous

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

И да, для Лиспа они весьма неутешительные.

anonymous
()
Ответ на: комментарий от anonymous

> И да, для Лиспа они весьма неутешительные.

Про eval из треда про питон и скалу не забудь. Картинку с бенчмаркингом тоже нарисуй.

mv ★★★★★
()

Your code is not Lispy at all. It's a crap.

anonymous
()
Ответ на: комментарий от mv

>Про eval из треда про питон и скалу не забудь. Картинку с бенчмаркингом тоже нарисуй.

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

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

anonymous
()
Ответ на: комментарий от anonymous

> Вам задали абсурдную задачу: использовать для вычислений язык, не предназначенный для этого. Вы сломя голову взялись за неё, и сели в лужу.

Я в упор не вижу лужи. 10-15% скорости - это фигня. На 30мб бОльший рантайм - это фигня. С ростом сложности задачи рантайм лиспа будет таким же, а у вас он будет раздуваться.

Давайте решим задачу на ассемблере, там рантайма ещё меньше будет, скорость ещё выше будет. Что это докажет? Что Си не годится для мат. расчётов?

mv ★★★★★
()
Ответ на: комментарий от mv

> С ростом сложности задачи рантайм лиспа будет таким же, а у вас он будет раздуваться.

С чего бы Сишному рантайму раздуваться? А рантайм Лиспа, пусть это даже фиксированный оверхэд, всё равно намного больше Сишного (это для программы, которая фактически ничего не делает).

tailgunner ★★★★★
()
Ответ на: комментарий от mv

Я сейчас пойду спать, нет времени возиться на виртуалке с ограниченной памятью с такой задачей.

Но: 1. Можно использовать purify для того, чтобы массив не двигался при сборке мусора. 2. Непонятно, откуда взялось 200мб консов. Здесь явно что-то не так.

den73 ★★★★★
()
Ответ на: комментарий от den73

А, и ещё 3: лисп должен делать эту задачу быстрее, чем С.

den73 ★★★★★
()
Ответ на: комментарий от den73

> Но: 1. Можно использовать purify для того, чтобы массив не двигался при сборке мусора. 2. Непонятно, откуда взялось 200мб консов. Здесь явно что-то не так.

Массив убивается при выходе из process-stream.

> А, и ещё 3: лисп должен делать эту задачу быстрее, чем С.

С чего вдруг? Тут линейная арифметика.

mv ★★★★★
()
Ответ на: комментарий от mv

Откуда взялось 200мб "консов" - понял. Это - как раз и есть массив. 
> Массив убивается при выходе из process-stream.
Неправда, массив расположен в куче. Чтобы он располагался на стеке, нужно написать внутри let, где он определён:

(declare (dynamic-extent buffer))

При этом получим красоту:
Unhandled memory fault at #xB4C5673C.
   [Condition of type SB-SYS:MEMORY-FAULT-ERROR]


Те, стека явно не хватило (я делаю сильно уменьшенный массив, т.к. у меня линукс стоит на виртуалке с 256Мб памяти). 

Впрочем, поскольку сам алгоритм ничего, кроме буфера, не выделяет, то и время сборки мусора ничтожно. 

> С чего вдруг? Тут линейная арифметика.
http://shootout.alioth.debian.org/debian/benchmark.php?test=sumcol&lang=all

смотрим в конце программы, которые не попали в конкурс из-за нарушения правил. 

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


(in-package :cl-user)
(declaim (optimize (speed 3) (space 0) (safety 0) (debug 0)))

(defparameter *buffer-size* (* 200 1024 256))

;(defun write-random-byte (stream)
;  (write-byte (random 255) stream))
;
;(defun write-buffer ()
;  (with-open-file (s "/root/f200m"
;		     :direction :output
;		     :if-does-not-exist :create
;		     :if-exists :supersede
;		     :element-type 'unsigned-byte)
;    (write-random-byte s)))
;
;(time (write-buffer))

(defun process-stream (stream)
  (let* ((buffer-size (coerce *buffer-size* 'fixnum))
	 (buffer (make-array buffer-size :element-type '(unsigned-byte 8)))
	 (summa 0) (med 0))
    (declare (type fixnum summa med))
    (read-sequence buffer stream)
    (dotimes (i buffer-size)
      (incf summa (aref buffer i)))
    (setf med (round (/ summa buffer-size)))
    (map-into buffer (lambda (b) (- b med)) buffer)
    ))

(defun process-file ()
  (with-open-file (s "f200m" :element-type '(unsigned-byte 8))
	(process-stream s)))

(time (process-file)) 

Инструкции:
1. сохраняем текст в файл
2. запускаем sbcl
3. выполняем (compile-file "имя-файла")
4. (load *)

читаем, что он пишет, например так:

Evaluation took:
  0.729 seconds of real time
  0.684042 seconds of total run time (0.088005 user, 0.596037 system)
  [ Run times consist of 0.020 seconds GC time, and 0.665 seconds non-GC time. ]
  93.83% CPU
  1,089,802,715 processor cycles
  1 page fault
  52,577,424 bytes consed

(room) при этом выдаёт около 110мб, но я почти уверен, что это 
стратегия работы с памятью и её наверняка можно подрулить. 
Реально используется только 52Мб (ровно размер массива), а 
остальное выделяется про запас, чтобы было место, куда
сдвинуть этот массив при сборке мусора. 

Если будет время, завтра посмотрю, что с этим можно сделать (purify наверняка здесь поможет). 

den73 ★★★★★
()
Ответ на: комментарий от den73

И ещё, если заменить (dotimes (i buffer-size) (incf summa (aref buffer i)))

на

(map nil (lambda (b) (incf summa b)) buffer)

вроде получается децл быстрее. Хотя я не уверен.

den73 ★★★★★
()
Ответ на: комментарий от den73

Кстати, всё это неправильно для 32-разрядного лиспа, т.к. summa не помещается в 32 разрядный fixnum.  

У меня (sbcl 1.0.22, 32-разрядный)
CL-USER> (format t "~X" most-positive-fixnum)
1FFFFFFF
Т.е., даже не 32 разряда.
А
CL-USER> (format t "~X" (* 200 1024 1024 256))
C80000000

Вот 32 - разрядный вариант:
(in-package :cl-user)
(declaim (optimize (speed 3) (space 0) (safety 0) (debug 0)))

(defconstant +buffer-size+ (* 200 1024 256))

;(defun write-random-byte (stream)
;  (write-byte (random 255) stream))

;(defun write-buffer ()
;  (with-open-file (s "/root/f50m"
;		     :direction :output
;		     :if-does-not-exist :create
;		     :if-exists :supersede
;		     :element-type 'unsigned-byte)
;    (dotimes (i +buffer-size+) 
;      (write-random-byte s))))

;(time (write-buffer))

(defun process-stream (stream)
  (let* ((buffer (make-array +buffer-size+ :element-type '(unsigned-byte 8)))
	 (summa 0) (med 0))
    (declare (fixnum med) (integer summa))
    (declare (type (simple-array (unsigned-byte 8)) buffer))
    (read-sequence buffer stream)
    (map nil (lambda (b) (incf summa b)) buffer)
    (setf med (round (/ summa +buffer-size+)))
    (map-into buffer (lambda (b) (- b med)) buffer)
    ))

(defun process-file ()
  (with-open-file (s "f50m" :element-type '(unsigned-byte 8))
	(process-stream s)))

(time (process-file)) 
(room)

В общем, не сиьно лучше предыдущих вариантов. 

den73 ★★★★★
()
Ответ на: комментарий от den73

> Неправда, массив расположен в куче. Чтобы он располагался на стеке, нужно написать внутри let, где он определён:

Хотите сказать, что объекты в куче вообще никогда не убиваются gc? o_O

> смотрим в конце программы, которые не попали в конкурс из-за нарушения правил.

Ну там ничего такого скарального нет. Более того, из-за явных определений типов в loop'е компилятор не сможет соптимизировать код до использования машинного представления и будет вставлять везде boxing (imul) и unboxing (sar). Уже это не даёт шанса обогнать нормальную сишную программу. С другой стороны, умения писать программы никто не отменял даже для случая с Си.

mv ★★★★★
()
Ответ на: комментарий от mv

> из-за явных определений типов в loop'е компилятор не сможет соптимизировать код до использования машинного представления и будет вставлять везде boxing (imul) и unboxing (sar)

O_O

т.е. в Советской России^W^WЛиспе типизированные программы компилируются в более медленный машкод?

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> т.е. в Советской России^W^WЛиспе типизированные программы компилируются в более медленный машкод?

В зависимости от компилятора. В SBCL в loop по определённым причинам определяется композитный тип (or <ваш тип> real), и компилятор потом не может (не имеет права) соптимизировать работу с таким типом. Выявлено случайно :) Убираешь fixnum - всё отличненько становится.

Что касается ручной типизации, то в стандарте нигде не написано, что программа должна от этого работать быстрее :-D

mv ★★★★★
()
Ответ на: комментарий от mv

> Что касается ручной типизации, то в стандарте нигде не написано, что программа должна от этого работать быстрее :-D

Ну да, а здравый смысл у всех свой %)

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

> Ну да, а здравый смысл у всех свой %)

Тем не менее, не стоить удивляться использованию fixnum там, где fixnum руками задан :) А где не задан, компилятор может до самого машинного представления чисел и операций над ними дооптимизироваться.

mv ★★★★★
()
Ответ на: комментарий от mv

> Ну там ничего такого скарального нет.
Не хочу обидеть, но похоже, что у Вас проблемы с вниманием. Я вообще-то имел в виду, что на задаче, аналогичной рассматриваемой, С работает за 4.6 секунды, а лисп - за 3.5. И это написано чёрным по белому на странице, на которую я давал ссылку. Из контекста разговора явно следовало, что именно я хотел сказать, давая ссылку.

> Хотите сказать, что объекты в куче вообще никогда не убиваются gc? o_O

Маленькое упражнение. Ответьте на три простых вопроса:
1. Что Вы написали про время удаления массива сначала
2. Что Вы написали в цитируемом посте
3. Есть ли разница между тем, что Вы написали в первом и во втором случае?

den73 ★★★★★
()
Ответ на: комментарий от den73

> Не хочу обидеть, но похоже, что у Вас проблемы с вниманием. Я вообще-то имел в виду, что на задаче, аналогичной рассматриваемой, С работает за 4.6 секунды, а лисп - за 3.5. И это написано чёрным по белому на странице, на которую я давал ссылку.

На странице чёрным и синим по белому написано, что самая быстрая валидная лисповая реализация (SBCL #4) отрабатывает за 13.41 с. Образец SBCL #2 в условия шутаута не пролез.

Вообще, этот шутаут давно испортился. Сишная программа, использующая intrinsic в алгоритме с ручной оптимизацией под sse - это не Си (забыл, в каком конкретно тесте такое видел).

> Маленькое упражнение. Ответьте на три простых вопроса:

Я ответил на ваше "Но: 1. Можно использовать purify для того, чтобы массив не двигался при сборке мусора. 2. Непонятно, откуда взялось 200мб консов. Здесь явно что-то не так.", не глядя на выхлоп программы (и пропустив cons). Мне показалось, что речь идёт о статистике работы gc, раз уже речь про сборку мусора шла. Соответственно, я хотел сказать, что все референсы на выделенный массив теряются при выходе из let в тестируемой функции, и 200мб после этого рассматриваются, как мусор.

mv ★★★★★
()
Ответ на: комментарий от mv

> все референсы на выделенный массив теряются при выходе из let в тестируемой функции, и 200мб после этого рассматриваются, как мусор

Было бы кому рассматривать. Рассматривать-то его будет (точнее, не будет) сборщик мусора, а время его запуска не определено. Значит, массив будет удалён не ранее выхода из let, а вовсе не в момент выхода из let. Он может быть удалён при выходе из let, только если он dynamic-extent, а в нашем случае на это не хватит стека.

> Образец SBCL #2 в условия шутаута не пролез.

Ну я же специально сказал, что смотреть надо именно на "невалидные" программы. Если потратить 5 минут, то можно вычитать и причину невалидности быстрой программы - она использует чтение с помощью read-char, а не read-line. Значит, в условиях нашего соревнования лисп сделал бы С (ведь мы же играем не в шутаут).

Ладно, может быть, я слишком придирчив, извините :)

den73 ★★★★★
()
Ответ на: комментарий от den73

Интересно, а если мы руками напишем сложение (usigned-byte 8) и (unsigned-byte 64), это будет считаться? Ведь в этом случае, можно проверять переполнение и увеличивать старший разряд на единицу вручную.

И ещё, пришлите, пожалуйста, ссылку на программу на С и как её компилировать.

den73 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.