boost::shared_ptr, GC и тормоза

Не перестают меня удивлять и заявления о том, что де, раз в C++ есть boost, с его shared_ptr и прочим, то проблема сборки мусора для C++ фактически решена.
Ладно уж, не будем о том, что счетчики ссылок неспособны разрешить циклические зависимости, просто сравним ту самую производительность, за которую писатели на C++ так трясутся.
Тест, который я придумал, очень прост, но в то же время очень показателен.
Заключается он в следующем:
Создаем вектор на 'length' элементов, заполняем его указателями на структуры, подобные лисповым cons-ячейкам, и 'n' раз прогоняем цикл, в котором создаем копию этого вектора, а старый удаляем(элементы при этом мы не копируем, а копируем только указатели на них). Указатели, в C++, естественно, используем "умные".
В качестве противника C++, то есть в качестве языка, в реализациях которого присутствует нормальный GC, я взял Common Lisp, если конкретно - SBCL.
Для варианта на Common Lisp получился вот такой код:
(declaim (optimize (speed 3) (safety 0))) (defun copy-vector (vector) (declare (type simple-vector vector)) (let* ((length (length vector)) (new-vector (make-array length))) (dotimes (i length) (setf (svref new-vector i) (svref vector i))) new-vector)) (defun test (n length) (declare (type fixnum length n)) (let ((vector (make-array length))) (dotimes (i length) (setf (svref vector i) (cons nil nil))) (dotimes (i n) (setf vector (copy-vector vector))))) (defun main (&aux (n (second sb-ext:*posix-argv*)) (length (third sb-ext:*posix-argv*))) (when (> (length sb-ext:*posix-argv*) 3) (write-line "Usage test_sbcl [ n [ length ] ]") (sb-ext:quit :unix-status 1)) (let ((n (or (and n (parse-integer n)) 1000000)) (length (or (and length (parse-integer length)) 100))) (declare (type fixnum n length)) (test n length)) (sb-ext:gc :full t)) (sb-ext:save-lisp-and-die "test_sbcl.exe" :executable t :toplevel #'main)
Обращаю внимание(чтобы не было возмущений на тему того, что в лиспе, в отличие от варианта на C++, память не освобождается), что в конце функции main я все-таки вызываю сборщик мусора SBCL, в параметром :full t, что означает, что он проводит "полную" сборку мусора, освобождая все объекты, которые только можно.
Для C++ получилось вот так:
#include #include #include #include struct Cons { void* car; void* cdr; Cons(void* _car, void* _cdr) { car = _car; cdr = _cdr; } }; typedef boost::shared_ptr ConsPtr; typedef std::vector ConsPtrVector; typedef boost::shared_ptr ConsPtrVectorPtr; ConsPtrVectorPtr CopyVector(ConsPtrVectorPtr vec) { int length = (*vec).size(); ConsPtrVectorPtr copy(new ConsPtrVector(length)); for(int i = 0; i<= 0) { std::cout << "Invalid parameter: " << argv[1] << std::endl; return 1; } break; case 3: if((n = atoi(argv[1])) <= 0) { std::cout << "Invalid parameter: " << argv[1] << std::endl; return 1; } if((length = atoi(argv[2])) <= 0) { std::cout << "Invalid parameter: " << argv[2] << std::endl; return 1; } break; default: std::cout << "Usage: test_cpp [ n [ length ] ]" << std::endl; return 1; } Test(n, length); return 0; }
Собирается вариант на лиспе вот такой командой:
А вариант на C++, соответственно, вот такой:
Результаты, если честно, меня удивили. Ну то есть, я предполагал, что вариант со счетчиком ссылок будет медленнее, но чтобы настолько!

Как видно, программа на лиспе, с его сборщиком мусора, отрабатывает быстрее аж более чем в 4.5 раза(~0.6 секунд против ~2.8 секунд для C++). И это еще учитывая что CL - высокоуровневый язык с динамической типизацией, и учитывая всю инициализацию, которую проводит рантайм лисп-системы при запуске.
Ну ладно, думаю, всем известно что GC идеально подходит для множества выделений мелких объектов(а в том варианте цикл прогоняется 1000000 раз, с размером вектора в 100 элементов), надо посмотреть как C++ себя покажет при работе с крупным массивом.

Ну что сказать - отрыв в 3.2 раза(~1.7 секунд против ~5.4) это, конечно, не в 4.5, но где-то рядом :)
Результат, в принципе, становится понятным из ассемблерного кода.
Ну то есть, в случае с SBCL я предполагал, что он мне там нагенерирует, примерно:
http://paste.lisp.org/display/120909
А вот лапшу, в которую вылился код на C++ - я разобрать так и не смог:
http://paste.lisp.org/display/120910
Кто-то еще говорит, что он понимает, во что превратится его код после обработки оптимизирующим компилятором?
|
</> |