boost::shared_ptr, GC и тормоза
love5an — 26.03.2011
Я на самом деле очень сильно удивлен тем, что очень много людей
даже примерно не представляют себе, сколько оверхеда вносят
счетчики ссылок, и насколько они менее эффективны, чем нормальный
сборщик мусора.Не перестают меня удивлять и заявления о том, что де, раз в 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
Кто-то еще говорит, что он понимает, во что превратится его код после обработки оптимизирующим компилятором?
|
|
</> |
Полная загрузка станков: где искать выгодные заказы на механическую обработку металла
Unknown artist
Завтрак.
Адвент-календарь: почти финал
Под венец
Воспоминания о былых цветиках
киношные подборки 2
Мой календарь на 2026 год от Живого Журнала

