@@ -115,6 +115,7 @@ class RedisResp {
115
115
var response : RedisResponse
116
116
117
117
var ( matched, offset) = try compare ( & buffer, at: from, with: RedisResp . plus)
118
+
118
119
if matched {
119
120
( response, offset) = try parseSimpleString ( & buffer, offset: offset)
120
121
} else {
@@ -143,13 +144,24 @@ class RedisResp {
143
144
return ( response, offset)
144
145
}
145
146
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
+ }
146
158
private func parseArray( _ buffer: inout Data , offset: Int ) throws -> ( RedisResponse , Int ) {
147
159
var ( arrayLength, newOffset) = try parseIntegerValue ( & buffer, offset: offset)
148
160
var responses = [ RedisResponse] ( )
149
161
var response : RedisResponse
150
162
if arrayLength >= 0 {
151
163
for _ in 0 ..< Int ( arrayLength) {
152
- ( response, newOffset) = try parseByPrefix ( & buffer, from: newOffset)
164
+ ( response, newOffset) = try parseByPrefixArrayOptimization ( & buffer, from: newOffset)
153
165
responses. append ( response)
154
166
}
155
167
return ( RedisResponse . Array ( responses) , newOffset)
@@ -169,18 +181,16 @@ class RedisResp {
169
181
throw RedisRespError ( code: . EOF)
170
182
}
171
183
}
172
- let data = buffer. subdata ( in: newOffset..< newOffset+ strLen)
173
- let redisString = RedisString ( data)
184
+ let redisString = RedisString ( buffer [ newOffset..< newOffset+ strLen] )
174
185
return ( RedisResponse . StringValue ( redisString) , totalLength)
175
186
} else {
176
187
return ( RedisResponse . Nil, newOffset)
177
188
}
178
189
}
179
190
180
191
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)
184
194
let length = eos+ RedisResp. crLf. count
185
195
guard let str = optStr else {
186
196
throw RedisRespError ( code: . notUTF8)
@@ -194,9 +204,8 @@ class RedisResp {
194
204
}
195
205
196
206
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)
200
209
let length = eos+ RedisResp. crLf. count
201
210
guard let str = optStr else {
202
211
throw RedisRespError ( code: . notUTF8)
@@ -207,30 +216,28 @@ class RedisResp {
207
216
// Mark: Parser helper functions
208
217
209
218
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 {
211
221
let length = try socket? . read ( into: & buffer)
212
222
if length == 0 {
213
223
throw RedisRespError ( code: . EOF)
214
224
}
215
225
}
216
226
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 )
220
229
} else {
221
230
return ( false , offset)
222
231
}
223
232
}
224
233
225
234
private func find( _ buffer: inout Data , from: Int , data: Data ) throws -> Int {
226
235
var offset = from
227
- var notFound = true
228
-
229
- while notFound {
236
+ while true {
230
237
let range = buffer. range ( of: data, options: [ ] , in: offset..< buffer. count)
231
238
if range != nil {
232
- offset = ( range? . lowerBound) !
233
- notFound = false
239
+ return range! . lowerBound
240
+
234
241
} else {
235
242
let length = try socket? . read ( into: & buffer)
236
243
if length == 0 {
@@ -240,11 +247,27 @@ class RedisResp {
240
247
}
241
248
return offset
242
249
}
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
+ }
243
267
244
268
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)
248
271
let length = eos+ RedisResp. crLf. count
249
272
guard let str = optStr else {
250
273
throw RedisRespError ( code: . notUTF8)
0 commit comments