Skip to content

Add custom backing storage to ByteBuffer #1218

@Joannis

Description

@Joannis

ByteBuffer's API is great for common in-memory binary data storage and for parsing/serializing data.
One of the problem that I find in every single project I have is the poor performance of ByteBuffer's backing storage which Swift is retaining/releasing every single API call. I'd love to have a data type such as UnsafeByteBuffer and UnsafeMutableByteBuffer which is the same API wrapped around a manually allocated/deallocated buffer.

I'm very sure that this would open up many opportunities for optimizing current applications such as Database drivers, JSON libraries and templating libraries. Even if those oppurtunities are there now, it'd still be a great improvement in the ease of doing said operations.

Databases

Many databases will, for example, return a contiguous buffer with many separate entities. For each entity, an encoder will need to be created which decodes the info of a single entity's row/document into a struct/class. I

struct DatabasePacket {
    let buffer: ByteBuffer

    var rows: Int { 
        // computed property to return the amount of rows read from the buffer as indicated by a packet header
    }
}

let packet: DatabasePacket = ...
var dataModels = [DataModel]() // Of course you'd reserve capacity etc...

for _ in 0..<packet.rows {
    // A 
    let rowLength = packet.buffer.readInteger(as: Int32.self)
    let unsafeByteBuffer = packet.readUnsafeByteBuffer(length: Int(rowLength))

    let model = try DatabaseRowDecoder().decode(DataModel.self, from: unsafeByteBuffer)
    dataModels.append(model)
}

UnsafeByteBuffer Pseudo Implementation

I'd directly refer to the pointer and provide an API for reading the data from that. Like ByteBuffer but without wrapping a class for auto-deallocation. It also wouldn't allow mutations and therefore mutation.

struct UnsafeByteBuffer {
    private let pointer: UnsafeRawPointer
    private var readerIndex: Int
    private let totalBytes: Int
}

UnsafeMutableByteBuffer Pseudo Implementation

I'd directly refer to the pointer again to provide an API for reading the data from that. Again without wrapping a class for auto-deallocation. But it would allow mutations, but would fatalError or at least do an assertionFailure if you try to access data outside of bounds.

struct UnsafeMutableByteBuffer {
    private let pointer: UnsafeMutableRawPointer
    public private(set) var readerIndex: Int
    public private(set) var writerIndex: Int
    private let totalBytes: Int
    private var isDeallocated = false

    mutating func deallocate() {
        if isDeallocated { return }
        isDeallocated = true
        pointer.deallocate()
    }
}

The goal of this type is not to read data efficiently, but to modify current buffers manually and/or to provide a useful interface. It might support auto-expansion using mutating methods. But allocation/deallocation is 100% within the developer's control.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions