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

топ 100 блогов love5an26.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;
}


Собирается вариант на лиспе вот такой командой:
sbcl --script test.lisp

А вариант на C++, соответственно, вот такой:
g++ -O3 -o test_cpp.exe -I/d/boost_1_46_1/ test.cpp
(я использую MSYS и GCC из mingw32)

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

Ну ладно, думаю, всем известно что GC идеально подходит для множества выделений мелких объектов(а в том варианте цикл прогоняется 1000000 раз, с размером вектора в 100 элементов), надо посмотреть как C++ себя покажет при работе с крупным массивом.
boost::shared_ptr, GC и тормоза
Ну что сказать - отрыв в 3.2 раза(~1.7 секунд против ~5.4) это, конечно, не в 4.5, но где-то рядом :)

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

Оставить комментарий

Архив записей в блогах:
lad (корсиканский) - мальчик der (корсиканский) - там ladder (англ.) - лестница * Неисчерпаем великий Афинский Акрополь - античная классика на все времена! Сколько раз смотришь старые съемки, столько раз открываешь интересное. Это Пропилеи, первое в мире фото выдающейся ...
...
Помимо обычных сидячих поездов разных скоростей и классов, по Египту ходит также и один маршрут спального поезда: "Каир - Асуан - Каир". В пути маршрут где-то 12 часов, проходит ночью. Сели в поезд в Луксоре и проехали на нём до Каира. Начнём с того, что стоимость у поезда ...
Заставь дурака богу молиться. Это про меня. Хотя, я, конечно, загнула - богу молиться. Да, и не заставлял никто. У нас вобще-то ещё вивторак. А что это значит? Правильно. Приходила Люба. Но моё утро началось гораздо раньше этого момента. Потому, что. Нет, я сдержала себя изо всех ...
Герасимов был очень правильный советский художник, приверженец социалистического реализма и синхронных с колебаниями линии партии телодвижений. В 30-е годы он много писал вождей, в том числе создал широко тиражировавшуюся в советской печати картину "Сталин и Ворошилов в Кремле", ...