When to use objc_memmove_collectable directly in Objective-C code?

When searching for something else in the header and documentation files, I recently discovered the following function prototype in <objc/objc-auto.h> :

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

It is in the middle of a group of functions following the comment that says: "Write down barriers. Used by the compiler." These functions are used to inform the garbage collector that the memory it manages has been changed, so it can scan links and not accidentally return memory that must be rooted in a strong link that it does not know about. I know that this use of __strong should almost always force the compiler to insert the correct function, but I also heard Apple engineers say that there are extremely rare cases when you have to call such functions directly. (I believe that when a file is not compiled as Objective-C, for example, C code that is written to GC-managed memory, but I'm not sure.)

Unfortunately, Apple Objective-C Runtime Release Notes for Mac OS X v10.5 contains the following information about this feature: (last under the first heading)

"When copying bulk memory to the garbage collector, you should use the objc_memmove_collectable(void *dst, const void *src, size_t size) API objc_memmove_collectable(void *dst, const void *src, size_t size) ."

The function seems focused on moving items from non-GC memory to GC memory, and email message archives seem to suggest that the goal is to trigger only one write barrier for a large copy of the block. (For example, 1000 people record with a write barrier for each of them, compared to 1 mass copy with one write barrier for the entire memory area that is written to.) This is one instance when it should be used, but the documents don’t say anything about when it should not (or is not necessary).

For example, I have a block of memory that I allocated NSAllocateCollectable() and NSScannedOption , and I use it as a dynamically expanding circular buffer. If the buffer fills up, I double its size using NSReallocateCollectable() and NSScannedOption . The part that is wrapped (between the first slot in the array and the last object in the buffer) is copied / moved to the beginning of the second half of the array. Then I bzero() slots in which the data was copied to avoid over-rooting of the moved objects. (See Lines 460-467 in this file . Yes, the code works the same way as it is - it is fully tested, and I did not see any crashes since I added the __strong attribute some time ago.)

Questions:

  • When is it necessary to use objc_memmove_collectable() instead of memmove() or memcpy() ?
  • For example, what if the source and destination are GC-managed memory? (My memory is declared as __strong id *array; so I assume the compiler inserts a barrier.)
  • If this is not necessary, will it help / hinder the work of the GC? (For example, does it hold a lock or helps the GC avoid manual scans?) Is it good / white?

Edit: Since memcpy and memmove do not interact with the GC at all, I was wondering why I did not see any failures from the memory collected from under me. My best guess at this point is that since bzero also does not tell the GC anything, the collector will not know about zeroed memory and moved data until the next time it scans the entire memory block. If the collector still considers null references as roots and does not yet count new memory cells, this explains why the values ​​are not received prematurely. Does this sound right?

+4
source share
2 answers

I'm sure @bbum will come with a more explanatory and definitive answer, but here is my understanding, which may not be 100% accurate.

As the header comment says, whenever you write data in large quantities (i.e. with memcpy or memmove , not c = ) to the scanned GC highlight buffer, you should always use objc_memmove_collectable to create a write barrier. The data source does not matter. memcpy does not have smarts to know if you are copying from scanned allocated GC memory to another scanned, allocated GC memory.

I'm not sure if the GC will not work properly without a write barrier, but it will certainly work better. Writing barriers is a hint of a collector saying, β€œHey! I could write a pointer here. Could you check this for me?” Without them, the collector would be forced to do full scans all the time.

In addition, one objc_memmove_collectable() much more efficient than a bunch of implicitly reserved for write = assignments, even if you completely ignore the cost of the actual memory record. Only one write barrier is created instead of N.

+3
source

I personally do not have much experience in this area, but I found an interesting discussion on the topic here .

+1
source

Source: https://habr.com/ru/post/1300850/


All Articles