Ответ 1
Что здесь происходит, это немного сложно, так как оно связано с несколькими различными системами, но это определенно не связано с ценой переключения контекста; ваша программа делает очень мало системных вызовов (проверьте это с помощью strace).
Прежде всего важно понять некоторые основные принципы о том, как обычно реализуются реализации malloc
:
- Большинство реализаций
malloc
получают кучу памяти из ОС, вызываяsbrk
илиmmap
во время инициализации. Объем полученной памяти может быть скорректирован в некоторых реализацияхmalloc
. Как только память будет получена, она обычно нарезается на разные классы размеров и размещается в структуре данных, так что когда программа запрашивает память, например,malloc(123)
, реализацияmalloc
может быстро найти часть памяти, соответствующую этим требованиям. - Когда вы вызываете
free
, память возвращается в свободный список и может быть повторно использована при последующих вызовах наmalloc
. Некоторые реализацииmalloc
позволяют точно настроить, как это работает. - Когда вы выделяете большие куски памяти, большинство реализаций
malloc
просто передают вызовы на огромные объемы памяти прямо на системный вызовmmap
, который выделяет "страницы" памяти во время. Для большинства систем 1 страница памяти составляет 4096 байт. - В большинстве случаев большинство ОС будут пытаться очистить страницы памяти перед передачей их процессам, которые запросили память через
mmap
илиsbrk
. Вот почему вы видите вызовыclear_page_orig
в первичном выходе. Эта функция пытается записать 0s на страницы памяти.
Теперь эти принципы пересекаются с другой идеей, которая имеет много имен, но обычно называется "поисковый пейджинг". Что означает "запрос подкачки", означает, что когда пользовательская программа запрашивает кусок памяти из ОС (например, вызывая mmap
), память выделяется в виртуальном адресном пространстве процесса, но нет физической поддержки RAM, что памяти еще.
Здесь описывается процесс поискового вызова:
- Программа под названием
mmap
для размещения 500 МБ ОЗУ. - Ядро отображает область адресов в адресном пространстве процесса для запрошенной 500 МБ ОЗУ. Он отображает "несколько" (зависимых от ОС) страниц (4096 байт каждый, обычно) физической ОЗУ, чтобы вернуть эти виртуальные адреса.
- Пользовательская программа начинает доступ к памяти, записывая ее.
- В конце концов, пользовательская программа получит доступ к адресу, который действителен, но не имеет физической памяти, поддерживающей его.
- Это приводит к сбою страницы в CPU.
- Ядро отвечает на ошибку страницы, видя, что процесс обращается к действительному адресу, но один без физической ОЗУ, поддерживая его.
- Затем ядро обнаруживает, что ОЗУ выделяет этот регион. Это может быть медленным, если память для других процессов должна быть записана на диск, сначала ( "поменяется" ).
Наиболее вероятная причина, по которой вы видите ухудшение производительности в последнем случае, заключается в следующем:
- В вашем ядре закончилась нулевая страница памяти, которая может быть распределена для выполнения вашего запроса на 40 МБ, поэтому она снова и снова обнуляет память, о чем свидетельствует ваш выход на выходе.
- Вы генерируете pagefaults при обращении к памяти, которая еще не отображается. Поскольку вы получаете доступ к 40 МБ вместо 10 МБ, вы будете генерировать больше ошибок страницы, так как есть больше страниц памяти, которые необходимо сопоставить.
- В качестве еще одного ответа,
memset
есть O (n), что означает, что чем больше памяти вам нужно записать, тем дольше это займет. - Меньше вероятность, так как в эти дни 40mb не так много RAM, но проверьте количество свободной памяти в вашей системе, чтобы убедиться, что у вас достаточно ОЗУ.
Если ваше приложение чрезвычайно чувствительно к производительности, вы можете напрямую вызвать mmap
и:
- передайте флаг
MAP_POPULATE
, который приведет к тому, что все ошибки страницы произойдут спереди и отобразятся все физическая память, - тогда вы не будете платить за ошибки при доступе к странице. - передать флаг
MAP_UNINITIALIZED
, который попытается избежать обнуления страниц памяти до распространения их в вашем процессе. Обратите внимание, что использование этого флага является проблемой безопасности и не должно использоваться, если вы не полностью понимаете последствия использования этого параметра. Возможно, что процессу могут быть выданы страницы памяти, которые использовались другими несвязанными процессами для хранения конфиденциальной информации. Также обратите внимание, что ваше ядро должно быть скомпилировано для разрешения этой опции. Большинство ядер (например, ядро AWS Linux) не поставляются с этой опцией, включенной по умолчанию. Вы почти наверняка не будете использовать этот вариант.
Я бы предупредил вас, что этот уровень оптимизации почти всегда является ошибкой; большинство приложений имеют гораздо более низкие результаты для оптимизации, что не связано с оптимизацией стоимости сбоев страниц. В реальном мире я бы рекомендовал:
- Избегайте использования
memset
на больших блоках памяти, если это действительно необходимо. Большую часть времени обнуление памяти перед повторным использованием одним и тем же процессом не требуется. - Предотвращение выделения и освобождения одних и тех же фрагментов памяти снова и снова; возможно, вы можете просто выделить большой блок вперед и повторно использовать его по мере необходимости позже.
- Использование флага
MAP_POPULATE
выше, если стоимость ошибок страницы при доступе действительно вредна для производительности (маловероятно).
Пожалуйста, оставляйте комментарии, если у вас есть какие-либо вопросы, и я с удовольствием отредактирую этот пост, если это необходимо, раскроем немного.