Подтвердить что ты не робот

Повысить стоимость разыменования shared_ptr

Я пытаюсь сравнить производительность между необработанными указателями, повысить shared_ptr и повысить слабую_ptr. В части разыменования я ожидал, что shared_ptr и raw_ptr будут равны, но результаты показывают, что shared_ptr примерно в два раза медленнее. Для теста я создаю массив с указателями или общими указателями на ints, а затем разыменовывая в цикле следующим образом:

int result;
for(int i = 0; i != 100; ++i)
{
  for(int i = 0; i != SIZE; ++i)
    result += *array[i];
}

Полный код теста можно найти здесь: https://github.com/coolfluid/coolfluid3/blob/master/test/common/utest-ptr-benchmark.cpp

Тестовые тайминги для оптимизированной сборки без утверждений можно найти здесь: http://coolfluidsrv.vki.ac.be/cdash/testDetails.php?test=145592&build=7777

Значения, представляющие интерес, представляют собой "DerefShared time" и "DerefRaw time"

Я думаю, что тест может быть как-то испорчен, но я не смог понять, откуда эта разница. Профилирование показывает, что оператор * из shared_ptr получает inlined, кажется, что это занимает больше времени. Я дважды проверил, что утверждение boost выключено.

Буду очень признателен, если кто-нибудь сможет объяснить, в чем разница.

Дополнительный автономный тест: https://gist.github.com/1335014

4b9b3361

Ответ 1

Как сказал в своем комментарии Алан Стоукс, это связано с эффектами кеша. Общие указатели включают счетчик ссылок, что означает, что они физически больше в памяти, чем исходный указатель. При хранении в смежном массиве вы получаете меньше указателей на строку кэша, что означает, что цикл должен выходить в основную память чаще, чем для необработанного указателя.

Вы можете наблюдать за этим поведением в тестовом режиме с исходным указателем, выделяя SIZE*2 ints, но также меняя цикл де-ссылки на шаг i+=2 вместо ++i. Это дало примерно одинаковые результаты в моих тестах. Мой код для сырого теста ниже.

#include <iostream>
#include <boost/timer.hpp>

#define SIZE 1000000

typedef int* PtrT;

int do_deref(PtrT* array)
{
    int result = 0;
    for(int i = 0; i != 1000; ++i)
    {
        for(int i = 0; i != SIZE*2; i+=2)
            result += *array[i];
    }

    return result;
}

int main(void)
{
    PtrT* array = new PtrT[SIZE*2];
    for(int i = 0; i != SIZE*2; ++i)
        array[i] = new int(i);
    boost::timer timer;
    int result = do_deref(array);
    std::cout << "deref took " << timer.elapsed() << "s" << std::endl;
    return result;
}

Кстати, использование boost::make_shared<int>(i) вместо PtrT(new int(I)) выделяет счетчик ссылок и объект вместе в памяти, а не в отдельных местах. В моих тестах это улучшило производительность разыменования разделяемого указателя примерно на 10-20%. Код для этого ниже:

#include <iostream>
#include <boost/timer.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#define SIZE 1000000

typedef boost::shared_ptr<int> PtrT;

int do_deref(PtrT* array)
{
    int result = 0;
    for(int j = 0; j != 1000; ++j)
    {
        for(int i = 0; i != SIZE; ++i)
            result += *array[i];
    }

    return result;
}

int main(void)
{
    PtrT* array = new PtrT[SIZE];
    for(int i = 0; i != SIZE; ++i)
        array[i] = boost::make_shared<int>(i);
    boost::timer timer;
    int result = do_deref(array);
    std::cout << "deref took " << timer.elapsed() << "s" << std::endl;
    return result;
}

Мои результаты (x86-64 Unbuntu 11 VM):

Original Raw: 6.93
New Raw: 12.9
Original Shared: 12.7
New Shared: 10.59