From 28ae97d24ee75713d9402051b0b0a61dcc3ca663 Mon Sep 17 00:00:00 2001 From: mustiikhalil <26250654+mustiikhalil@users.noreply.github.com> Date: Fri, 10 Jan 2025 19:37:00 +0100 Subject: [PATCH] Adds a way to store external data and use them directly within the ByteBuffer Adds a way to store external data and use them direclty within them ByteBuffer instead of copying memory into the internal memory of the ByteBuffer --- .../FlatbuffersBenchmarks.swift | 17 ++- swift.swiftformat | 2 +- swift/Sources/FlatBuffers/ByteBuffer.swift | 117 +++++++++--------- .../FlatBuffers/FlatBufferBuilder.swift | 4 +- .../FlatBuffersMonsterWriterTests.swift | 21 +++- 5 files changed, 92 insertions(+), 69 deletions(-) diff --git a/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift b/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift index 21eff5fac5f..6a1478b74ec 100644 --- a/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift +++ b/benchmarks/swift/Benchmarks/FlatbuffersBenchmarks/FlatbuffersBenchmarks.swift @@ -15,8 +15,8 @@ */ import Benchmark -import CoreFoundation import FlatBuffers +import Foundation @usableFromInline struct AA: NativeStruct { @@ -29,6 +29,14 @@ struct AA: NativeStruct { } let benchmarks = { + let data = { + var array = [8888.88, 8888.88] + var data = Data() + array.withUnsafeBytes { ptr in + data.append(contentsOf: ptr) + } + return data + }() let ints: [Int] = Array(repeating: 42, count: 100) let bytes: [UInt8] = Array(repeating: 42, count: 100) let str10 = (0...9).map { _ -> String in "x" }.joined() @@ -198,4 +206,11 @@ let benchmarks = { blackHole(fb.endTable(at: s)) } } + + Benchmark("Reading Doubles") { benchmark in + let byteBuffer = ByteBuffer(data: data, allowReadingUnalignedBuffers: true) + for _ in benchmark.scaledIterations { + blackHole(byteBuffer.read(def: Double.self, position: 0)) + } + } } diff --git a/swift.swiftformat b/swift.swiftformat index f8b2a6b028d..e8b2c324b30 100644 --- a/swift.swiftformat +++ b/swift.swiftformat @@ -24,4 +24,4 @@ --exclude **/swift_code_*.swift --exclude **/*.grpc.swift ---header "/*\n * Copyright {year} Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */" \ No newline at end of file +--header "/*\n * Copyright 2024 Google Inc. All rights reserved.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */" \ No newline at end of file diff --git a/swift/Sources/FlatBuffers/ByteBuffer.swift b/swift/Sources/FlatBuffers/ByteBuffer.swift index 9442c855a6c..9e70bc9b277 100644 --- a/swift/Sources/FlatBuffers/ByteBuffer.swift +++ b/swift/Sources/FlatBuffers/ByteBuffer.swift @@ -26,8 +26,21 @@ public struct ByteBuffer { /// deallocating the memory that was held by (memory: UnsafeMutableRawPointer) @usableFromInline final class Storage { + @usableFromInline + enum Blob { + #if !os(WASI) + case data(Data) + case bytes(ContiguousBytes) + #endif + + case array([UInt8]) + case pointer(UnsafeMutableRawPointer) + case none + } + // This storage doesn't own the memory, therefore, we won't deallocate on deinit. private let unowned: Bool + private let retainedBlob: Blob /// pointer to the start of the buffer object in memory var memory: UnsafeMutableRawPointer /// Capacity of UInt8 the buffer can hold @@ -40,13 +53,40 @@ public struct ByteBuffer { alignment: alignment) capacity = count unowned = false + retainedBlob = .none } @usableFromInline - init(memory: UnsafeMutableRawPointer, capacity: Int, unowned: Bool) { - self.memory = memory + init(blob: Blob, capacity: Int, unowned: Bool = true) { self.capacity = capacity self.unowned = unowned + retainedBlob = blob + memory = UnsafeMutableRawPointer.allocate(byteCount: 2, alignment: 1) + + switch blob { + #if !os(WASI) + case .data(var data): + data.withUnsafeBytes { ptr in + self.memory = UnsafeMutableRawPointer(mutating: ptr.baseAddress!) + self.capacity = ptr.count + } + case .bytes(var bytes): + bytes.withUnsafeBytes { ptr in + self.memory = UnsafeMutableRawPointer(mutating: ptr.baseAddress!) + self.capacity = ptr.count + } + #endif + case .array(var array): + array.withUnsafeBytes { ptr in + self.memory = UnsafeMutableRawPointer(mutating: ptr.baseAddress!) + self.capacity = ptr.count + } + case .pointer(let unsafeMutableRawPointer): + memory = unsafeMutableRawPointer + case .none: + fatalError( + "This initializer should only work when there is a data blob") + } } deinit { @@ -113,21 +153,15 @@ public struct ByteBuffer { public var memory: UnsafeMutableRawPointer { _storage.memory } /// Current capacity for the buffer public var capacity: Int { _storage.capacity } - /// Crash if the trying to read an unaligned buffer instead of allowing users to read them. - public let allowReadingUnalignedBuffers: Bool /// Constructor that creates a Flatbuffer object from a UInt8 /// - Parameter /// - bytes: Array of UInt8 - /// - allowReadingUnalignedBuffers: allow reading from unaligned buffer - public init( - bytes: [UInt8], - allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false) + public init(bytes: [UInt8]) { var b = bytes _storage = Storage(count: bytes.count, alignment: alignment) _writerSize = _storage.capacity - allowReadingUnalignedBuffers = allowUnalignedBuffers b.withUnsafeMutableBytes { bufferPointer in _storage.copy(from: bufferPointer.baseAddress!, count: bytes.count) } @@ -137,49 +171,34 @@ public struct ByteBuffer { /// Constructor that creates a Flatbuffer from the Swift Data type object /// - Parameter /// - data: Swift data Object - /// - allowReadingUnalignedBuffers: allow reading from unaligned buffer public init( - data: Data, - allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false) + data: Data) { - var b = data - _storage = Storage(count: data.count, alignment: alignment) + _storage = Storage(blob: .data(data), capacity: data.count) _writerSize = _storage.capacity - allowReadingUnalignedBuffers = allowUnalignedBuffers - b.withUnsafeMutableBytes { bufferPointer in - _storage.copy(from: bufferPointer.baseAddress!, count: data.count) - } } #endif /// Constructor that creates a Flatbuffer instance with a size /// - Parameter: /// - 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) - allowReadingUnalignedBuffers = false } - #if swift(>=5.0) && !os(WASI) + #if !os(WASI) /// Constructor that creates a Flatbuffer object from a ContiguousBytes /// - Parameters: /// - contiguousBytes: Binary stripe to use as the buffer /// - count: amount of readable bytes - /// - allowReadingUnalignedBuffers: allow reading from unaligned buffer public init( contiguousBytes: Bytes, - count: Int, - allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false) + count: Int) { - _storage = Storage(count: count, alignment: alignment) + _storage = Storage(blob: .bytes(contiguousBytes), capacity: count) _writerSize = _storage.capacity - allowReadingUnalignedBuffers = allowUnalignedBuffers - contiguousBytes.withUnsafeBytes { buf in - _storage.copy(from: buf.baseAddress!, count: buf.count) - } } #endif @@ -187,31 +206,15 @@ public struct ByteBuffer { /// - Parameter: /// - assumingMemoryBound: The unsafe memory region /// - capacity: The size of the given memory region - /// - allowReadingUnalignedBuffers: allow reading from unaligned buffer public init( assumingMemoryBound memory: UnsafeMutableRawPointer, - capacity: Int, - allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false) + capacity: Int) { - _storage = Storage(memory: memory, capacity: capacity, unowned: true) + _storage = Storage( + blob: .pointer(memory), + capacity: capacity, + unowned: true) _writerSize = capacity - allowReadingUnalignedBuffers = allowUnalignedBuffers - } - - /// Creates a copy of the buffer that's being built by calling sizedBuffer - /// - Parameters: - /// - memory: Current memory of the buffer - /// - count: count of bytes - /// - allowReadingUnalignedBuffers: allow reading from unaligned buffer - init( - memory: UnsafeMutableRawPointer, - count: Int, - allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false) - { - _storage = Storage(count: count, alignment: alignment) - _storage.copy(from: memory, count: count) - _writerSize = _storage.capacity - allowReadingUnalignedBuffers = allowUnalignedBuffers } /// Creates a copy of the existing flatbuffer, by copying it to a different memory. @@ -219,17 +222,13 @@ public struct ByteBuffer { /// - memory: Current memory of the buffer /// - count: count of bytes /// - removeBytes: Removes a number of bytes from the current size - /// - allowReadingUnalignedBuffers: allow reading from unaligned buffer init( memory: UnsafeMutableRawPointer, count: Int, - removing removeBytes: Int, - allowReadingUnalignedBuffers allowUnalignedBuffers: Bool = false) + removing removeBytes: Int) { - _storage = Storage(count: count, alignment: alignment) - _storage.copy(from: memory, count: count) + _storage = Storage(blob: .pointer(memory), capacity: count) _writerSize = removeBytes - allowReadingUnalignedBuffers = allowUnalignedBuffers } /// Fills the buffer with padding by adding to the writersize @@ -429,10 +428,8 @@ public struct ByteBuffer { /// - position: the index of the object in the buffer @inline(__always) public func read(def: T.Type, position: Int) -> T { - if allowReadingUnalignedBuffers { - return _storage.memory.advanced(by: position).loadUnaligned(as: T.self) - } - return _storage.memory.advanced(by: position).load(as: T.self) + _storage.memory.advanced(by: position).assumingMemoryBound(to: T.self) + .pointee } /// Reads a slice from the memory assuming a type of T diff --git a/swift/Sources/FlatBuffers/FlatBufferBuilder.swift b/swift/Sources/FlatBuffers/FlatBufferBuilder.swift index 26ae6349154..98cf863c4d3 100644 --- a/swift/Sources/FlatBuffers/FlatBufferBuilder.swift +++ b/swift/Sources/FlatBuffers/FlatBufferBuilder.swift @@ -98,8 +98,8 @@ public struct FlatBufferBuilder { public var sizedBuffer: ByteBuffer { assert(finished, "Data shouldn't be called before finish()") return ByteBuffer( - memory: _bb.memory.advanced(by: _bb.reader), - count: Int(_bb.size)) + assumingMemoryBound: _bb.memory.advanced(by: _bb.reader), + capacity: Int(_bb.size)) } // MARK: - Init diff --git a/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersMonsterWriterTests.swift b/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersMonsterWriterTests.swift index ccd8200bca1..b507d02789b 100644 --- a/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersMonsterWriterTests.swift +++ b/tests/swift/tests/Tests/FlatBuffers.Test.SwiftTests/FlatBuffersMonsterWriterTests.swift @@ -39,6 +39,7 @@ class FlatBuffersMonsterWriterTests: XCTestCase { .appendingPathExtension("mon") let data = try! Data(contentsOf: url) let _data = ByteBuffer(data: data) + readVerifiedMonster(fb: _data) } @@ -176,8 +177,7 @@ class FlatBuffersMonsterWriterTests: XCTestCase { let unlignedPtr = UnsafeMutableRawPointer(baseAddress) var bytes = ByteBuffer( assumingMemoryBound: unlignedPtr, - capacity: ptr.count - 1, - allowReadingUnalignedBuffers: true) + capacity: ptr.count - 1) var monster: Monster = getRoot(byteBuffer: &bytes) self.readFlatbufferMonster(monster: &monster) return true @@ -186,7 +186,7 @@ class FlatBuffersMonsterWriterTests: XCTestCase { XCTAssertEqual(testUnaligned(), true) } - func testCopyUnalignedToAlignedBuffers() { + func testReadingRemovedSizeUnalignedBuffer() { // Aligned read let fbb = createMonster(withPrefix: true) let testUnaligned: () -> Bool = { @@ -201,8 +201,7 @@ class FlatBuffersMonsterWriterTests: XCTestCase { let unlignedPtr = UnsafeMutableRawPointer(baseAddress) let bytes = ByteBuffer( assumingMemoryBound: unlignedPtr, - capacity: ptr.count - 1, - allowReadingUnalignedBuffers: false) + capacity: ptr.count - 1) var newBuf = FlatBuffersUtils.removeSizePrefix(bb: bytes) var monster: Monster = getRoot(byteBuffer: &newBuf) self.readFlatbufferMonster(monster: &monster) @@ -212,6 +211,18 @@ class FlatBuffersMonsterWriterTests: XCTestCase { XCTAssertEqual(testUnaligned(), true) } + func testForceRetainedObject() { + let byteBuffer = { + // swiftformat:disable all + var data: Data? = Data([48, 0, 0, 0, 77, 79, 78, 83, 0, 0, 0, 0, 36, 0, 72, 0, 40, 0, 0, 0, 38, 0, 32, 0, 0, 0, 28, 0, 0, 0, 27, 0, 20, 0, 16, 0, 12, 0, 4, 0, 0, 0, 0, 0, 0, 0, 11, 0, 36, 0, 0, 0, 164, 0, 0, 0, 0, 0, 0, 1, 60, 0, 0, 0, 68, 0, 0, 0, 76, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 120, 0, 0, 0, 0, 0, 80, 0, 0, 0, 128, 63, 0, 0, 0, 64, 0, 0, 64, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 64, 2, 0, 5, 0, 6, 0, 0, 0, 2, 0, 0, 0, 64, 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 30, 0, 40, 0, 10, 0, 20, 0, 152, 255, 255, 255, 4, 0, 0, 0, 4, 0, 0, 0, 70, 114, 101, 100, 0, 0, 0, 0, 5, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 50, 0, 0, 0, 5, 0, 0, 0, 116, 101, 115, 116, 49, 0, 0, 0, 9, 0, 0, 0, 77, 121, 77, 111, 110, 115, 116, 101, 114, 0, 0, 0, 3, 0, 0, 0, 20, 0, 0, 0, 36, 0, 0, 0, 4, 0, 0, 0, 240, 255, 255, 255, 32, 0, 0, 0, 248, 255, 255, 255, 36, 0, 0, 0, 12, 0, 8, 0, 0, 0, 0, 0, 0, 0, 4, 0, 12, 0, 0, 0, 28, 0, 0, 0, 5, 0, 0, 0, 87, 105, 108, 109, 97, 0, 0, 0, 6, 0, 0, 0, 66, 97, 114, 110, 101, 121, 0, 0, 5, 0, 0, 0, 70, 114, 111, 100, 111, 0, 0, 0]) + // swiftformat:enable all + let buffer = ByteBuffer(data: data!) + data = nil + return buffer + }() + readVerifiedMonster(fb: byteBuffer) + } + func readMonster(monster: Monster) { var monster = monster readFlatbufferMonster(monster: &monster)