Чи є якась документація, яка порівнює / протиставляє стандартні реалізації бібліотеки C ++? [зачинено]


16

(Це не ігрове програмування саме по собі, але я впевнений, якби я запитав це так, мені сказали б не передчасно оптимізувати, навіть якщо історія говорить про те, що кожна велика гра в кінці переживає про ці речі.)

Чи є де-небудь документ, який узагальнює відмінності у продуктивності та, зокрема, у використанні пам'яті, між різними реалізаціями стандартних бібліотек C ++? Деталі деяких реалізацій захищені NDA, але порівняння навіть STLport проти libstdc ++ проти libc ++ проти MSVC / Dinkumware (проти EASTL?) Здається, що це було б дуже корисно.

Зокрема, я шукаю відповіді на такі питання:

  • Скільки накладних витрат на пам'ять пов'язано зі стандартними контейнерами?
  • Які контейнери, якщо такі є, здійснюють динамічні виділення лише шляхом декларування?
  • Чи виконує std :: string копіювання під час запису? Коротка струнна оптимізація? Мотузки?
  • Чи std :: deque використовує кільцевий буфер чи це лайно?

Я був під враженням, що dequeзавжди було реалізовано в STL з вектором.
Тетрад

@Tetrad: Ще кілька тижнів тому я теж був, але потім я читав, що його часто реалізовували канатні структури - і, здається, це є у STLport.

STL має відкритий робочий проект , який може бути використаний для пошуку інформації щодо різних структур даних (як послідовних, так і асоціативних), алгоритмів та реалізованих допоміжних класів. Однак виявляється, що накладні витрати на пам'ять є специфічними для реалізації, а не визначеними специфікаціями.
Томас Рассел

3
@Duck: Розробка ігор - це єдине місце, про яке я знаю, що регулярно використовує функції C ++ високого рівня, однак потрібно ретельно відстежувати розподіл пам’яті, оскільки вона працює на системах без віртуальної пам’яті з низькою пам’яттю. Кожна відповідь на ТА буде: "Не передчасно оптимізуйте, STL добре, використовуйте!" - 50% відповідей на даний момент є такими - і все-таки тест Майка чітко демонструє велику стурбованість ігор, які бажають використовувати std :: map, і плутанину Тетрада і мою щодо звичайних реалізацій std :: deque.

2
@Joe Wreschnig Я дуже не хочу голосувати за закриття, тому що мене цікавлять результати цього. : p
Качка комуніста

Відповіді:


6

Якщо ви не знайдете такої таблиці порівняння, альтернативою є введення власного розподільника до відповідних класів STL та додавання деякого журналу.

Тестованість, яку я тестував (VC 8.0), не використовує розподілення пам'яті, лише оголошуючи рядок / вектор / deque, але для цього список і карта. У рядку є коротка струнна оптимізація, оскільки додавання 3 символів не викликало розподіл. Вихід додається нижче коду.

// basic allocator implementation used from here
// http://www.codeguru.com/cpp/cpp/cpp_mfc/stl/article.php/c4079

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <deque>
#include <list>
#include <map>

template <class T> class my_allocator;

// specialize for void:
template <> 
class my_allocator<void> 
{
public:
    typedef void*       pointer;
    typedef const void* const_pointer;
    // reference to void members are impossible.
    typedef void value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };
};

#define LOG_ALLOC_SIZE(call, size)      std::cout << "  " << call << "  " << std::setw(2) << size << " byte" << std::endl

template <class T> 
class my_allocator 
{
public:
    typedef size_t    size_type;
    typedef ptrdiff_t difference_type;
    typedef T*        pointer;
    typedef const T*  const_pointer;
    typedef T&        reference;
    typedef const T&  const_reference;
    typedef T         value_type;
    template <class U> 
    struct rebind 
    { 
        typedef my_allocator<U> other; 
    };

    my_allocator() throw() : alloc() {}
    my_allocator(const my_allocator&b) throw() : alloc(b.alloc) {}

    template <class U> my_allocator(const my_allocator<U>&b) throw() : alloc(b.alloc) {}
    ~my_allocator() throw() {}

    pointer       address(reference x) const                    { return alloc.address(x); }
    const_pointer address(const_reference x) const              { return alloc.address(x); }

    pointer allocate(size_type s, 
               my_allocator<void>::const_pointer hint = 0)      { LOG_ALLOC_SIZE("my_allocator::allocate  ", s * sizeof(T)); return alloc.allocate(s, hint); }
    void deallocate(pointer p, size_type n)                     { LOG_ALLOC_SIZE("my_allocator::deallocate", n * sizeof(T)); alloc.deallocate(p, n); }

    size_type max_size() const throw()                          { return alloc.max_size(); }

    void construct(pointer p, const T& val)                     { alloc.construct(p, val); }
    void destroy(pointer p)                                     { alloc.destroy(p); }

    std::allocator<T> alloc;
};

int main(int argc, char *argv[])
{

    {
        typedef std::basic_string<char, std::char_traits<char>, my_allocator<char> > my_string;

        std::cout << "===============================================" << std::endl;
        std::cout << "my_string ctor start" << std::endl;
        my_string test;
        std::cout << "my_string ctor end" << std::endl;
        std::cout << "my_string add 3 chars" << std::endl;
        test = "abc";
        std::cout << "my_string add a huge number of chars chars" << std::endl;
        test += "d df uodfug ondusgp idugnösndögs ifdögsdoiug ösodifugnösdiuödofu odsugöodiu niu od unoudö n nodsu nosfdi un abc";
        std::cout << "my_string copy" << std::endl;
        my_string copy = test;
        std::cout << "my_string copy on write test" << std::endl;
        copy[3] = 'X';
        std::cout << "my_string dtors start" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "vector ctor start" << std::endl;
        std::vector<int, my_allocator<int> > v;
        std::cout << "vector ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            v.push_back(i);
        }
        std::cout << "vector dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "deque ctor start" << std::endl;
        std::deque<int, my_allocator<int> > d;
        std::cout << "deque ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "deque insert start" << std::endl;
            d.push_back(i);
            std::cout << "deque insert end" << std::endl;
        }
        std::cout << "deque dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "list ctor start" << std::endl;
        std::list<int, my_allocator<int> > l;
        std::cout << "list ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "list insert start" << std::endl;
            l.push_back(i);
            std::cout << "list insert end" << std::endl;
        }
        std::cout << "list dtor starts" << std::endl;
    }

    {
        std::cout << std::endl << "===============================================" << std::endl;
        std::cout << "map ctor start" << std::endl;
        std::map<int, float, std::less<int>, my_allocator<std::pair<const int, float> > > m;
        std::cout << "map ctor end" << std::endl;
        for(int i = 0; i < 5; ++i)
        {
            std::cout << "map insert start" << std::endl;
            std::pair<int, float> a(i, (float)i);
            m.insert(a);
            std::cout << "map insert end" << std::endl;
        }
        std::cout << "map dtor starts" << std::endl;
    }

    return 0;
}

Поки VC8 та STLPort 5.2 протестовані, ось порівняння (включене в тест: рядок, вектор, deque, список, карта)

                    Allocation on declare   Overhead List Node      Overhead Map Node

VC8                 map, list               8 Byte                  16 Byte
STLPort 5.2 (VC8)   deque                   8 Byte                  16 Byte
Paulhodge's EASTL   (none)                  8 Byte                  16 Byte

Вихідний рядок VC8 / вектор / deque / list / map:

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    128 byte
my_string copy
  my_allocator::allocate    128 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  128 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    12 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate  12 byte
  my_allocator::allocate    24 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  24 byte

===============================================
deque ctor start
deque ctor end
deque insert start
  my_allocator::allocate    32 byte
  my_allocator::allocate    16 byte
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
  my_allocator::allocate    16 byte
deque insert end
deque dtor starts
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  16 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
  my_allocator::allocate    12 byte
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
  my_allocator::allocate    24 byte
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

STLPort 5.2. вихід, складений з VC8

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::deallocate   0 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
deque ctor start
  my_allocator::allocate    32 byte
  my_allocator::allocate    128 byte
deque ctor end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque insert start
deque insert end
deque dtor starts
  my_allocator::deallocate  128 byte
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

Результати EASTL, декев немає

===============================================
my_string ctor start
my_string ctor end
my_string add 3 chars
  my_allocator::allocate     9 byte
my_string add a huge number of chars chars
  my_allocator::allocate    115 byte
  my_allocator::deallocate   9 byte
my_string copy
  my_allocator::allocate    115 byte
my_string copy on write test
my_string dtors start
  my_allocator::deallocate  115 byte
  my_allocator::deallocate  115 byte

===============================================
vector ctor start
vector ctor end
  my_allocator::allocate     4 byte
  my_allocator::allocate     8 byte
  my_allocator::deallocate   4 byte
  my_allocator::allocate    16 byte
  my_allocator::deallocate   8 byte
  my_allocator::allocate    32 byte
  my_allocator::deallocate  16 byte
vector dtor starts
  my_allocator::deallocate  32 byte

===============================================
list ctor start
list ctor end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list insert start
  my_allocator::allocate    12 byte
list insert end
list dtor starts
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte
  my_allocator::deallocate  12 byte

===============================================
map ctor start
map ctor end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map insert start
  my_allocator::allocate    24 byte
map insert end
map dtor starts
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte
  my_allocator::deallocate  24 byte

Це корисно, щоб отримати детальну інформацію про базові розподіли, але, на жаль, нічого не говорить про накладні та очікувану ефективність кешу.

@Joe правильно, важко вирішити всі ваші запитання однією відповіддю. Я не впевнений, що саме ви маєте на увазі під "накладними" та більше того, порівняно з чим? Я подумав, що над головою ви маєте на увазі споживання пам'яті.
Майк Семдер

Під "накладними" я мав на увазі більше розмір порожніх екземплярів структур та всіх пов'язаних з ними ітераторів, і як складніші обробляють розподіл - наприклад, чи std :: list внутрішньо виділяє більше одного вузла одночасно, чи я оплатити базову вартість розподілу для кожного вузла тощо?

1
Питання не стільки "Будь ласка, зробіть це порівняння", а "де ресурс для цього порівняння" - я не думаю, що SO - це хороше місце, щоб "підтримувати" його. Можливо, вам слід почати викидати його на сайт Google або у вікі чи щось таке.

1
@Joe добре тепер його тут: p Мені не дуже цікаво переміщати його на інший сайт, мене просто цікавили результати.
Майк Семдер

8

std::stringне робить копію під час запису. Корова була раніше оптимізацією, але як тільки декілька потоків увійде в малюнок, це поза песимізацією - це може сповільнити код за рахунок масивних факторів. Це так погано, що стандарт C ++ 0x активно забороняє це як стратегію реалізації. Не тільки це, але вседозволеність std::stringвимикання змінних ітераторів та посилань символів означає, що "писати" для цього std::stringтягне за собою майже кожну операцію.

Оптимізація коротких рядків - це приблизно 6 символів, я вважаю, чи щось у цьому регіоні. Мотузки заборонені - вони std::stringповинні зберігати суміжну пам'ять для c_str()функції. Технічно ви могли підтримувати як суміжну струну, так і мотузку в одному класі, але ніхто ніколи цього не робив. Більше того, з моїх відомостей про мотузки, зробити їх безпечними для маніпуляцій з нитками було б неймовірно повільно - можливо, так само погано або гірше, ніж корова.

Жоден контейнер не здійснює розподілення пам'яті, оголошуючись у сучасних STL. Контейнери на основі вузлів, такі як список і карта, як раніше, але тепер у них є вбудована кінцева оптимізація і не потрібна. Це звичайно проводити оптимізацію під назвою "swaptimization", де ви обмінюєтесь порожнім контейнером. Поміркуйте:

std::vector<std::string> MahFunction();
int main() {
    std::vector<std::string> MahVariable;
    MahFunction().swap(MahVariable);
}

Звичайно, в C ++ 0x це зайве, але в C ++ 03 тоді, коли це було загальновживаним, якщо MahVariable виділяє пам'ять на декларування, то це знижує ефективність. Я знаю на факт, що це було використано для більш швидкого перерозподілу контейнерів, як vectorу MSVC9 STL, який усунув необхідність копіювання елементів.

dequeвикористовує щось, про яке йдеться у списку непрокрученого зв’язку. Це в основному список масивів, як правило, у вузлі фіксованого розміру. Таким чином, для більшості застосувань він зберігає переваги обох структур даних - безперервного доступу та амортизованого видалення O (1) та можливість додавання як передньої, так і задньої та кращої відключення ітератора, ніж vector. dequeніколи не може бути реалізований вектором через його алгоритмічну складність та гарантії відключення ітератора.

Скільки пов'язано накладних витрат на пам'ять? Ну, чесно кажучи, це трохи негідне запитання. Контейнери STL розроблені так, щоб вони були ефективними, і якби ви повторювали їх функціональність, ви б або закінчилися чимось гіршим, або знову на тому ж місці. Знаючи їх основні структури даних, ви можете знати накладні витрати на пам'ять, які вони використовують, віддають або беруть, і це буде лише більше, ніж з поважної причини, наприклад, невеликої оптимізації рядків.


"Це так погано, що стандарт C ++ 0x активно забороняє це як стратегію впровадження." І вони забороняють його, тому що попередні реалізації використовували його або намагалися ним користуватися. Ви, мабуть, живете в світі, де кожен весь час використовує останню оптимально реалізовану STL. Ця відповідь зовсім не корисна.

Мені також цікаво, які властивості std :: deque, на вашу думку, запобігають суміжним базовим сховищам - ітератори дійсні лише після видалення на початку / кінці, а не в середині та після будь-яких вставок, які легко можна виконати за допомогою вектора. А використання кругового буфера, здається, відповідає всім алгоритмічним гарантіям - амортизований O (1) вставляє та видаляє на кінцях, O (n) видаляє посередині.

3
@Joe: Я думаю, що корова відзначалася поганою справою з кінця 90-х. Є рядкові реалізації, які використовували його, зокрема CString-, але це не означає, що минуло std::stringчас. Для цього вам не потрібно використовувати найновіші та найбільші в реалізації STL. msdn.microsoft.com/en-us/library/22a9t119.aspx говорить: "Якщо елемент вставлено спереду, усі посилання залишаються дійсними". Не знаєте, як ви плануєте реалізувати це за допомогою кругового буфера, оскільки вам потрібно буде змінити розмір, коли він заповниться.
DeadMG


Я, звичайно, не збираюся захищати COW як техніку впровадження, але я також не наївний щодо того, як часто програмне забезпечення продовжує впроваджуватися з використанням поганих методик довгий час після того, як вони будуть визначені як погані. Наприклад, тест Майка вище виявляє сучасний stdlib, який виділяється на декларування. Дякуємо за покажчик про дійсність посилання на deque. (Для nitpick, вектор може відповідати всім гарантіям щодо недійсності ітератора та алгоритмічної складності; такої вимоги немає). Якщо що, я вважаю це додатково необхідним документом, таким як запитує моє запитання.

2

Питання не стільки "Будь ласка, зробіть це порівняння", а "де ресурс для цього порівняння"

Якщо це справді ваше питання (що, безумовно, ні те, що ви сказали у своєму фактичному тексті запитання, який закінчився чотирма запитаннями, жодне з яких не запитувало, де ви можете знайти ресурс), то відповідь проста:

Немає жодної.

Більшість програмістів на C ++ не повинні так сильно піклуватися про накладні витрати на стандартні бібліотечні структури, кеш-ефективність їх (що дуже високо) залежить від компілятора) або подібні речі. Не кажучи вже про те, що ви зазвичай не обираєте стандартну реалізацію бібліотеки; ви використовуєте те, що постачається з вашим компілятором. Тож навіть якщо це робить якісь неприємні речі, варіанти альтернатив обмежені.

Звичайно, є програмісти, які переймаються подібними справами. Але всі вони давно присягнули використовувати стандартну бібліотеку.

Отже, у вас є одна група програмістів, які просто не байдужі. І ще одна група програмістів, яким би було байдуже, чи використовували вони, але оскільки вони не користуються ним, їм все одно. Оскільки про це ніхто не переймається, реальної інформації про подібні речі немає. Тут і там є неформальні виправлення інформації (Ефективний C ++ має розділ про реалізацію std :: string та величезні відмінності між ними), але нічого вичерпного. І звичайно нічого не оновлювалося.


Спекулятивна відповідь. +1 - напевно, правда, -1 - жодного способу довести це.
deceleratedcaviar

У минулому я бачив багато дуже хороших і детальних порівнянь, але всі вони застаріли. Все, що раніше було введено в хід, в наш час є неактуальним.
Петро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.