Skip to content

Commit 319cf08

Browse files
vkuttypCopilot
andcommitted
perf: apply TDS performance optimisations from C# port
- TDSLogin7: negotiate 32KB packet size (was 4096) — reduces packet count ~8x - sendPacket: coalesce all TDS fragments into single ByteBuffer, one writeAndFlush instead of N channel.write() calls per large request - TDSDecoder.decodeNbcRow: replace readBytes() heap copy with zero-copy readSlice() and getInteger(at:) for null bitmap access Mirrors the same three fixes applied to CosmoSQLClient-Dotnet (commit 04ad1f5) which yielded 1–21% throughput improvement over Microsoft.Data.SqlClient. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 203e0d4 commit 319cf08

3 files changed

Lines changed: 20 additions & 20 deletions

File tree

Sources/CosmoMSSQL/MSSQLConnection.swift

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -751,16 +751,22 @@ public final class MSSQLConnection: SQLDatabase, @unchecked Sendable {
751751
}
752752

753753
private func sendPacket(type: TDSPacketType, payload: inout ByteBuffer) {
754-
// Encode TDS packet(s) and send each packet individually.
755-
// SQL Server can reject multi-packet TDS messages sent in a single write.
754+
// Coalesce all TDS packets into a single ByteBuffer and flush in one syscall.
756755
let payloadSize = payload.readableBytes
757-
let maxBody = 4096 - TDSPacketHeader.size // 4088
756+
let maxBody = 32768 - TDSPacketHeader.size // 32760
757+
758+
// Pre-compute total wire size so we allocate exactly once.
759+
let fullPackets = payloadSize / maxBody
760+
let remainder = payloadSize % maxBody
761+
let numPackets = fullPackets + (remainder > 0 ? 1 : 0)
762+
let wireSize = payloadSize + numPackets * TDSPacketHeader.size
763+
var wire = channel.allocator.buffer(capacity: wireSize)
758764

759765
var offset = 0
760766
var packetID: UInt8 = 1
761767
while offset < payloadSize {
762768
let chunkLen = min(maxBody, payloadSize - offset)
763-
let isLast = (offset + chunkLen) >= payloadSize
769+
let isLast = (offset + chunkLen) >= payloadSize
764770
let totalLen = UInt16(chunkLen + TDSPacketHeader.size)
765771

766772
let header = TDSPacketHeader(
@@ -769,21 +775,14 @@ public final class MSSQLConnection: SQLDatabase, @unchecked Sendable {
769775
length: totalLen,
770776
packetID: packetID
771777
)
772-
var pkt = channel.allocator.buffer(capacity: Int(totalLen))
773-
header.encode(into: &pkt)
774-
pkt.writeBytes(payload.getBytes(at: payload.readerIndex + offset, length: chunkLen)!)
775-
776-
if isLast {
777-
// Last chunk: writeAndFlush flushes all buffered writes in one TCP segment.
778-
// Fire-and-forget — the server can't reply until it receives this, so reads
779-
// will naturally follow the write through the event loop's FIFO ordering.
780-
channel.writeAndFlush(pkt, promise: nil)
781-
} else {
782-
channel.write(pkt, promise: nil)
783-
}
778+
header.encode(into: &wire)
779+
wire.writeImmutableBuffer(payload.getSlice(at: payload.readerIndex + offset, length: chunkLen)!)
780+
784781
packetID = packetID == 255 ? 1 : packetID + 1
785782
offset += chunkLen
786783
}
784+
// Single writeAndFlush → one TCP segment for all packets.
785+
channel.writeAndFlush(wire, promise: nil)
787786
}
788787

789788
/// Receive one complete TDS message via the async stream bridge handler.

Sources/CosmoMSSQL/TDS/TDSDecoder.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,15 +158,16 @@ struct TDSTokenDecoder {
158158

159159
private mutating func decodeNbcRow(_ buf: inout ByteBuffer) throws {
160160
let bitmapLen = (columns.count + 7) / 8
161-
guard let bitmapBytes = buf.readBytes(length: bitmapLen) else {
161+
// Use a zero-copy slice instead of readBytes() to avoid heap-allocating a [UInt8].
162+
guard let bitmapSlice = buf.readSlice(length: bitmapLen) else {
162163
throw TDSError.incomplete
163164
}
164165

165166
var values: [SQLValue] = []
166167
for (i, col) in columns.enumerated() {
167-
let byteIdx = i / 8
168+
let byteIdx = bitmapSlice.readerIndex + i / 8
168169
let bitIdx = i % 8
169-
let isNull = (bitmapBytes[byteIdx] >> bitIdx) & 0x01 == 1
170+
let isNull = ((bitmapSlice.getInteger(at: byteIdx, as: UInt8.self) ?? 0) >> bitIdx) & 0x01 == 1
170171
if isNull {
171172
values.append(.null)
172173
} else {

Sources/CosmoMSSQL/TDS/TDSLogin7.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Foundation
99
struct TDSLogin7 {
1010
// Fixed header fields
1111
var tdsVersion: UInt32 = 0x74000004 // TDS 7.4
12-
var packetSize: UInt32 = 4096
12+
var packetSize: UInt32 = 32768
1313
var clientProgVer: UInt32 = 0x07000000
1414
var clientPID: UInt32 = UInt32(ProcessInfo.processInfo.processIdentifier)
1515
var connectionID: UInt32 = 0

0 commit comments

Comments
 (0)