Skip to content

Commit

Permalink
Adds a way to store external data and use them directly within the By…
Browse files Browse the repository at this point in the history
…teBuffer

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
  • Loading branch information
mustiikhalil committed Jan 10, 2025
1 parent a2cd1ea commit 28ae97d
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
*/

import Benchmark
import CoreFoundation
import FlatBuffers
import Foundation

@usableFromInline
struct AA: NativeStruct {
Expand All @@ -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()
Expand Down Expand Up @@ -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))
}
}
}
2 changes: 1 addition & 1 deletion swift.swiftformat
Original file line number Diff line number Diff line change
Expand Up @@ -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 */"
--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 */"
117 changes: 57 additions & 60 deletions swift/Sources/FlatBuffers/ByteBuffer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand All @@ -137,99 +171,64 @@ 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<Bytes: ContiguousBytes>(
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

/// Constructor that creates a Flatbuffer from unsafe memory region without copying
/// - 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.
/// - Parameters:
/// - 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
Expand Down Expand Up @@ -429,10 +428,8 @@ public struct ByteBuffer {
/// - position: the index of the object in the buffer
@inline(__always)
public func read<T>(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
Expand Down
4 changes: 2 additions & 2 deletions swift/Sources/FlatBuffers/FlatBufferBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class FlatBuffersMonsterWriterTests: XCTestCase {
.appendingPathExtension("mon")
let data = try! Data(contentsOf: url)
let _data = ByteBuffer(data: data)

readVerifiedMonster(fb: _data)
}

Expand Down Expand Up @@ -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
Expand All @@ -186,7 +186,7 @@ class FlatBuffersMonsterWriterTests: XCTestCase {
XCTAssertEqual(testUnaligned(), true)
}

func testCopyUnalignedToAlignedBuffers() {
func testReadingRemovedSizeUnalignedBuffer() {
// Aligned read
let fbb = createMonster(withPrefix: true)
let testUnaligned: () -> Bool = {
Expand All @@ -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)
Expand All @@ -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)
Expand Down

0 comments on commit 28ae97d

Please sign in to comment.