Skip to content

Commit d3c3fff

Browse files
authored
Merge pull request #3 from scuml/optimizations
Optimizations
2 parents edf91f0 + 19207a7 commit d3c3fff

File tree

1 file changed

+44
-21
lines changed

1 file changed

+44
-21
lines changed

Sources/SwiftRedis/RedisResp.swift

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ class RedisResp {
115115
var response: RedisResponse
116116

117117
var (matched, offset) = try compare(&buffer, at: from, with: RedisResp.plus)
118+
118119
if matched {
119120
(response, offset) = try parseSimpleString(&buffer, offset: offset)
120121
} else {
@@ -143,13 +144,24 @@ class RedisResp {
143144
return (response, offset)
144145
}
145146

147+
/*
148+
Arrays usually need to parse bulk strings. And the longer the array, the more optimization is needed. So to save processing
149+
time, check the $ first. (https://redis.io/topics/protocol)
150+
*/
151+
private func parseByPrefixArrayOptimization(_ buffer: inout Data, from: Int) throws -> (RedisResponse, Int) {
152+
let (matched, offset) = try compare(&buffer, at: from, with: RedisResp.dollar)
153+
if matched {
154+
return try parseBulkString(&buffer, offset: offset)
155+
}
156+
return try parseByPrefix(&buffer, from: offset)
157+
}
146158
private func parseArray(_ buffer: inout Data, offset: Int) throws -> (RedisResponse, Int) {
147159
var (arrayLength, newOffset) = try parseIntegerValue(&buffer, offset: offset)
148160
var responses = [RedisResponse]()
149161
var response: RedisResponse
150162
if arrayLength >= 0 {
151163
for _ in 0 ..< Int(arrayLength) {
152-
(response, newOffset) = try parseByPrefix(&buffer, from: newOffset)
164+
(response, newOffset) = try parseByPrefixArrayOptimization(&buffer, from: newOffset)
153165
responses.append(response)
154166
}
155167
return (RedisResponse.Array(responses), newOffset)
@@ -169,18 +181,16 @@ class RedisResp {
169181
throw RedisRespError(code: .EOF)
170182
}
171183
}
172-
let data = buffer.subdata(in: newOffset..<newOffset+strLen)
173-
let redisString = RedisString(data)
184+
let redisString = RedisString(buffer[newOffset..<newOffset+strLen])
174185
return (RedisResponse.StringValue(redisString), totalLength)
175186
} else {
176187
return (RedisResponse.Nil, newOffset)
177188
}
178189
}
179190

180191
private func parseError(_ buffer: inout Data, offset: Int) throws -> (RedisResponse, Int) {
181-
let eos = try find(&buffer, from: offset, data: RedisResp.crLf)
182-
let data = buffer.subdata(in: offset..<eos)
183-
let optStr = String(data: data as Data, encoding: String.Encoding.utf8)
192+
let eos = try findCrlf(&buffer, from: offset)
193+
let optStr = String(data: buffer[offset..<eos], encoding: String.Encoding.utf8)
184194
let length = eos+RedisResp.crLf.count
185195
guard let str = optStr else {
186196
throw RedisRespError(code: .notUTF8)
@@ -194,9 +204,8 @@ class RedisResp {
194204
}
195205

196206
private func parseSimpleString(_ buffer: inout Data, offset: Int) throws -> (RedisResponse, Int) {
197-
let eos = try find(&buffer, from: offset, data: RedisResp.crLf)
198-
let data = buffer.subdata(in: offset..<eos)
199-
let optStr = String(data: data, encoding: String.Encoding.utf8)
207+
let eos = try findCrlf(&buffer, from: offset)
208+
let optStr = String(data: buffer[offset..<eos], encoding: String.Encoding.utf8)
200209
let length = eos+RedisResp.crLf.count
201210
guard let str = optStr else {
202211
throw RedisRespError(code: .notUTF8)
@@ -207,30 +216,28 @@ class RedisResp {
207216
// Mark: Parser helper functions
208217

209218
private func compare(_ buffer: inout Data, at offset: Int, with: Data) throws -> (Bool, Int) {
210-
while offset+with.count >= buffer.count {
219+
// use offset+1 instead of offset+with.count as with.count is always == 1
220+
while offset+1 >= buffer.count {
211221
let length = try socket?.read(into: &buffer)
212222
if length == 0 {
213223
throw RedisRespError(code: .EOF)
214224
}
215225
}
216226

217-
let range = buffer.range(of: with, options: [], in: offset..<offset+with.count)
218-
if range != nil {
219-
return (true, offset+with.count)
227+
if (buffer[offset..<offset+1] == with){
228+
return (true, offset+1)
220229
} else {
221230
return (false, offset)
222231
}
223232
}
224233

225234
private func find(_ buffer: inout Data, from: Int, data: Data) throws -> Int {
226235
var offset = from
227-
var notFound = true
228-
229-
while notFound {
236+
while true {
230237
let range = buffer.range(of: data, options: [], in: offset..<buffer.count)
231238
if range != nil {
232-
offset = (range?.lowerBound)!
233-
notFound = false
239+
return range!.lowerBound
240+
234241
} else {
235242
let length = try socket?.read(into: &buffer)
236243
if length == 0 {
@@ -240,11 +247,27 @@ class RedisResp {
240247
}
241248
return offset
242249
}
250+
private func findCrlf(_ buffer: inout Data, from: Int) throws -> Int {
251+
/* 5X faster than using find() above. range() is expensive */
252+
var i = from
253+
while true {
254+
while i <= buffer.count {
255+
if buffer[i] == 13 && buffer[i+1] == 10{
256+
return i
257+
}
258+
i+=1
259+
}
260+
let length = try socket?.read(into: &buffer)
261+
if length == 0 {
262+
throw RedisRespError(code: .EOF)
263+
}
264+
}
265+
return from
266+
}
243267

244268
private func parseIntegerValue(_ buffer: inout Data, offset: Int) throws -> (Int64, Int) {
245-
let eos = try find(&buffer, from: offset, data: RedisResp.crLf)
246-
let data = buffer.subdata(in: offset..<eos)
247-
let optStr = String(data: data as Data, encoding: String.Encoding.utf8)
269+
let eos = try findCrlf(&buffer, from: offset)
270+
let optStr = String(data: buffer[offset..<eos], encoding: String.Encoding.utf8)
248271
let length = eos+RedisResp.crLf.count
249272
guard let str = optStr else {
250273
throw RedisRespError(code: .notUTF8)

0 commit comments

Comments
 (0)