| Fabien Sanglard | 2cbf591 | 2024-10-07 17:18:35 -0700 | [diff] [blame] | 1 | # Delayed ACK |
| 2 | |
| 3 | Historically, ADB transport protocol transfer speed was affected by two factors. |
| 4 | |
| 5 | 1. Each `A_WRTE` apacket was CRCed upon write and the CRC was checked upon read on the other end. |
| 6 | 2. There could be only one `A_WRTE` apacket in-flight on an asocket. A local asocket |
| 7 | would not schedule more data to be sent out until it had received an `A_OKAY` apacket response from |
| 8 | its peer. |
| 9 | |
| 10 | The first issue was solved in [aosp/568123](https://android-review.googlesource.com/q/568123). |
| 11 | In that CL, the protocol was updated to remove the requirement for CRC generation and verification. |
| 12 | This does not affect the reliability of a transport since both USB and TCP have packet checksums of their own. |
| 13 | |
| 14 | The second issue is solved by "delayed ACK" ([aosp/1953877](https://android-review.googlesource.com/q/1953877)), |
| Fabien Sanglard | 2d3e62c | 2025-02-06 13:45:48 -0800 | [diff] [blame] | 15 | an experimental feature controlled by the environment variable `ADB_BURST_MODE`. |
| Fabien Sanglard | 2cbf591 | 2024-10-07 17:18:35 -0700 | [diff] [blame] | 16 | |
| 17 | # How delayed ACK works |
| 18 | |
| 19 | The idea is to introduce the concept of a per-asocket "available send bytes" (ASB) integer. |
| 20 | This integer represent how many bytes we are willing to send without having received any |
| 21 | `A_OKAY` for them. |
| 22 | |
| 23 | While the ASB is positive, the asocket does not wait for an `A_OKAY` before sending |
| 24 | more `A_WRTE` apackets. A remote asocket can be written to up until the ASB is exhausted. |
| 25 | |
| 26 | The ASB capability is first negotiated on `A_OPEN`/`A_OKAY` exchange. After |
| 27 | that, the ASB is maintained via decrement upon `A_WRTE` and increment |
| 28 | upon `A_OKAY`. |
| 29 | |
| 30 | This approach allows to "burst" `A_WRTE` packet but also "burst" `A_OKAY` packets |
| 31 | to allow several `A_WRTE` packets to be in-flight on an asocket. This greatly |
| 32 | increases data transfer throughput. |
| 33 | |
| 34 | # Implementation |
| 35 | |
| 36 | ## Packet update |
| 37 | 1. `A_OPEN` unused field (`arg1`) is repurposed to declare the wish to use delayed ACK features. |
| 38 | If not supported, the receiving end of the `A_OPEN` will `A_CLSE` the connection. |
| 39 | 2. `A_OKAY` now has a payload (a int32_t) which acknowledge how much payload was |
| 40 | received in the last received `A_WRTE` apacket. |
| 41 | |
| 42 | ## Trace |
| 43 | |
| 44 | Here are two traces showing the timing of three A_WRTE. |
| 45 | |
| 46 | ### Before |
| 47 | ``` |
| 48 | Host > A_OPEN > Device |
| 49 | Host > A_WRTE > Device |
| 50 | The LS removes itself from the fdevent EPOLLIN and nothing is sent. |
| 51 | Host < A_OKAY < Device |
| 52 | The LS requests fdevent EPOLLIN for its fd to start reading and send more A_WRTE. |
| 53 | Host > A_WRTE > Device |
| 54 | The LS removes itself from the fdevent EPOLLIN and nothing is sent. |
| 55 | Host < A_OKAY < Device |
| 56 | The LS requests fdevent EPOLLIN for its fd to start reading and send more A_WRTE. |
| 57 | Host > A_WRTE > Device |
| 58 | The LS removes itself from the fdevent EPOLLIN and nothing is sent. |
| 59 | Host < A_OKAY < Device |
| 60 | The LS requests fdevent EPOLLIN for its fd to start reading and send more A_WRTE. |
| 61 | ``` |
| 62 | |
| 63 | |
| 64 | ## After |
| 65 | |
| 66 | With ASB, see how `A_WRTE` and `A_OKAY` are burst instead of being paired. |
| 67 | |
| 68 | ``` |
| 69 | Host(ASB=0) > A_OPEN(arg1=1MiB) > Device |
| 70 | Host(ASB=X) < A_OKAY(<ASB=X>) < Device |
| 71 | Host<ASB=X-a) > A_WRTE(payload size=a) > Device |
| 72 | Host<ASB=Y-a-b) > A_WRTE(payload size=b) > Device |
| 73 | Host<ASB=Z-a-b-c) > A_WRTE(payload size=c) > Device |
| 74 | ASB is < 0. The LS removes itself from the fdevent EPOLLIN and nothing is sent. |
| 75 | ... |
| 76 | Host(ASB=X-b-c) < A_OKAY(<a>) < Device |
| 77 | ASB is > 0. The LS requests fdevent EPOLLIN for its fd to start reading and send more A_WRTE. |
| 78 | ... |
| 79 | Host(ASB=X-c) < A_OKAY(<b>) < Device |
| 80 | Host(ASB=X) < A_OKAY(<c>) < Device |
| 81 | ``` |
| 82 | |
| Fabien Sanglard | bfc7264 | 2025-02-06 14:23:51 -0800 | [diff] [blame] | 83 | # Results |
| Fabien Sanglard | 2cbf591 | 2024-10-07 17:18:35 -0700 | [diff] [blame] | 84 | |
| Fabien Sanglard | bfc7264 | 2025-02-06 14:23:51 -0800 | [diff] [blame] | 85 | Initial testing show that Burst Mode is nearly 70% faster at pushing files to a device over a USB-3 cable. |
| 86 | |
| 87 | ## Before |
| 88 | ``` |
| 89 | $ adb kill-server && unset ADB_BURST_MODE && adb start-server |
| 90 | $ adb push -Z ~/Desktop/10G1 /data/local/tmp |
| 91 | /usr/local/google/home/sanglardf/Desktop/10G1: 1 file pushed, 0 skipped. 202.0 MB/s (10737418240 bytes in 50.701s) |
| 92 | $ adb push -Z ~/Desktop/10G1 /data/local/tmp |
| 93 | /usr/local/google/home/sanglardf/Desktop/10G1: 1 file pushed, 0 skipped. 205.9 MB/s (10737418240 bytes in 49.724s) |
| 94 | $ adb push -Z ~/Desktop/10G1 /data/local/tmp |
| 95 | /usr/local/google/home/sanglardf/Desktop/10G1: 1 file pushed, 0 skipped. 197.6 MB/s (10737418240 bytes in 51.828s) |
| 96 | ``` |
| 97 | |
| 98 | ## After |
| 99 | |
| 100 | ``` |
| 101 | $ adb kill-server && export ADB_BURST_MODE=1 && adb start-server |
| 102 | $ adb push -Z ~/Desktop/10G1 /data/local/tmp |
| 103 | /usr/local/google/home/sanglardf/Desktop/10G1: 1 file pushed, 0 skipped. 337.2 MB/s (10737418240 bytes in 30.365s) |
| 104 | $ adb push -Z ~/Desktop/10G1 /data/local/tmp |
| 105 | /usr/local/google/home/sanglardf/Desktop/10G1: 1 file pushed, 0 skipped. 342.0 MB/s (10737418240 bytes in 29.945s) |
| 106 | $ adb push -Z ~/Desktop/10G1 /data/local/tmp |
| 107 | /usr/local/google/home/sanglardf/Desktop/10G1: 1 file pushed, 0 skipped. 341.3 MB/s (10737418240 bytes in 30.000s) |
| 108 | ``` |