Использование realloc в С++
std::realloc
является опасным в С++, если память malloc'd содержит типы non-pod. Кажется, единственная проблема заключается в том, что std::realloc
не будет называть деструкторы типа, если он не может вырастить память на месте.
Тривиальная работа вокруг будет функцией 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
по своей сути является оптимизацией.
Обновление:
Следуя словам Стива Джессопса о том, будет ли vector
более эффективным с использованием realloc, я написал доказательство концепции для тестирования. 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
vector
обычно растет большими приращениями. Вы не можете делать это многократно, не перемещаясь, если только вы не тщательно упорядочиваете вещи так, чтобы в них было много свободных адресов чуть выше внутреннего буфера вектора (что фактически требует назначения целых страниц, потому что, очевидно, вы не можете иметь другие распределения позже на той же странице).
Итак, я думаю, что для того, чтобы получить здесь действительно хорошую оптимизацию, вам нужно больше, чем "тривиальное обходное решение", которое делает возможное дешевое перераспределение, если это возможно - вам нужно как-то сделать некоторую подготовку, чтобы сделать это возможным, и что затраты на подготовку вы адресуете пространство. Если вы делаете это только для определенных векторов, которые указывают, что они станут большими, то это довольно бессмысленно, потому что они могут указать с помощью reserve()
, что они станут большими. Вы можете делать это автоматически только для всех векторов, если у вас обширное адресное пространство, чтобы вы могли "отбросить" большой кусок его на каждый вектор.
Как я понимаю, причина, по которой концепция Allocator
не имеет функции перераспределения, заключается в ее простоте. Если std::allocator
имеет функцию try_realloc
, то либо каждый Allocator должен иметь один (который в большинстве случаев не может быть реализован, а просто должен всегда возвращать false), либо каждый стандартный контейнер должен быть специализируется на std::allocator
, чтобы воспользоваться им. Ни один из вариантов не является отличным интерфейсом Allocator, хотя я полагаю, что это не было бы огромным усилием для разработчиков почти всех классов Allocator просто для добавления функции do-nothing try_realloc
.
Если vector
является медленным из-за перераспределения, deque
может быть хорошей заменой.
Ответ 2
Вы можете реализовать что-то вроде try_realloc
, которое вы предложили, используя mmap
с MAP_ANONYMOUS
и MAP_FIXED
и mremap
с помощью MREMAP_FIXED
.
Изменить: просто заметил, что man-страница для mremap даже говорит:
mremap() использует таблицу страниц Linux схема. mremap() изменяет сопоставление между виртуальные адреса и страницы памяти. Это можно использовать для реализации очень эффективный перераспределить (3).
Ответ 3
realloc
в C - не более чем удобная функция; он имеет очень мало преимуществ для производительности/сокращения копий. Основное исключение, о котором я могу думать, это код, который выделяет большой массив, а затем уменьшает размер, как только необходимый размер известен, но даже для этого может потребоваться переместить данные в некоторые реализации malloc
(те, которые разделяют блоки строго по размеру), поэтому я считаю это использование realloc
действительно плохой практики.
Пока вы не постоянно перераспределяете свой массив каждый раз, когда добавляете элемент, но вместо этого увеличиваете его экспоненциально (например, на 25%, 50% или 100%) всякий раз, когда у вас заканчивается свободное пространство, новая память, копирование и освобождение старого приведет к примерно одинаковой (и идентичной, в случае фрагментации памяти) производительности при использовании realloc
. Это, безусловно, подход, который использует реализация С++ STL, поэтому я думаю, что вся ваша забота необоснованна.
Изменить: один (редкий, но неслыханный) случай, когда realloc
действительно полезен для гигантских блоков в системах с виртуальной памятью, где библиотека C взаимодействует с ядром для перемещения целые страницы на новые адреса. Причина, по которой я говорю это редко, заключается в том, что вам нужно иметь дело с очень большими блоками (по крайней мере, несколько сотен kB), прежде чем большинство реализаций войдут в сферу управления распределением страницы и, вероятно, намного больше (возможно, несколько MB) перед входом и выходом из ядра для переупорядочения виртуальной памяти дешевле, чем просто делать копию. Конечно, try_realloc
здесь не будет полезен, так как вся польза исходит из того, что делает движение недорого.