Всегда ли гарантировано, что блок в Objective-C захватывает переменную? - PullRequest
0 голосов
/ 08 ноября 2018

Есть ли в Objective-C (Objective-C ++) какие-либо условия, когда компилятор может обнаружить, что захват переменной в блоке никогда не используется, и, таким образом, принять решение не захватывать переменную в первую очередь?

Например, предположим, что у вас есть NSArray, который содержит большое количество предметов, которые могут занять много времени для освобождения. Вам нужно получить доступ к NSArray в главном потоке, но как только вы закончите с ним, вы захотите освободить его в фоновой очереди. Фоновый блок должен только захватить массив, а затем немедленно освободить его. Это на самом деле не имеет ничего общего с этим. Может ли компилятор обнаружить это и «ошибочно» вообще пропустить захват блока?

Пример:

// On the main thread...
NSArray *outgoingRecords = self.records;
self.records = incomingRecords;

dispatch_async(background_queue, ^{
  (void)outgoingRecords;

  // After this do-nothing block exits, then outgoingRecords
  // should be deallocated on this background_queue.  
});

Я гарантирую, что outgoingRecords всегда будет захвачен в этом блоке и что он всегда будет освобожден на background_queue?

Редактировать # 1

Я добавлю немного больше контекста, чтобы лучше проиллюстрировать мою проблему:

У меня есть класс Objective-C ++, который содержит очень большой std :: vector неизменяемых записей. Это может быть легко более 1 миллиона записей. Они являются базовыми структурами в векторе и доступны в главном потоке для заполнения табличного представления. В фоновом потоке другой набор записей базы данных может быть прочитан в отдельный вектор, который также может быть довольно большим.

Как только фоновое чтение произошло, я перехожу к основному потоку, чтобы поменять местами объекты Objective-C и снова заполнить таблицу.

В этот момент меня совершенно не волнует содержание более старого вектора или его родительского класса Objective-C. Там нет причудливых деструкторов или объектного графа для разрушения, но освобождение сотен мегабайт, может быть, даже гигабайт памяти не мгновенно. Так что я готов перенести его в background_queue, и там произойдет освобождение памяти. В моих тестах это работает нормально и дает мне немного больше времени в основном потоке, чтобы делать другие вещи до истечения 16 мс.

Я пытаюсь понять, могу ли я сойти с рук просто захватывая объект в «пустом» блоке или я должен выполнить какую-то операцию без операции (например, вызов count), чтобы компилятор не мог оптимизировать это прочь как-то.

Редактировать # 2

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

Вот еще один сценарий, который не использует dispatch_queues, но все еще использует блоки, и это та часть, которая меня действительно интересует.

id<MTLCommandBuffer> commandBuffer = ...

// A custom class that manages an MTLTexture that is backed by an IOSurface.
__block MyTextureWrapper *wrapper = ... 

// Issue some Metal calls that use the texture inside the wrapper.

// Wait for the buffer to complete, then release the wrapper.
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
  wrapper = nil;
}];

В этом сценарии порядок выполнения гарантируется Металлом. В отличие от приведенного выше примера, в этом сценарии производительность не является проблемой. Скорее IOSurface, который поддерживает MTLTexture, перерабатывается в CVPixelBufferPool. IOSurface распределяется между процессами, и, насколько я могу судить, MTLTexture, по-видимому, не увеличивает useCount на поверхности. Мой класс обертки делает. Когда мой класс-обертка освобожден, значение useCount уменьшается, и затем bufferPool освобождается для переработки IOSurface.

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

1 Ответ

0 голосов
/ 09 ноября 2018

Изменить по адресу: правка вопроса:

Из Clang Спецификация языка для блоков :

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

Компилятору не требуется захватывать переменную, если она может доказать что никакие ссылки на переменную фактически не будут оцениваться. Программисты могут заставить переменную быть захваченной, ссылаясь на нее в оператор в начале блока, вот так :

(void) foo;

Это важно, когда при захвате переменной возникают побочные эффекты, так как она может в Objective-C или C ++ .

(выделение добавлено).

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


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

Во-первых, блок может фактически выполняться до того, как контекст, который его представил, возвращает и освобождает свою сильную ссылку. То есть код, который называется dispatch_async(), может быть заменен на ЦП, и блок может запускаться первым.

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

...