Когда использовать objc_memmove_collectable непосредственно в коде Objective-C? - PullRequest
4 голосов
/ 10 февраля 2010

При поиске чего-то еще в заголовочных файлах и документации я недавно обнаружил следующий прототип функции в <objc/objc-auto.h>:

void *objc_memmove_collectable(void *dst, const void *src, size_t size);

Он находится в середине группы функций после комментария, который говорит: «Запишите барьеры. Используется компилятором». Эти функции используются для информирования сборщика мусора о том, что память, которой он управляет, была изменена, чтобы он мог сканировать ссылки, а не случайно восстанавливать память, которая должна быть укоренена сильной ссылкой, о которой он не знает. Я знаю, что такое правильное использование __strong почти всегда заставит компилятор вставить правильную функцию, но я также слышал, что инженеры Apple говорят, что бывают крайне редкие случаи, когда вы должны вызывать такие функции напрямую. (Я считаю это когда файл не скомпилирован как Objective-C, такой как код C, который записывает в память, управляемую GC, но я не уверен.)

В документе Apple Objective-C Runtime для Mac OS X v10.5 указано, что об этой функции говорится следующее (последний абзац под первым заголовком)

"При массовом копировании памяти в блок сбора мусора необходимо использовать API objc_memmove_collectable(void *dst, const void *src, size_t size)."

Эта функция, по-видимому, предназначена для перемещения элементов из не-GC-памяти в GC-память, и архивы дискуссий по электронной почте, похоже, предполагают, что цель состоит в том, чтобы вызвать только один барьер записи для большой блочной копии. (Например, 1000 отдельных записей с барьером записи для каждой, по сравнению с 1 массовой копией с одним барьером записи для всей области памяти, в которую выполняется запись.) Это один случай, когда необходимо использовать , но Документы ничего не говорят о том, когда не следует (или не нужно) использовать.

Например, у меня есть блок памяти, который я выделил с NSAllocateCollectable() и NSScannedOption, и я использую его как динамически расширяющийся циклический буфер. Если буфер заполнится, я удваиваю его размер, используя NSReallocateCollectable() и NSScannedOption. Часть, которая оборачивается (между первым слотом в массиве и последним объектом в буфере), копируется / перемещается в начало второй половины массива. Затем я bzero() слотов, откуда были скопированы данные, чтобы избежать чрезмерного укоренения перемещенных объектов. (См. Строки 460-467 в этого файла . Да, код работает как есть - он полностью тестируется модулем, и я не видел сбоев с тех пор, как некоторое время назад добавил атрибут __strong.)

Вопросы:

  • Когда необходимо использовать objc_memmove_collectable() вместо memmove() или memcpy()?
  • Например, что, если источником и назначением являются память, управляемая GC? (Моя память объявлена ​​как __strong id *array;, поэтому я предполагаю, что компилятор вставляет барьер.)
  • Если это не нужно, поможет ли это улучшить производительность ГХ? (Например, поддерживает ли он какую-либо блокировку или помогает GC избежать сканирования вручную?) Считается ли это хорошим / плохим стилем?

Редактировать: Поскольку memcpy и memmove вообще не взаимодействуют с GC, мне было интересно, почему я не видел сбоев в памяти, собираемой из-под меня. Мое лучшее предположение на данный момент состоит в том, что, поскольку bzero также ничего не сообщает GC, сборщик не узнает об обнуленной памяти и перемещенных данных, пока в следующий раз не просканирует весь этот блок памяти. Если сборщик все еще считает обнуленные ссылки как корни и еще не считает новые области памяти, это объясняет, почему значения не собираются преждевременно. Это звучит правильно?

Ответы [ 2 ]

3 голосов
/ 10 февраля 2010

Я уверен, что @bbum придет с более объяснительным и окончательным ответом, но вот мое понимание, которое может быть не на 100% точным.

Как указано в заголовке комментария, всякий раз, когда вы записываете данные в большом количестве (т. Е. С memcpy или memmove, а не с =) в отсканированный, выделенный GC буфер, вы должны всегда используйте objc_memmove_collectable для создания барьера записи. Источник данных не имеет значения. memcpy не имеет смысла знать, копируете ли вы из отсканированной памяти, выделенной ГХ, в другую отсканированную память, выделенную ГХ.

Я не уверен, что GC будет работать некорректно без барьера записи, но, безусловно, будет работать лучше. Писать барьеры - это намеки коллекционеру: «Эй! Я мог бы написать указатель здесь. Вы не могли бы проверить это для меня?'. Без них сборщик будет вынужден делать полное сканирование все время.

Кроме того, один objc_memmove_collectable() намного эффективнее, чем набор неявно запрещенных для записи = назначений, даже если вы полностью игнорируете стоимость фактической записи памяти. Вместо N. создается только один барьер записи.

1 голос
/ 10 февраля 2010

У меня лично нет большого опыта в этой области, но я нашел интересное обсуждение по теме здесь .

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