2010-11-03 6 views
15

std::realloc является опасным в C++, если память malloc'd содержит не-типы. Кажется, только проблема в том, что std::realloc не вызовет тип деструкторов, если он не может вырастить память на месте.Использование realloc в C++

Тривиальная работа вокруг была бы функцией try_realloc. Вместо того чтобы malloc'ing новой памяти, если она не может быть выращена in situ, она просто вернет false. В этом случае можно было бы выделить новую память, объекты, скопированные (или перемещенные) в новую память, и, наконец, освобожденную память.

Это кажется чрезвычайно полезным. std::vector может очень использовать это, возможно, избегая всех копий/перераспределений.
Упреждающее огнезащитное средство: Технически это то же самое, что и у Big-O, но если рост вектора - это горло бутылки в вашем приложении, скорость x2 хороша, даже если Big-O остается неизменным.

НО, я не могу найти какой-либо c api, который работает как try_realloc.

Я что-то упустил? try_realloc не так полезен, как я себе представляю? Есть ли скрытая ошибка, которая делает try_realloc непригодной для использования?

Еще лучше, есть ли менее документированный API, который работает как try_realloc?

ПРИМЕЧАНИЕ: Я, очевидно, в коде библиотеки/платформы. Я не волнуюсь, так как try_realloc по своей сути является оптимизацией.


Update: После Стив Jessops комментарий по вопросу о том vector будет более эффективным использованием перераспределить я написал доказательство концепции для проверки. realloc-vector имитирует шаблон роста вектора, но имеет возможность повторно использовать вместо него. Я запустил программу до миллиона элементов в векторе.

Для сравнения a vector должен выделять 19 раз, увеличивая до миллиона элементов.

Результаты, если realloc-vector - это единственная вещь, использующая кучу, результаты являются удивительными, 3-4 выделения при увеличении до миллиона байт.

Если realloc-vector используется вместе с vector, который растет на 66% скорости в realloc-vector результаты менее многообещающим, выделяя 8-10 раз во время роста.

И наконец, если realloc-vector используется вместе с vector, который растет с той же скоростью, realloc-vector выделяет 17-18 раз. Едва экономя одно распределение над стандартным поведением вектора.

Я не сомневаюсь, что хакер мог распределить размеры игры, чтобы улучшить сбережения, но я согласен со Стивом в том, что огромная работа по написанию и обслуживанию такого распределителя не работает.

+1

Трудно представить конкретные предложения платформы, не представляя платформу, на которую вы хотите настроить таргетинг. –

+0

Моя цель - gcc + Linux. Тем не менее, мне вообще интересно, поэтому будет рассмотрено решение на любой платформе. –

+2

Я не могу не думать: если вы хотите получить лучшую производительность, используйте vector.reserve(), чтобы вам не нужно было вырастить вектор вообще. –

ответ

11

vector обычно растет большими приращениями. Вы не можете делать это многократно, не пересаживаясь, если вы не тщательно упорядочиваете вещи так, чтобы в большом объеме были бесплатные адреса чуть выше внутреннего буфера вектора (что фактически требует назначения целых страниц, потому что, очевидно, вы не можете иметь другие распределения позже на той же странице).

Так что я думаю, что для того, чтобы получить действительно хорошую оптимизацию, вам потребуется больше, чем «тривиальный обходной путь», что делает дешевую перераспределению, если это возможно - вы должны каким-то образом сделать некоторые приготовления к сделать возможным, и что подготовка стоит вам адрес пространства. Если вы делаете это только для определенных векторов, которые указывают, что они станут большими, то это довольно бессмысленно, потому что они могут указать reserve(), что они станут большими. Вы можете делать это автоматически только для всех векторов, если у вас обширное адресное пространство, чтобы вы могли «отбросить» большой кусок его на каждый вектор.

Как я понимаю, причина, по которой концепция Allocator не имеет функции перераспределения, заключается в ее простоте. Если std::allocator имеет функцию try_realloc, то либо каждый Allocator должен иметь один (который в большинстве случаев не может быть реализован, а просто должен всегда возвращать false), либо каждый стандартный контейнер должен быть специализирован для std::allocator воспользуйтесь этим. Ни один из вариантов не является отличным интерфейсом Allocator, хотя я полагаю, что это не было бы огромным усилием для разработчиков почти всех классов Allocator просто для добавления функции try_realloc do-nothing.

Если vector является медленным из-за перераспределения, deque может быть хорошей заменой.

+0

Я работаю в предположении, что 'try_realloc' тривиально дешев. Когда пропускная способность 'vector' превышается по сравнению с экспоненциальным ростом по умолчанию, он будет использовать' try_realloc' для увеличения емкости на единицу. Когда 'try_realloc' больше не может расти без перераспределения, затем выделяйте в два раза больше необходимой памяти, делайте стандартную копию/перемещайте материал, удваивая емкость. –

+0

Как правило, я не понимаю, почему try_realloc должен быть намного быстрее, чем malloc. –

+0

@caspin: Как это более эффективно, чем то, что фактически делает «вектор», что (ради аргумента) все же удваивает его емкость каждый раз, когда он перемещается? Думаю, все, что у вас получается, - это то, что в вашей версии дополнительная память еще не «действительно выделена», поэтому она доступна для другого использования. Если он не используется, то нет никакой разницы, и если он так используется, вы фактически * замедляете * вектор, так как ваш 'try_realloc' будет терпеть неудачу раньше, чем моя дополнительная емкость будет исчерпана. Даже в моей версии, с overcommit вы можете избежать «действительно использовать» физическую память. –

4

Вы могли бы реализовать что-то вроде try_realloc вы предложили, используя mmap с MAP_ANONYMOUS и MAP_FIXED и mremap с MREMAP_FIXED.

Edit: только что заметил, что страница для mremap даже говорит:

mremap() использует таблицы страниц схемы Linux. mremap() изменяет отображение между виртуальными адресами и страницами памяти. Это можно использовать для реализации очень эффективного realloc (3).

+2

Проблема заключается в том, что минимальное распределение с 'mmap' является страницей, и это массивный избыток для большинства векторов. –

+0

Согласен, но я не думаю, что есть какие-то распределенные распределители на субстранице, которые могут предложить это, и если он значительно меньше размера страницы или, следовательно, преимущества производительности, вероятно, будут незначительными в любом случае. – Flexo

2

realloc in C - это не более чем функция удобства; он имеет очень мало преимуществ для производительности/сокращения копий. Основное исключение, о котором я могу думать, это код, который выделяет большой массив, а затем уменьшает размер, как только необходимый размер известен, но даже это может потребовать переноса данных на некоторые версии malloc (те, которые разделяют блоки строго по размеру), поэтому я рассматриваю это использование realloc действительно плохая практика.

Если вы не постоянно перераспределяете свой массив каждый раз, когда добавляете элемент, но вместо этого увеличиваете его экспоненциально (например, на 25%, 50% или 100%), когда вы исчерпаете пространство, просто вручную выделение новой памяти, копирование и освобождение старого приведет к примерно одинаковой (и идентичной, в случае фрагментации памяти) производительности с использованием realloc. Это, безусловно, подход, который использует реализация C++ STL, поэтому я думаю, что вся ваша забота необоснованна.

Edit: Один (редко, но не неслыханное) случай, когда realloc на самом деле полезно для гигантских блоков в системах с виртуальной памятью, где библиотека C взаимодействует с ядром переместить целые страницы на новые адреса.Причина, по которой я говорю это редко, заключается в том, что вам нужно иметь дело с очень большими блоками (по крайней мере, несколько сотен kB), прежде чем большинство реализаций войдут в сферу управления распределением страницы и, вероятно, намного больше (возможно, несколько MB) перед входом и выходом из ядра для переупорядочения виртуальной памяти дешевле, чем просто делать копию. Конечно, try_realloc здесь не был бы здесь полезен, так как вся польза исходит от фактически делает ход недорого.

+0

Я думаю, что, как только распределение вырастет до мегабайта, абсолютные накладные расходы стратегия экспоненциального распределения может начать становиться проблемой, поэтому оптимизация только в этом диапазоне размеров все равно будет хорошей. Другим случаем, когда realloc является хорошим, является, когда есть цикл, который перераспределяет только один блок памяти, который должен в конечном итоге оказаться в месте кучи, где его можно выращивать без копирования. А также абсолютная стоимость копирования увеличивается с размером блока, поэтому оптимизация, применяемая только к большим блокам, по-прежнему хороша. – hyde

Смежные вопросы