Было бы интересно сравнить производительность вирт. функций.
Нужно учесть следующее: доступ к полям в виде родовых функций намного-намного медленнее доступа к полям структуры, за исключением частных случаев (например, в теле метода, специфицированного этим классом, может быть лишь раза в полтора медленнее структуры, согласно рук-ву SBCL). Кроме того, в С++ есть статические методы, к-рых в CLOS вроде бы как нет. Далее, я не смог заставить CLOS заинлайнить вызовы вирт.ф-й, а в С++ это в принципе возможно (хотя я не понял, заинлайнены ли они в нашем случае).
По этой причине я взял вместо классов структуры (и в жизни тоже всегда использую структуры вместо классов).
Код такой, но за качество не ручаюсь. Добровольцы на code review приглашаются.
;; clos-virtual-method-benchmark.lisp
(in-package :cl-user)
(declaim
(optimize (speed 3) (safety 0) (debug 0)
(space 0) (compilation-speed 0)))
(proclaim
'(optimize (speed 3) (safety 0) (debug 0)
(space 0) (compilation-speed 0)))
(defparameter *times* (* 100 1000 1000))
(defstruct base
next
)
(defstruct (test-class-3 (:include base))
fld1
fld2
)
(declaim (sb-ext:maybe-inline Делишко+))
(proclaim '(sb-ext:maybe-inline Делишко+))
(declaim (inline Делишко+))
(proclaim '(inline Делишко+))
(defgeneric Делишко+ (ц))
(defmethod Делишко+ ((ц base))
7)
(defmethod Делишко+ ((ц test-class-3))
(setf (test-class-3-fld1 ц) (test-class-3-fld2 ц))
1)
(defmethod Делишко+ ((ц string))
0)
(defun inner (o n)
(declare (type fixnum n))
(declare (type base o))
(declare (inline Делишко+))
(let ((res 0))
(declare (type fixnum res))
(dotimes (i n)
(incf res (the fixnum (Делишко+ o)))
(incf res (the fixnum (Делишко+ o)))
(incf res (the fixnum (Делишко+ o)))
(incf res (the fixnum (Делишко+ o)))
(incf res (the fixnum (Делишко+ o)))
(incf res (the fixnum (Делишко+ o)))
(incf res (the fixnum (Делишко+ o)))
(incf res (the fixnum (Делишко+ o)))
(incf res (the fixnum (Делишко+ o)))
(incf res (the fixnum (Делишко+ o)))
(setf res (mod res 16))
(setf o (base-next o)))
(print res)
res))
(defun main()
(let* (
(o1 (make-test-class-3 :fld1 1))
(o2 (make-test-class-3 :fld1 1 :next o1))
res)
(setf (test-class-3-next o1) o2)
(setf res (inner o1 *times*))
(format t "~%~S~%" res)))
(let ((*trace-output* *standard-output*))
(time (main)))
;; eof
С++
// cpp-virtual-method-benchmark.cpp
#include "stdio.h"
#include <cstdlib>
class base_class {
public:
base_class* next;
virtual int meth() { return 0; };
};
class test_class : public base_class {
public:
int fld1, fld2, fld3, fld4, fld5;
int meth () { fld4 = fld2; return 1; };
};
int inner(base_class *o,int bound) {
int res=0;
for (int i=0;i<bound;i++) {
res += o->meth();
res += o->meth();
res += o->meth();
res += o->meth();
res += o->meth();
res += o->meth();
res += o->meth();
res += o->meth();
res += o->meth();
res += o->meth();
o = o->next;
res = res % 16;
}
return res;
}
int main(int argc, char* argv[])
{
test_class o1;
test_class o2;
o1.fld1=1;
o2.fld1=1;
o1.next=&o2;
o2.next=&o1;
int n = 100*1000*1000;
int result=inner(&o1,n);
printf("%d %d\n",o1.fld5,result); // проверяем корректность и чтобы оптимизатор
// не выкинул неиспользуемый код
return 0;
}
// g++ -O2 -o cpp-benchmark cpp-virtual-method-benchmark.cpp ; echo disassemble inner | gdb cpp-benchmark ; time ./cpp-benchmark
Ну и для полноты картины, создаваемая объектная система для Яра:
- Полностью оптимизирована под отладку, смещение в VMT выражено глобальной переменной, вызов функции через символ - 31 секунда.
- то же, но вызов функции как функции 16 секунд
- то же, оптимизация под скорость - 4.2 секунды
Получилось в 1.6 раза быстрее CLOS и в 2.7 раз медленнее С++. Правда, для множественной диспетчеризации скорее всего проиграем против CLOS. С другой стороны, сделать inline статический диспатч для CLOS методов в случае, когда тип достаточно хорошо известен, мне не удалось, оно делается с помощью MOP. Я делаю примерно то же, но без CLOS.