[Swift] Memory usage fix (#8643)

Allows a complete reset for the underlying memory of the
_InternalByteBuffers within FlatBuffers and FlexBuffers.
This commit is contained in:
mustiikhalil
2025-07-18 18:37:58 +02:00
committed by GitHub
parent 2e49b3ba60
commit ca73ff34b7
14 changed files with 146 additions and 58 deletions

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if canImport(Common)
import Common
#endif

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if canImport(Common)
import Common
#endif

View File

@@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if canImport(Common)
import Common
#endif
@@ -72,14 +73,32 @@ public struct FlexBuffersWriter {
return ByteBuffer(byteBuffer: _bb)
}
#if !os(WASI)
/// Data representation of the buffer
///
/// Should only be used after ``finish(offset:addPrefix:)`` is called
public var data: Data {
assert(finished, "Data shouldn't be called before finish()")
return _bb.withUnsafeSlicedBytes { ptr in
var data = Data()
data.append(
ptr.baseAddress!.bindMemory(
to: UInt8.self,
capacity: ptr.count),
count: ptr.count)
return data
}
}
#endif
/// Resets the internal state. Automatically called before building a new flexbuffer.
public mutating func reset() {
_bb.clear()
stack.removeAll(keepingCapacity: true)
public mutating func reset(keepingCapacity: Bool = false) {
_bb.clear(keepingCapacity: keepingCapacity)
stack.removeAll(keepingCapacity: keepingCapacity)
finished = false
minBitWidth = .w8
keyPool.removeAll()
stringPool.removeAll()
keyPool.removeAll(keepingCapacity: keepingCapacity)
stringPool.removeAll(keepingCapacity: keepingCapacity)
}
// MARK: - Storing root

View File

@@ -30,8 +30,6 @@ struct _InternalByteBuffer {
/// deallocating the memory that was held by (memory: UnsafeMutableRawPointer)
@usableFromInline
final class Storage {
// This storage doesn't own the memory, therefore, we won't deallocate on deinit.
private let unowned: Bool
/// pointer to the start of the buffer object in memory
var memory: UnsafeMutableRawPointer
/// Capacity of UInt8 the buffer can hold
@@ -43,35 +41,25 @@ struct _InternalByteBuffer {
byteCount: count,
alignment: alignment)
capacity = count
unowned = false
}
@usableFromInline
init(memory: UnsafeMutableRawPointer, capacity: Int, unowned: Bool) {
self.memory = memory
self.capacity = capacity
self.unowned = unowned
}
deinit {
if !unowned {
memory.deallocate()
}
memory.deallocate()
}
@usableFromInline
func copy(from ptr: UnsafeRawPointer, count: Int) {
assert(
!unowned,
"copy should NOT be called on a buffer that is built by assumingMemoryBound")
memory.copyMemory(from: ptr, byteCount: count)
}
@usableFromInline
func initialize(for size: Int) {
assert(
!unowned,
"initalize should NOT be called on a buffer that is built by assumingMemoryBound")
memset(memory, 0, size)
}
@@ -100,6 +88,8 @@ struct _InternalByteBuffer {
}
@usableFromInline var _storage: Storage
// Initial size of the internal storage
private let initialSize: Int
/// The size of the elements written to the buffer + their paddings
var writerIndex: Int = 0
/// Alignment of the current memory being written to the buffer
@@ -122,17 +112,21 @@ struct _InternalByteBuffer {
/// - size: Length of the buffer
/// - allowReadingUnalignedBuffers: allow reading from unaligned buffer
init(initialSize size: Int) {
let size = size.convertToPowerofTwo
_storage = Storage(count: size, alignment: alignment)
_storage.initialize(for: size)
initialSize = size.convertToPowerofTwo
_storage = Storage(count: initialSize, alignment: alignment)
_storage.initialize(for: initialSize)
}
/// Clears the current instance of the buffer, replacing it with new memory
@inline(__always)
mutating public func clear() {
mutating public func clear(keepingCapacity: Bool = false) {
writerIndex = 0
alignment = 1
_storage.initialize(for: _storage.capacity)
if keepingCapacity {
_storage.initialize(for: _storage.capacity)
} else {
_storage = Storage(count: initialSize, alignment: alignment)
}
}
@inline(__always)