Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Sources/Arrow/Array/Builder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,13 @@ class ArrayBuilderVariable<T: VariableLength> {
if let val {
nullBuilder.appendValid(true)
let data = val.data
if valueBuilder.length + data.count > valueBuilder.capacity {
valueBuilder.doubleCapacity()
let requiredCapacity = valueBuilder.length + data.count
if requiredCapacity > valueBuilder.capacity {
var newCapacity = valueBuilder.capacity
while newCapacity < requiredCapacity {
newCapacity *= 2
}
valueBuilder.increaseCapacity(to: newCapacity)
}
valueBuilder.append(data)
let newOffset = UInt32(valueBuilder.length)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Arrow/ArrowData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ public struct ArrowData {
}

// TODO: this should replace nullBuffer
var nulls: NullBuffer {
var nulls: BitPackedNullBuffer {
let buffer = buffers[0]
let pointer = buffer.rawPointer.assumingMemoryBound(to: UInt8.self)
return NullBuffer(
return BitPackedNullBuffer(
length: Int(buffer.length), capacity: 0, ownsMemory: false,
buffer: pointer)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,34 @@
// See the License for the specific language governing permissions and
// limitations under the License.

/// A type used to represent nulls and booleans in Arrow arrays.
protocol NullBuffer {
var length: Int { get }
func isSet(_ bit: Int) -> Bool
}

/// Represents an array with no nulls (all values valid).
struct AllValidNullBuffer: NullBuffer {
let length: Int

func isSet(_ bit: Int) -> Bool {
precondition(bit < length)
return true
}
}

/// Represents an array with all nulls.
struct AllNullBuffer: NullBuffer {
let length: Int

func isSet(_ bit: Int) -> Bool {
precondition(bit < length)
return false
}
}

/// A bit-packed buffer used to represent nulls and booleans in Arrow arrays.
final class NullBuffer {
final class BitPackedNullBuffer: NullBuffer {
var length: Int
var capacity: Int
let ownsMemory: Bool
Expand Down
11 changes: 8 additions & 3 deletions Sources/Arrow/Buffer/FixedBufferBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,22 @@ final class FixedWidthBufferBuilder<T: Numeric> {
private var bitOffset: Int8 = 0

init(
minCapacity: Int = 4096
minCapacity: Int = 1024
) {
self.length = 0
self.capacity = minCapacity / MemoryLayout<T>.size
// Ensure at least 1 element capacity
self.capacity = max(1, minCapacity / MemoryLayout<T>.size)
self.buffer = .allocate(capacity: capacity)
self.ownsMemory = true
}

func append(_ val: T) {
if length >= capacity {
resize(to: capacity * 2)
var newCapacity = capacity * 2
while length >= newCapacity {
newCapacity *= 2
}
resize(to: newCapacity)
}
buffer[length] = val
length += 1
Expand Down
46 changes: 30 additions & 16 deletions Sources/Arrow/Buffer/NullBufferBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ final class NullBufferBuilder {
var length: Int
var capacity: Int
var bitCount: Int = 0
var valueCount: Int = 0
var nullCount: Int = 0
private var buffer: UnsafeMutablePointer<UInt8>
private var ownsMemory: Bool
private var currentByte: UInt8 = 0
Expand All @@ -36,8 +38,11 @@ final class NullBufferBuilder {
/// Appends a validity bit to the buffer.
@inline(__always)
func appendValid(_ isValid: Bool) {
valueCount += 1
if isValid {
currentByte |= 1 << bitOffset
} else {
nullCount += 1
}
bitOffset += 1
bitCount += 1
Expand Down Expand Up @@ -76,26 +81,35 @@ final class NullBufferBuilder {
/// Builds completed `NullBuffer` with 64-byte alignment, flushing any intermediate state.
///
/// Memory ownership is transferred to the returned `NullBuffer`. Any memory held is deallocated.
/// - Returns: the completed `NullBuffer` with capacity shrunk to a multiple of 64 bytes.
/// - Returns: The completed `NullBuffer` which in the case of zero nulls will be a struct
/// which always reports validity to be true, or if all nulls the inverse of this. Otherwise a buffer with
/// capacity shrunk to a multiple of 64 bytes will be returned.
///
func finish() -> NullBuffer {
if bitOffset != 0 {
flushByte()
}
precondition(ownsMemory, "Buffer already finished.")
ownsMemory = false
// defer { ownsMemory = false }
let newCapacity = (length + 63) & ~63
let newBuffer = UnsafeMutableRawPointer.allocate(
byteCount: newCapacity,
alignment: 64
).bindMemory(to: UInt8.self, capacity: newCapacity)
newBuffer.initialize(from: buffer, count: length)
buffer.deallocate()
return NullBuffer(
length: length,
capacity: newCapacity,
ownsMemory: true,
buffer: newBuffer
)

if nullCount == 0 {
return AllValidNullBuffer(length: valueCount)
} else if nullCount == valueCount {
return AllNullBuffer(length: valueCount)
} else {
ownsMemory = false
let newCapacity = (length + 63) & ~63
let newBuffer = UnsafeMutableRawPointer.allocate(
byteCount: newCapacity,
alignment: 64
).bindMemory(to: UInt8.self, capacity: newCapacity)
newBuffer.initialize(from: buffer, count: length)
buffer.deallocate()
return BitPackedNullBuffer(
length: length,
capacity: newCapacity,
ownsMemory: true,
buffer: newBuffer
)
}
}
}
4 changes: 2 additions & 2 deletions Sources/Arrow/Buffer/VariableBufferBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ final class VariableLengthTypeBufferBuilder<T> where T: VariableLength {
length += data.count
}

func doubleCapacity() {
resize(to: capacity * 2)
func increaseCapacity(to newCapacity: Int) {
resize(to: newCapacity)
}

private func resize(to newCapacity: Int) {
Expand Down
Loading