
Динамо-машины Метод Сократа
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 [ 49 ] 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
Таблица 21.3. Реальные расходы памяти на хранение объекта, рассматриваемые в предположении, что sizeof (string) == 16, указатели и int имеют размер 4 байта, и выделение памяти происходит 16-байтовыми блоками
Контейнер | Теоретический размер данных узла | Реальный размер блока выделенной для узла памяти (с учетом выравнивания и накладных расходов при вьщелении памяти) |
list lt;char gt; | 9 байтов | 16 байтов |
set lt;char gt;, mu1tiset lt;char gt; | 13 байтов | 16 байтов |
list lt;int gt; | 12 байтов | 16 байтов |
set lt;int gt;, multiset lt;int gt; | 16 байтов | 16 байтов |
1ist lt;stri ng gt; | 24 байта | 32 байта |
set lt;string gt;, multiset lt;stri ng gt; | 28 байтов | 32 байта |
В табл. 21.3 приведены результаты простого анализа с использованием рассмотренных выше предположений. Вы можете повторить эксперимент на своей платформе со своим компилятором - просто подставив собственные значения. Чтобы написать программу, которая определяет реальные накладные расходы при выделении блока определенного размера на вашей платформе, обратитесь к приложению 3 в книге Джона Бентли (Jon Bentley) [BentleyOO].
Благодаря табл. 21.3 мы немедленно обнаруживаем один интересный результат: во многих случаях - примерно для 75% всех возможных размеров типа т - накладные расходы при использовании 1 i st и set/multiset в расе м атр и ва с м о й среде оказываются одинаковы. Более того, вот результат, который еще интереснее, - list lt;char gt; и set lt;int gt; имеют в данной среде одни и тс же реальные накладные расходы, несмотря на то, что последний контейнер хранит данные большего размера и требует большего количества дополнительной информации для каждого узла.
Если используемое количество памяти является важным фактором при выборе вами структуры данных в конкретной ситуации, потратьте несколько минут на проведение подобного анализа для вашей системы и посмотрите на реальные различия в потреблении памяти разными контейнерами - иногда эти различия гораздо меньше, чем вы думаете!
Резюме
Для каждого вида контейнера определяются свои компромиссы между расходами памяти и производительностью. При использовании vector и set можно быстро решить те задачи, которые невозможно столь же эффективно решить при использовании 1 ist - например, поиск за время 0(log N); при использовании vector можно сделать веши, невозможные при применении list или set - например, произвольный доступ. Вставка элемента в средину легко выполняется в 1 i st, менее эффективно-в set, и очень медленно - в vector. Словом, примеров таких по-разному выполняющихся задач очень много. Большая гибкость зачастую требует больших накладных расходов памяти, но если учесть выравнивание данных и возможные стратегии распределения памяти, то различие может оказаться куда меньшим, чем вы думаете! Вопросы выравнивания данных и оптимизации использования памяти рассматривались также в книге [SutterOO].
Если содержимое вектора отсортировано. Задача 21. Контейнеры в памяти. Часть 2: какие они на самом деле?
gt; Рекомендация
Следует четко представлять, к каким реальным расходам памяти приводит использование различных видов контейнеров и стратегии распределения динамической памяти.
Задача 22, Новый взгляд на new.
Часть 1: многоликий оператор new Сложность: 4
Любой класс, предоставляющий собственный оператор new или new[], должен также обеспечить соответствующие версии обычного оператора new, размещающего new и new, не генерирующего исключений. В противном случае у пользователей вашего класса могут возникнуть ненужные проблемы.
Вопрос ДЛЯ новичка
1. Какие три варианта оператора new описаны в стандарте С++? Вопрос для профессионала
2. Что такое оператор new, специфичный для класса, и как им следует пользоваться? Опишите, в каких случаях вы должны быть особо осторожны при предоставлении собственных, специфичных для класса операторов new и delete.
3. Какой именно оператор new вызывается в приведенном далее коде в строках, пронумерованных от 1 до 4?
class Base {
public:
static void* operator new(std::si2e t, const FastMemoryA);
class Derived : public Base {
...
Derived* pi = new Derived; 1
Derived* p2 = new (std::nothrow) Derived; 2
void* p3 = /* Некоторая область памяти, достаточная
для размещения Derived */ ; new (рЗ) Derived; 3
FastMemory f;
Derived* p4 = new (f) Derived; 4
Решение
в этой и следующей задачах я хочу сформулировать и обосновать два основных совета.
Любой класс, предоставляющий собствснный оператор new или new[], должен также обеспечить соответствующие версии обычного оператора new, размещающего new и new, не генерирующего исключений. В противном случае у пользователей вашего класса могут возникнуть ненужные проблемы.
Избегайте использования new(nothrow) и убедитесь, что, когда вы проверяете отказ new, вы действительно проверяете именно то, что хотите проверить.
Советы могут показаться неожиданными, поэтому давайте детально их проанализируем. Для простоты я не упоминаю отдельно оператор new для массивов, но все сказанное об операторе new для одного объекта, относится и к нему.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 [ 49 ] 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |