Skip to content

Commit 319ab07

Browse files
committed
⚡ Simplify and speed up THREAD response parsing
Benchmarks: ``` Calculating ------------------------------------- v0.4.5-8-g781b44d 0.4.5 invalid_thread_empty_response 114.984k 100.762k i/s - 343.030k times in 2.983283s 3.404345s thread_rfc5256_example1 6.313k 5.228k i/s - 19.183k times in 3.038420s 3.669553s thread_rfc5256_example2 6.215k 5.090k i/s - 19.153k times in 3.081493s 3.763122s thread_rfc5256_example3 112.226k 113.239k i/s - 349.323k times in 3.112662s 3.084841s thread_rfc5256_example4 32.746k 26.211k i/s - 97.246k times in 2.969691s 3.710168s thread_rfc5256_example5 60.780k 48.940k i/s - 180.990k times in 2.977799s 3.698196s Comparison: invalid_thread_empty_response v0.4.5-8-g781b44d: 114984.1 i/s 0.4.5: 100762.4 i/s - 1.14x slower thread_rfc5256_example1 v0.4.5-8-g781b44d: 6313.5 i/s 0.4.5: 5227.6 i/s - 1.21x slower thread_rfc5256_example2 v0.4.5-8-g781b44d: 6215.5 i/s 0.4.5: 5089.7 i/s - 1.22x slower thread_rfc5256_example3 0.4.5: 113238.6 i/s v0.4.5-8-g781b44d: 112226.4 i/s - 1.01x slower thread_rfc5256_example4 v0.4.5-8-g781b44d: 32746.2 i/s 0.4.5: 26210.7 i/s - 1.25x slower thread_rfc5256_example5 v0.4.5-8-g781b44d: 60779.8 i/s 0.4.5: 48940.1 i/s - 1.24x slower ``` Note that the only example that was slower (by a statistically insignificant amount), is merely an empty response. All other examples were significantly faster.
1 parent 8d06a25 commit 319ab07

File tree

2 files changed

+42
-53
lines changed

2 files changed

+42
-53
lines changed

lib/net/imap/response_parser.rb

Lines changed: 40 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,66 +1483,55 @@ def mailbox_data__search
14831483
end
14841484
alias sort_data mailbox_data__search
14851485

1486+
# RFC5256: THREAD
1487+
# thread-data = "THREAD" [SP 1*thread-list]
14861488
def thread_data
1487-
token = match(T_ATOM)
1488-
name = token.value.upcase
1489-
token = lookahead
1490-
1491-
if token.symbol == T_SPACE
1492-
threads = []
1493-
1494-
while true
1495-
shift_token
1496-
token = lookahead
1497-
1498-
case token.symbol
1499-
when T_LPAR
1500-
threads << thread_branch(token)
1501-
when T_CRLF
1502-
break
1503-
end
1504-
end
1505-
else
1506-
# no member
1507-
threads = []
1489+
name = label("THREAD")
1490+
threads = []
1491+
if SP?
1492+
threads << thread_list while lookahead_thread_list?
15081493
end
1509-
1510-
return UntaggedResponse.new(name, threads, @str)
1494+
UntaggedResponse.new(name, threads, @str)
15111495
end
15121496

1513-
def thread_branch(token)
1514-
rootmember = nil
1515-
lastmember = nil
1497+
alias lookahead_thread_list? lookahead_lpar?
1498+
alias lookahead_thread_nested? lookahead_thread_list?
15161499

1517-
while true
1518-
shift_token # ignore first T_LPAR
1519-
token = lookahead
1520-
1521-
case token.symbol
1522-
when T_NUMBER
1523-
# new member
1524-
newmember = ThreadMember.new(number, [])
1525-
if rootmember.nil?
1526-
rootmember = newmember
1527-
else
1528-
lastmember.children << newmember
1529-
end
1530-
lastmember = newmember
1531-
when T_SPACE
1532-
# do nothing
1533-
when T_LPAR
1534-
if rootmember.nil?
1535-
# dummy member
1536-
lastmember = rootmember = ThreadMember.new(nil, [])
1537-
end
1500+
# RFC5256: THREAD
1501+
# thread-list = "(" (thread-members / thread-nested) ")"
1502+
def thread_list
1503+
lpar
1504+
thread = if lookahead_thread_nested?
1505+
ThreadMember.new(nil, thread_nested)
1506+
else
1507+
thread_members
1508+
end
1509+
rpar
1510+
thread
1511+
end
15381512

1539-
lastmember.children << thread_branch(token)
1540-
when T_RPAR
1541-
break
1513+
# RFC5256: THREAD
1514+
# thread-members = nz-number *(SP nz-number) [SP thread-nested]
1515+
def thread_members
1516+
members = []
1517+
members << nz_number # thread root
1518+
while SP?
1519+
case lookahead!(T_NUMBER, T_LPAR).symbol
1520+
when T_NUMBER then members << nz_number
1521+
else nested = thread_nested; break
15421522
end
15431523
end
1524+
members.reverse.inject(nested || []) {|subthreads, number|
1525+
[ThreadMember.new(number, subthreads)]
1526+
}.first
1527+
end
15441528

1545-
return rootmember
1529+
# RFC5256: THREAD
1530+
# thread-nested = 2*thread-list
1531+
def thread_nested
1532+
nested = [thread_list, thread_list]
1533+
while lookahead_thread_list? do nested << thread_list end
1534+
nested
15461535
end
15471536

15481537
# mailbox-data =/ "STATUS" SP mailbox SP "(" [status-att-list] ")"

test/net/imap/fixtures/response_parser/thread_responses.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
:tests:
33
thread_rfc5256_example1:
4-
:response: "* THREAD (166)(167)(168)(169)(172)(170)(171)(173)(174 (175)(176)(178)(181)(180))(179)(177(183)(182)(188)(184)(185)(186)(187)(189))(190)(191)(192)(193)(194
4+
:response: "* THREAD (166)(167)(168)(169)(172)(170)(171)(173)(174 (175)(176)(178)(181)(180))(179)(177 (183)(182)(188)(184)(185)(186)(187)(189))(190)(191)(192)(193)(194
55
195)(196 (197)(198))(199)(200 202)(201)(203)(204)(205)(206 207)(208)\r\n"
66
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
77
name: THREAD
@@ -135,7 +135,7 @@
135135
- !ruby/struct:Net::IMAP::ThreadMember
136136
seqno: 208
137137
children: []
138-
raw_data: "* THREAD (166)(167)(168)(169)(172)(170)(171)(173)(174 (175)(176)(178)(181)(180))(179)(177(183)(182)(188)(184)(185)(186)(187)(189))(190)(191)(192)(193)(194
138+
raw_data: "* THREAD (166)(167)(168)(169)(172)(170)(171)(173)(174 (175)(176)(178)(181)(180))(179)(177 (183)(182)(188)(184)(185)(186)(187)(189))(190)(191)(192)(193)(194
139139
195)(196 (197)(198))(199)(200 202)(201)(203)(204)(205)(206 207)(208)\r\n"
140140
thread_rfc5256_example2:
141141
:response: "* THREAD (166)(167)(168)(169)(172)((170)(179))(171)(173)((174)(175)(176)(178)(181)(180))((177)(183)(182)(188

0 commit comments

Comments
 (0)