В этой статье утверждается, что определенная программа Lisp работает быстрее, чем ее C эквивалент. Пытаясь воспроизвести результаты, мне удалось приблизиться (Lisp 50% медленнее, чем C), но хотел знать, знает ли кто-нибудь способы сжать больше perf из SBCL 1.3.1.
Задача заключается в добавлении постоянного одиночного поплавка в каждую ячейку в 800 x 800 одиночных поплавков. Метод состоит в том, чтобы написать программу на C и в Общий Lisp и сравните время. Используя этот портативный таймер, код C как следующим образом:
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <time.h>
#include "./modules/tictoc/tictoc.h"
const int HORZ = 800;
const int VERT = 800;
#define PERF_REPS 1000
typedef float DATA_T;
struct image_s {
size_t n;
size_t w, h;
DATA_T * data;
};
typedef struct image_s image;
image * make_image (size_t w, size_t h) {
size_t n = w * h;
DATA_T * data = (DATA_T *)malloc(sizeof(DATA_T) * n);
assert (NULL != data);
image * result = (image *)malloc(sizeof(image));
assert (NULL != result);
result->n = n;
result->w = w;
result->h = h;
result->data = data;
return result;
}
void free_image (image * it) {
assert (NULL != it);
assert (NULL != it->data);
free (it->data);
free (it);
}
image * init_to_value (image * it, DATA_T val) {
assert (NULL != it);
assert (NULL != it->data);
size_t i;
const size_t n = it->n;
for (i = 0; i < n; ++i) {
it->data[i] = val;
}
return it;
}
void add (image * to, image * from, DATA_T val) {
assert (NULL != to);
assert (NULL != to->data);
assert (NULL != from);
assert (NULL != from->data);
size_t i;
const size_t n = to->n;
for (i = 0; i < n; ++i) {
to->data[i] = from->data[i] + val;
}
}
int main (int argc, char ** argv) {
image * from = init_to_value (make_image (HORZ, VERT), 0.0f);
image * to = init_to_value (make_image (HORZ, VERT), 0.0f);
TicTocTimer clock = tic();
for (size_t i = 0; i < PERF_REPS; ++i)
add (to, from, 42.0);
printf("Elapsed time %f seconds.\n",toc(&clock));
free_image (to);
free_image (from);
return 0;
}
Я компилирую и запускаю код следующим образом:
gcc -O3 image-add.c ./modules/tictoc/libtictoc.a && ./a.out
Типичное время на моей программе mac book составляет около 0,178 секунды. Довольно приятно.
Эквивалентный код Lisp, используя каждую опцию, которую я смог найти в Lisp hyperspec, в новой книге Общие Lisp Рецепты, а в Руководство пользователя SBCL, является следующим образом. В комментариях указываются некоторые вещи, которые я пробовал, которые не разница.
;;; None of the commented-out declarations made any difference in speed.
(declaim (optimize speed (safety 0)))
(defun image-add (to from val)
(declare (type (simple-array single-float (*))
to from))
(declare (type single-float val))
(let ((size (array-dimension to 0)))
;(declare (type fixnum size))
(dotimes (i size)
;(declare (type fixnum i))
(setf (aref to i) (+ (aref from i) val)))))
(defparameter HORZ 800)
(defparameter VERT 800)
(defparameter PERF-REPS 1000)
(let ((to (make-array (* HORZ VERT) :element-type 'single-float))
(fm (make-array (* HORZ VERT) :element-type 'single-float)))
;(declare (type fixnum HORZ))
;(declare (type fixnum VERT))
(time (dotimes (_ PERF-REPS)
;(declare (type fixnum PERF-REPS))
;(declare (type fixnum _))
;(declare (inline image-add))
(image-add to fm 42.0))))
Я компилирую и запускаю его следующим образом:
sbcl --script image-perf.lisp
а типичное время выполнения - 0.276. Неплохо, но я хочу, чтобы это было намного лучше. Конечно, весь смысл упражнения заключается в том, что код Lisp короче, но делает кто-нибудь знает способ сделать это так быстро или быстрее?