Swift 如何避免内存碎片

How memory fragmentation is avoided in Swift

GC的压缩、扫描和标记避免了堆内存碎片。那么Swift是如何避免内存碎片的呢?

这些说法正确吗?

  1. 每次引用计数变为零时,分配的 space 就会添加到 'available' 列表中。
  2. 对于下一次分配,使用最前面的适合该大小的内存块。
  3. 之前用完的内存块将尽可能再次使用

'available list' 是否按地址位置或大小排序?

是否会移动活动对象以更好地压缩?

我在编译的 Swift 程序的汇编中进行了一些挖掘,我发现 swift:: swift_allocObject is the runtime function called when a new Class object is instantiated. It calls SWIFT_RT_ENTRY_IMPL(swift_allocObject) which calls swift::swift_slowAlloc,它最终从 C 标准库调用... malloc()。所以 Swift 的运行时不做内存分配,它是 malloc() 做的。

malloc() 在 C 库中实现 (libc)。可以看到苹果实现libchere. malloc() is defined in /gen/malloc.c。如果您对确切使用的内存分配算法感兴趣,您可以从 那里。

Is the 'available list' sorted by address location or size?

这是 malloc 的实现细节,欢迎您在上面链接的源代码中发现。

1. Every time a reference count becomes zero, the allocated space gets added to an 'available' list.

是的,没错。除了“可用”列表可能不是列表。此外,此操作不一定由 Swift 运行时库完成,但可以由 OS 内核通过系统调用完成。

2. For the next allocation, the frontmost chunk of memory which can fit the size is used.

不一定是最前面的。有许多不同的内存分配方案。你想到的那个叫做“第一次适应”。以下是一些示例内存分配技术(来自 this site):

  • Best fit: The allocator places a process in the smallest block of unallocated memory in which it will fit. For example, suppose a process requests 12KB of memory and the memory manager currently has a list of unallocated blocks of 6KB, 14KB, 19KB, 11KB, and 13KB blocks. The best-fit strategy will allocate 12KB of the 13KB block to the process.

  • First fit: There may be many holes in the memory, so the operating system, to reduce the amount of time it spends analyzing the available spaces, begins at the start of primary memory and allocates memory from the first hole it encounters large enough to satisfy the request. Using the same example as above, first fit will allocate 12KB of the 14KB block to the process.

  • Worst fit: The memory manager places a process in the largest block of unallocated memory available. The idea is that this placement will create the largest hold after the allocations, thus increasing the possibility that, compared to best fit, another process can use the remaining space. Using the same example as above, worst fit will allocate 12KB of the 19KB block to the process, leaving a 7KB block for future use.

对象在其生命周期内不会被压缩。 libc 通过分配内存的方式处理内存碎片。它无法移动已经分配的对象。

Alexander 的回答很好,但还有一些与内存布局有关的其他细节。垃圾回收需要内存开销来进行压缩,因此 malloc 碎片造成的浪费 space 并没有真正使它处于劣势。移动内存也会影响电池和性能,因为它会使处理器缓存失效。 Apple 的内存管理实现可以压缩一段时间未访问的内存。尽管虚拟地址 space 可以碎片化,但由于压缩,实际 RAM 碎片较少。压缩还允许更快地交换到磁盘。

相关性较低,但 Apple 选择引用计数的重要原因之一更多地与 c 调用有关,然后是内存布局。如果您与 c 库进行大量交互,Apple 的解决方案效果会更好。垃圾收集系统与 c 的交互通常要慢一个数量级,因为它需要在调用之前停止垃圾收集操作。开销通常与任何语言的系统调用大致相同。通常这无关紧要,除非您在循环中调用 c 函数,例如使用 OpenGL 或 SQLite。其他 threads/processes 可以在 c-call 等待垃圾收集器时正常使用处理器资源,因此如果您可以在几次调用中完成工作,影响就会很小。将来 Swift 的内存管理在系统编程和类似 rust 的生命周期内存管理方面可能会有优势。它在 Swift 的路线图上,但 Swift 4 尚不适合系统编程。通常在 C# 中,对于大量使用 C 库的系统编程和操作,您会转而使用托管 C++。