Подтвердить что ты не робот

Имеют ли функции выделения памяти, что содержимое памяти больше не используется?

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

В момент, когда освобождается память, используемая для временной строки, содержимое строки может очень хорошо проживать только в кеше. Однако CPU не осознает освобождение памяти: освобождение - это просто обновление в системе управления памятью. В результате ЦП может в конечном итоге записать ненужный контент без необходимости в фактическую память, когда кэш ЦП используется для другой памяти, если только релиз памяти не указывает на то, что память больше не используется. Следовательно, возникает вопрос:

Предоставляют ли функции управления памятью освобождение памяти, что содержимое соответствующей памяти может быть отброшено? Есть ли способ указать CPU, что память больше не используется? (по крайней мере, для некоторых процессоров: может быть, очевидно, различия между архитектурами). Поскольку различные реализации, вероятно, будут отличаться по качеству и могут или не могут сделать что-то фантастическое, вопрос действительно в том, есть ли какая-либо реализация управления памятью, указывающая на память как неиспользованную

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

4b9b3361

Ответ 1

Нет.

Операция кэширования, которую вы упоминаете (маркировка кэшированной памяти как неиспользуемой и отбрасывающей без обратной записи в основную память) называется отказом в кэшировании без обратной записи. Это выполняется с помощью специальной инструкции с операндом, который может (или не указывать) указывать, что адрес кэширования недействителен.

На всех архитектурах, с которыми я знаком, эта инструкция имеет привилегию, на мой взгляд. Это означает, что код usermode не может использовать инструкцию; Только ядро ​​может. Количество извращенных обманов, потеря данных и отказ в обслуживании, которые были бы возможны в противном случае, невероятны.

В результате, никакой распределитель памяти не может делать то, что вы предлагаете; У них просто нет (в usermode) инструментов для этого.

Архитектурная поддержка

  • Архитектура x86 и x86-64 имеет привилегированную инструкцию invd, которая делает недействительными все внутренние кеши без обратной записи и также направляет внешние кэши также на недействительность. Это единственная инструкция, способная сделать недействительной без обратной записи, и это действительно тупое оружие.
    • Непривилегированная команда clflush указывает адрес жертвы, но она записывает обратно до отмены, поэтому я упоминаю ее только в передаче.
    • Документация для всех этих инструкций приведена в Intel SDM, том 2.
  • Архитектура ARM выполняет кэширование без обратной записи запись в сопроцессор 15, регистр 7: MCR p15, 0, <Rd>, c7, <CRm>, <Opcode_2>, Можно указать кеш жертву. Записи в этом регистре являются привилегированными.
  • PowerPC имеет dcbi, который позволяет указать жертву dci, которая не поддерживает и кеширование версий обоих, но все четыре являются привилегированными (см. стр. 1400).
  • MIPS имеет команду CACHE, которая может указывать жертву. Это было привилегировано от MIPS Instruction Set v5.04, но в 6.04 Imagination Technologies замучили воду, и уже не понятно, что привилегированное, а что нет.

Таким образом, это исключает использование недействительности кэша без кратковременного сброса/записи в usermode.

Режим ядра?

Однако, я бы сказал, что это по-прежнему плохая идея в kernelmode по многим причинам:

  • Распределитель Linux, kmalloc(), выделяет вне арены для разных размеров распределений. В частности, он имеет арену для каждого размера распределения <=192 байтов с шагом 8; Это означает, что объекты могут быть потенциально ближе друг к другу, чем кэш-линии, или частично перекрывать следующую, а использование недействительности может, таким образом, выдувать соседние объекты, которые были справедливо в кеше и еще не были записаны. Это неверно.
    • Эта проблема усугубляется тем фактом, что строки кэша могут быть довольно большими (на x86-64, 64 байта) и, кроме того, не обязательно одинаковы по размеру в иерархии кеша. Например, в Pentium 4 были кешины с поддержкой 64B L1, но кэш-линии 128B L2.
  • Это приводит к тому, что время освобождения будет линейным по количеству линий кэша объекта для освобождения.
  • Он имеет очень ограниченную выгоду; Размер кеша L1 обычно находится в КБ, поэтому несколько тысяч флешей полностью опустошают его. Кроме того, кеш может уже сбросить данные без вашего запроса, поэтому ваша недействительность хуже, чем бесполезно: используется полоса пропускания памяти, но у вас больше нет строки в кеше, поэтому, когда она будет частично записана, ей потребуется быть отозванными.
  • В следующий раз, когда распределитель памяти вернет этот блок, который может быть вскоре, его пользователь будет иметь гарантированную пропущенную кешировку и выборку из основной ОЗУ, в то время как у него могла бы быть грязная несвязанная линия или чистая очищенная линия. Стоимость гарантированного промаха кэша и выборки из основной оперативной памяти значительно больше, чем строка с кешем, без аннулирования, которая автоматически и разумно заправляется аппаратным обеспечением кеширования.
  • Дополнительный код, необходимый для цикла и очистки этих строк, отнимает пространство кэша команд.
  • Лучшее использование для десятков циклов, взятых вышеупомянутым циклом для недействительности кешлин, было бы продолжать делать полезную работу, позволяя значительной пропускной способности кэша и подсистемы памяти записывать ваши грязные строки.
    • Мой современный процессор Haswell имеет 32 байта/тактовый цикл записи L1 и ширину полосы пропускания 25 ГБ/с. Я уверен, что еще несколько лишних 32-байтных кешлин можно сжать где-то там.
  • Наконец, для недолговечных небольших распределений, подобных этому, есть возможность выделить его в стеке.

Фактическая практика распределения памяти

  • Известный dlmalloc не отменяет освобожденную память.
  • glibc не отменяет освобожденную память.
  • jemalloc не отменяет освобожденную память.
  • musl -libc malloc() не отменяет освобожденную память.

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

Ответ 2

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

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

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

Edit: Если вы хотите расширить определение того, что считаете "памятью", вы можете искать непоследовательные типы памяти, которые различаются по определению и реализации, но некоторые могут предоставить то, что вы ищете. В некоторых архитектурах отображается " scratchpad" память", которая контролируется пользователем и обеспечивает быстрый доступ без хлопот когерентности кэш-памяти (но также и без выгоды). Некоторые архитектуры даже доходят до настраиваемого оборудования, которое позволяет вам выбрать, хотите ли вы кэшировать основную память в нем или использовать его в качестве области блокнота.

Ответ 3

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

И сегодня размеры RAM обычно настолько велики, что, когда ОС начинает писать грязные страницы в хранилище резервных копий, у вас есть проблемы независимо от того, что. Если у вас 16 ГБ ОЗУ, вы не будете писать сотни килобайт или мегабайт, вы будете писать гигабайты, а ваш компьютер будет замедляться до обхода. Пользователь избегает ситуации, не используя приложения, которые используют слишком много памяти.

Ответ 4

Довольно много распределителей хранят "список свободных блоков" в самих свободных блоках. То есть когда вы вызываете эту функцию освобождения, выделенный блок сплайсируется в свободный список, что может означать переписывание старых данных указателями вперед и назад. Эти записи будут перезаписывать, по крайней мере, первую часть выделения.

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

Проблема с вашей идеей заключается в том, что каждая отдельная запись на самом деле не такая дорогостоящая, и выяснение того, что можно отбросить, потребует довольно дорогого бухгалтерского учета. Реалистично вы не можете сделать syscall. Это означает, что вам нужно делать бухгалтерию в каждом приложении (что разумно: освобождение этих небольших блоков обычно возвращает память в приложение, а не в ОС). Это, в свою очередь, означает, что приложение должно знать о дизайне кэша процессора, что отнюдь не является постоянным. Приложение даже должно было знать о различных схемах когерентности кэшей!

Ответ 5

Здесь вы задаете несколько связанных вопросов. Самый смелый ответ - самый простой. Когда вы выпускаете память с чем-то вроде родового типа выпуска, единственное, что вы говорите, это "Мне это больше не нужно". Вы также неявно говорите: "Мне все равно, что вы с ним делаете". Это "мне все равно" на самом деле является ответом на ваш вопрос. Вы не говорите "вы можете отказаться от этого". Вы говорите: "Мне все равно, если вы отбросите это или нет".

Чтобы ответить на вопрос о поддержке ЦП, протокол MSI является базовым протоколом кэш-когерентности. Состояние I означает "недействительный", что позволяет реализовать состояние "не используется", о котором вы спрашиваете. Для этого вы создадите интерфейс выпуска с неродственной семантикой, то есть этот вид выпуска означает "Эта память больше не используется, и вы должны избегать ее записи в основную память". Обратите внимание, что в этой семантике есть требование о поведении процессора, которого нет в стандартной версии. Чтобы реализовать это, вам необходимо распределить память в соответствии с кэшем ЦП, а затем использовать инструкции ЦПУ для недействительности элементов кэша. Вам почти наверняка понадобится написать код сборки, чтобы эта работа позволила избежать необоснованных (и неверных) предположений о модели памяти, которая могла бы вызвать использование команды явного управления кэшем.

Мне лично не нужно было работать на этом уровне за какое-то время, поэтому я не знаком с тем, что доступно во всем мире, то есть может ли эта техника быть достаточно переносимой. У процессора Intel есть команда INVLPG. Обсуждение здесь должно стать достойной стартовой площадкой для следующего этапа ваших проблем: Когда делать или не делать INVLPG, MOV для CR3, чтобы минимизировать промывку TLB