поставщики реализуют new и malloc как распределители небольших объектов? - PullRequest
1 голос
/ 03 мая 2019

Разрешено ли реализациям иметь new и / или malloc, чтобы выделять гораздо больше памяти, чем запрошено, так что это может избежать накладных расходов для последующих небольших выделений?

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

Компиляторы уже делают это, или стандарт или другая проблема предотвращают это?

Ответы [ 3 ]

1 голос
/ 03 мая 2019

Большинство операционных систем [нужная ссылка] управляет памятью порциями, обычно называемыми «страницами».Это артефакт базового оборудования.

Давно признано, что библиотека malloc (и, соответственно, new) будет удовлетворять запрос пользователя на память, выделяя один или несколько "страниц "памяти из операционной системы, а затем передают эту память пользователю. (*) Последующие запросы, которые могут быть удовлетворены без запроса большего количества страниц из ОС, будут удовлетворены таким образом.

Кровавые подробности варьируются от системы к системе и от распределителя к распределителю.Обычно они пытаются найти баланс между скоростью (выделения / освобождения) и эффективностью (минимального использования памяти).

Также традиционным является то, что приложения имеют определенные требования к памяти (скорость, эффективность).) сделайте malloc большой кусок памяти за один раз, а затем выполните управление этой памятью самостоятельно.Это добавляет сложность и увеличивает вероятность ошибок (например, память, выделенная через управление памятью приложения, но free() d, или память malloc() ed, но освобожденная через управление памятью приложения, или ошибка в самом управлении памятью приложения), но позволяетприложение для управления используемыми алгоритмами.

C ++ делает это проще с помощью allocators , которые эффективно "передают" управление памятью контейнера другому классу, позволяя использовать настроенную, повторно используемую памятьклассы управления.

Итак:

  1. Да, это возможно.
  2. Нет, ничто в стандарте не запрещает это.
  3. Да, этообычно уже сделано «под капотом».

Следствием 3. является, конечно, старый трюизм мера, оптимизация, мера. (Не пытайтесь оптимизироватьустраните проблему, которой у вас нет, и если она у вас есть, убедитесь, что ваша оптимизация действительно улучшила, а не усугубила ситуацию.)


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

Это называется «переключение контекста», и с точки зрения процессорного времени это одна из самых дорогих операций, которые там есть.Поэтому с самого начала разработчики библиотек искали способы минимизировать переключение контекста.Вот почему malloc и new обычно довольно хорошо оптимизированы для общего использования.

1 голос
/ 03 мая 2019

Делают ли это компиляторы, или стандарт или другая проблема препятствуют этому?

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

Цитирование стандарта C ++ (n4659),

6.7.4.1 Распределениефункции [basic.stc.dynamic.allocation]
...
2 .Функция выделения пытается выделить запрошенный объем памяти. Если он успешен , он должен вернуть адрес начала блока хранения , длина которого в байтах должна быть не меньше, чем запрашиваемый размер.

0 голосов
/ 03 мая 2019

Поставщики (в данном случае libc-Impleors, поскольку malloc / new обычно действительно реализуется вашей стандартной библиотекой, а не провайдером компилятора), делают разные вещи, и обычно вы можете ожидать какую-то оптимизацию небольших объектов и кэширование последнего размера выделения впо крайней мере, до определенного размера.

Например, вот мои тайминги с libc для mallo-free с glibc на Linux x86_64 и степенью двух размеров:

15.61 ns(R)     15.56 ns(U)     0.04 ns(S)      (2050002iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<3})
16.29 ns(R)     16.27 ns(U)     0.01 ns(S)      (1964070iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<4})
16.31 ns(R)     16.29 ns(U)     0.00 ns(S)      (1962244iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<5})
18.17 ns(R)     18.15 ns(U)     0.00 ns(S)      (1761118iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<6})
16.42 ns(R)     16.41 ns(U)     0.00 ns(S)      (1949061iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<7})
15.97 ns(R)     15.96 ns(U)     0.00 ns(S)      (2003412iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<8})
16.14 ns(R)     16.14 ns(U)     0.00 ns(S)      (1982292iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<9})
16.80 ns(R)     16.79 ns(U)     0.00 ns(S)      (1905223iters; 32 ms)   (malloc_free_)(&($_sz){1ULL<<10})
42.19 ns(R)     42.17 ns(U)     0.00 ns(S)      (758535iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<11})
42.90 ns(R)     42.88 ns(U)     0.00 ns(S)      (746074iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<12})
42.85 ns(R)     42.84 ns(U)     0.00 ns(S)      (746926iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<13})
42.32 ns(R)     42.18 ns(U)     0.00 ns(S)      (756378iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<14})
42.59 ns(R)     42.55 ns(U)     0.00 ns(S)      (751520iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<15})
41.98 ns(R)     41.97 ns(U)     0.00 ns(S)      (762451iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<16})
42.74 ns(R)     42.72 ns(U)     0.00 ns(S)      (748953iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<17})
42.32 ns(R)     42.31 ns(U)     0.00 ns(S)      (756267iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<18})
41.99 ns(R)     41.98 ns(U)     0.00 ns(S)      (762255iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<19})
42.31 ns(R)     42.30 ns(U)     0.00 ns(S)      (756442iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<20})
51.03 ns(R)     50.17 ns(U)     0.00 ns(S)      (627259iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<21})
44.93 ns(R)     44.91 ns(U)     0.00 ns(S)      (712362iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<23})
6674.43 ns(R)   677.29 ns(U)    5813.42 ns(S)   (4797iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<25})

и то же самоес musl-libc:

64.09 ns(R)     64.07 ns(U)     0.00 ns(S)      (499411iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<3})
61.49 ns(R)     61.47 ns(U)     0.00 ns(S)      (520542iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<4})
62.67 ns(R)     62.64 ns(U)     0.00 ns(S)      (510794iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<5})
61.53 ns(R)     61.52 ns(U)     0.00 ns(S)      (520150iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<6})
61.49 ns(R)     61.47 ns(U)     0.00 ns(S)      (520514iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<7})
62.78 ns(R)     62.66 ns(U)     0.00 ns(S)      (509871iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<8})
61.38 ns(R)     61.36 ns(U)     0.00 ns(S)      (521468iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<9})
79.97 ns(R)     79.94 ns(U)     0.00 ns(S)      (400374iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<10})
68.77 ns(R)     68.72 ns(U)     0.00 ns(S)      (465530iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<11})
68.21 ns(R)     68.18 ns(U)     0.00 ns(S)      (469345iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<12})
76.55 ns(R)     76.39 ns(U)     0.00 ns(S)      (418194iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<13})
74.67 ns(R)     74.63 ns(U)     0.00 ns(S)      (428704iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<14})
63.95 ns(R)     63.94 ns(U)     0.00 ns(S)      (500507iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<15})
66.33 ns(R)     66.31 ns(U)     0.00 ns(S)      (482528iters; 32 ms)    (malloc_free_)(&($_sz){1ULL<<16})
2629.39 ns(R)   653.03 ns(U)    1975.27 ns(S)   (12174iters; 32 ms)     (malloc_free_)(&($_sz){1ULL<<17})
5776.54 ns(R)   0.00 ns(U)      5474.55 ns(S)   (5542iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<18})
6198.86 ns(R)   708.22 ns(U)    4847.24 ns(S)   (5165iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<19})
6173.01 ns(R)   379.67 ns(U)    5279.59 ns(S)   (5186iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<20})
7029.97 ns(R)   0.00 ns(U)      6224.80 ns(S)   (4555iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<21})
7050.58 ns(R)   1589.07 ns(U)   4757.32 ns(S)   (4541iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<23})
7584.38 ns(R)   0.00 ns(U)      6807.15 ns(S)   (4221iters; 32 ms)      (malloc_free_)(&($_sz){1ULL<<25})

Как видите, небольшие размеры оптимизированы для определенной степени, но по-разному для разных реализаций (многопоточная поддержка без malloc тоже немного варьируется).

Если вы действительно заботитесь о каких-то определенных распределениях небольшого размера, вероятно, лучше использовать собственный распределитель свободных списков и получить наилучшее использование скорости / памяти независимо от вашего бэкенда.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...