Highly-opinionated (ex-bullshit-free) MTPROTO proxy for Telegram. If you use v1.0 or upgrade broke you proxy, please read the chapter Version 2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. package benchmarks
  2. import (
  3. "bufio"
  4. "bytes"
  5. "crypto/rand"
  6. "encoding/base64"
  7. "fmt"
  8. "runtime"
  9. "testing"
  10. "time"
  11. "unsafe"
  12. )
  13. // =========================================================================
  14. // 1. TLS connPayload: bufio.NewReaderSize(conn, 4096) + bytes.Buffer.Grow(4096)
  15. // =========================================================================
  16. // connPayload mirrors tls/conn.go's connPayload struct.
  17. type connPayload struct {
  18. readBuf bytes.Buffer
  19. connBuffered *bufio.Reader
  20. read bool
  21. write bool
  22. }
  23. func newConnPayload() *connPayload {
  24. p := &connPayload{
  25. connBuffered: bufio.NewReaderSize(nil, 4096),
  26. read: true,
  27. write: true,
  28. }
  29. p.readBuf.Grow(4096)
  30. return p
  31. }
  32. func BenchmarkTLSConnPayload(b *testing.B) {
  33. b.ReportAllocs()
  34. for i := 0; i < b.N; i++ {
  35. _ = newConnPayload()
  36. }
  37. }
  38. func TestTLSConnPayloadHeapCost(t *testing.T) {
  39. const N = 1000
  40. runtime.GC()
  41. var m1, m2 runtime.MemStats
  42. runtime.ReadMemStats(&m1)
  43. payloads := make([]*connPayload, N)
  44. for i := 0; i < N; i++ {
  45. payloads[i] = newConnPayload()
  46. }
  47. runtime.ReadMemStats(&m2)
  48. totalBytes := m2.TotalAlloc - m1.TotalAlloc
  49. perConn := totalBytes / N
  50. fmt.Printf("\n=== TLS connPayload heap cost ===\n")
  51. fmt.Printf(" Struct size (shallow): %d bytes\n", unsafe.Sizeof(connPayload{}))
  52. fmt.Printf(" bufio.Reader size: %d bytes (struct) + 4096 (buf)\n", unsafe.Sizeof(bufio.Reader{}))
  53. fmt.Printf(" Total alloc for %d conns: %d bytes (%.1f KB)\n", N, totalBytes, float64(totalBytes)/1024)
  54. fmt.Printf(" Per connection: %d bytes (%.1f KB)\n", perConn, float64(perConn)/1024)
  55. fmt.Printf(" At 1000 conns: %.1f MB\n", float64(perConn)*1000/1024/1024)
  56. fmt.Printf(" At 2000 conns: %.1f MB\n", float64(perConn)*2000/1024/1024)
  57. // Keep alive to prevent GC
  58. runtime.KeepAlive(payloads)
  59. }
  60. // =========================================================================
  61. // 2. EventTraffic allocations
  62. // =========================================================================
  63. // eventBase mirrors mtglib/events.go
  64. type eventBase struct {
  65. streamID string
  66. timestamp time.Time
  67. }
  68. // EventTraffic mirrors mtglib/events.go
  69. type EventTraffic struct {
  70. eventBase
  71. Traffic uint
  72. IsRead bool
  73. }
  74. func NewEventTraffic(streamID string, traffic uint, isRead bool) EventTraffic {
  75. return EventTraffic{
  76. eventBase: eventBase{
  77. timestamp: time.Now(),
  78. streamID: streamID,
  79. },
  80. Traffic: traffic,
  81. IsRead: isRead,
  82. }
  83. }
  84. func BenchmarkEventTraffic(b *testing.B) {
  85. streamID := "dGVzdC1zdHJlYW0taWQ"
  86. b.ReportAllocs()
  87. for i := 0; i < b.N; i++ {
  88. _ = NewEventTraffic(streamID, 1024, true)
  89. }
  90. }
  91. // BenchmarkEventTrafficInterface tests if passing EventTraffic through an
  92. // interface causes heap escape.
  93. func BenchmarkEventTrafficInterface(b *testing.B) {
  94. streamID := "dGVzdC1zdHJlYW0taWQ"
  95. b.ReportAllocs()
  96. var sink interface{}
  97. for i := 0; i < b.N; i++ {
  98. sink = NewEventTraffic(streamID, 1024, true)
  99. }
  100. runtime.KeepAlive(sink)
  101. }
  102. func TestEventTrafficAllocRate(t *testing.T) {
  103. streamID := "dGVzdC1zdHJlYW0taWQ"
  104. const iterations = 100000
  105. runtime.GC()
  106. var m1, m2 runtime.MemStats
  107. runtime.ReadMemStats(&m1)
  108. var sink interface{}
  109. for i := 0; i < iterations; i++ {
  110. // Simulate what connTraffic.Read does: create event and pass to Send
  111. sink = NewEventTraffic(streamID, 1024, true)
  112. }
  113. runtime.ReadMemStats(&m2)
  114. totalBytes := m2.TotalAlloc - m1.TotalAlloc
  115. totalAllocs := m2.Mallocs - m1.Mallocs
  116. fmt.Printf("\n=== EventTraffic allocation rate ===\n")
  117. fmt.Printf(" Struct size: %d bytes\n", unsafe.Sizeof(EventTraffic{}))
  118. fmt.Printf(" eventBase size: %d bytes\n", unsafe.Sizeof(eventBase{}))
  119. fmt.Printf(" Total alloc for %d events: %d bytes (%.1f KB)\n", iterations, totalBytes, float64(totalBytes)/1024)
  120. fmt.Printf(" Per event: %d bytes\n", totalBytes/iterations)
  121. fmt.Printf(" Heap allocs: %d (%.2f per event)\n", totalAllocs, float64(totalAllocs)/float64(iterations))
  122. fmt.Printf(" NOTE: Each Read+Write on a connection creates 2 events.\n")
  123. fmt.Printf(" At 1000 conns * 100 ops/s: %.1f MB/s event alloc\n",
  124. float64(totalBytes)/float64(iterations)*1000*100*2/1024/1024)
  125. fmt.Printf(" At 2000 conns * 100 ops/s: %.1f MB/s event alloc\n",
  126. float64(totalBytes)/float64(iterations)*2000*100*2/1024/1024)
  127. runtime.KeepAlive(sink)
  128. }
  129. // =========================================================================
  130. // 3. connRewind buffer (bytes.Buffer for handshake recording)
  131. // =========================================================================
  132. func BenchmarkConnRewindBuffer(b *testing.B) {
  133. b.ReportAllocs()
  134. for i := 0; i < b.N; i++ {
  135. var buf bytes.Buffer
  136. // Simulate TLS ClientHello being recorded. Typical ClientHello
  137. // is 200-600 bytes; we use 512 as a representative size.
  138. data := make([]byte, 512)
  139. buf.Write(data)
  140. _ = buf.Bytes()
  141. }
  142. }
  143. func TestConnRewindBufferCost(t *testing.T) {
  144. // Measure bytes.Buffer overhead for various handshake sizes
  145. sizes := []int{256, 512, 768, 1024, 2048}
  146. fmt.Printf("\n=== connRewind buffer cost ===\n")
  147. fmt.Printf(" bytes.Buffer struct size: %d bytes\n", unsafe.Sizeof(bytes.Buffer{}))
  148. for _, size := range sizes {
  149. const N = 1000
  150. runtime.GC()
  151. var m1, m2 runtime.MemStats
  152. runtime.ReadMemStats(&m1)
  153. bufs := make([]bytes.Buffer, N)
  154. data := make([]byte, size)
  155. for i := 0; i < N; i++ {
  156. bufs[i].Write(data)
  157. }
  158. runtime.ReadMemStats(&m2)
  159. totalBytes := m2.TotalAlloc - m1.TotalAlloc
  160. // Subtract the cost of the data slice and bufs slice themselves
  161. perConn := totalBytes / N
  162. fmt.Printf(" Handshake %4d bytes -> buffer alloc per conn: %d bytes\n", size, perConn)
  163. runtime.KeepAlive(bufs)
  164. }
  165. // Estimate at connection scale with typical 512-byte handshake
  166. const typicalSize = 512
  167. const N = 1000
  168. runtime.GC()
  169. var m1, m2 runtime.MemStats
  170. runtime.ReadMemStats(&m1)
  171. bufs := make([]bytes.Buffer, N)
  172. data := make([]byte, typicalSize)
  173. for i := 0; i < N; i++ {
  174. bufs[i].Write(data)
  175. }
  176. runtime.ReadMemStats(&m2)
  177. perConn := (m2.TotalAlloc - m1.TotalAlloc) / N
  178. fmt.Printf(" At 1000 conns (512B handshake): %.1f MB\n", float64(perConn)*1000/1024/1024)
  179. fmt.Printf(" At 2000 conns (512B handshake): %.1f MB\n", float64(perConn)*2000/1024/1024)
  180. runtime.KeepAlive(bufs)
  181. }
  182. // =========================================================================
  183. // 4. streamID generation: make([]byte, 16) + base64 encoding
  184. // =========================================================================
  185. const ConnectionIDBytesLength = 16
  186. func generateStreamIDHeap() string {
  187. connIDBytes := make([]byte, ConnectionIDBytesLength) // heap alloc
  188. rand.Read(connIDBytes) //nolint: errcheck
  189. return base64.RawURLEncoding.EncodeToString(connIDBytes) // heap alloc
  190. }
  191. func generateStreamIDStack() string {
  192. var connIDBytes [ConnectionIDBytesLength]byte // stack
  193. rand.Read(connIDBytes[:]) //nolint: errcheck
  194. return base64.RawURLEncoding.EncodeToString(connIDBytes[:])
  195. }
  196. func BenchmarkStreamIDHeap(b *testing.B) {
  197. b.ReportAllocs()
  198. for i := 0; i < b.N; i++ {
  199. _ = generateStreamIDHeap()
  200. }
  201. }
  202. func BenchmarkStreamIDStack(b *testing.B) {
  203. b.ReportAllocs()
  204. for i := 0; i < b.N; i++ {
  205. _ = generateStreamIDStack()
  206. }
  207. }
  208. func TestStreamIDAllocCost(t *testing.T) {
  209. const N = 10000
  210. // Heap version (current code)
  211. runtime.GC()
  212. var m1, m2 runtime.MemStats
  213. runtime.ReadMemStats(&m1)
  214. heapIDs := make([]string, N)
  215. for i := 0; i < N; i++ {
  216. heapIDs[i] = generateStreamIDHeap()
  217. }
  218. runtime.ReadMemStats(&m2)
  219. heapTotal := m2.TotalAlloc - m1.TotalAlloc
  220. heapPer := heapTotal / N
  221. // Stack version (proposed)
  222. runtime.GC()
  223. runtime.ReadMemStats(&m1)
  224. stackIDs := make([]string, N)
  225. for i := 0; i < N; i++ {
  226. stackIDs[i] = generateStreamIDStack()
  227. }
  228. runtime.ReadMemStats(&m2)
  229. stackTotal := m2.TotalAlloc - m1.TotalAlloc
  230. stackPer := stackTotal / N
  231. fmt.Printf("\n=== streamID generation cost ===\n")
  232. fmt.Printf(" Heap version (make([]byte,16) + base64):\n")
  233. fmt.Printf(" Per call: %d bytes\n", heapPer)
  234. fmt.Printf(" At 1000 conns: %.1f KB\n", float64(heapPer)*1000/1024)
  235. fmt.Printf(" At 2000 conns: %.1f KB\n", float64(heapPer)*2000/1024)
  236. fmt.Printf(" Stack version (var buf [16]byte + base64):\n")
  237. fmt.Printf(" Per call: %d bytes\n", stackPer)
  238. fmt.Printf(" At 1000 conns: %.1f KB\n", float64(stackPer)*1000/1024)
  239. fmt.Printf(" At 2000 conns: %.1f KB\n", float64(stackPer)*2000/1024)
  240. fmt.Printf(" Savings per call: %d bytes (%.0f%%)\n", heapPer-stackPer,
  241. float64(heapPer-stackPer)/float64(heapPer)*100)
  242. runtime.KeepAlive(heapIDs)
  243. runtime.KeepAlive(stackIDs)
  244. }
  245. // =========================================================================
  246. // Combined summary
  247. // =========================================================================
  248. func TestCombinedSummary(t *testing.T) {
  249. const N = 1000
  250. // 1. TLS connPayload
  251. runtime.GC()
  252. var m1, m2 runtime.MemStats
  253. runtime.ReadMemStats(&m1)
  254. payloads := make([]*connPayload, N)
  255. for i := 0; i < N; i++ {
  256. payloads[i] = newConnPayload()
  257. }
  258. runtime.ReadMemStats(&m2)
  259. tlsPerConn := (m2.TotalAlloc - m1.TotalAlloc) / N
  260. // 2. connRewind (512 byte handshake)
  261. runtime.GC()
  262. runtime.ReadMemStats(&m1)
  263. bufs := make([]bytes.Buffer, N)
  264. data := make([]byte, 512)
  265. for i := 0; i < N; i++ {
  266. bufs[i].Write(data)
  267. }
  268. runtime.ReadMemStats(&m2)
  269. rewindPerConn := (m2.TotalAlloc - m1.TotalAlloc) / N
  270. // 3. streamID (heap)
  271. runtime.GC()
  272. runtime.ReadMemStats(&m1)
  273. ids := make([]string, N)
  274. for i := 0; i < N; i++ {
  275. ids[i] = generateStreamIDHeap()
  276. }
  277. runtime.ReadMemStats(&m2)
  278. streamIDPerConn := (m2.TotalAlloc - m1.TotalAlloc) / N
  279. // 4. EventTraffic per op (interface escape)
  280. runtime.GC()
  281. runtime.ReadMemStats(&m1)
  282. var sink interface{}
  283. for i := 0; i < N; i++ {
  284. sink = NewEventTraffic("test", 1024, true)
  285. }
  286. runtime.ReadMemStats(&m2)
  287. eventPer := (m2.TotalAlloc - m1.TotalAlloc) / N
  288. totalPerConn := tlsPerConn + rewindPerConn + streamIDPerConn
  289. fmt.Printf("\n")
  290. fmt.Printf("╔══════════════════════════════════════════════════════════╗\n")
  291. fmt.Printf("║ PER-CONNECTION ALLOCATION SUMMARY ║\n")
  292. fmt.Printf("╠══════════════════════════════════════════════════════════╣\n")
  293. fmt.Printf("║ Component │ Per Conn │ 1000 │ 2000 ║\n")
  294. fmt.Printf("╠════════════════════════╪═══════════╪══════════╪═════════╣\n")
  295. fmt.Printf("║ TLS connPayload │ %5d B │ %5.1f MB │ %5.1f MB║\n",
  296. tlsPerConn, float64(tlsPerConn)*1000/1024/1024, float64(tlsPerConn)*2000/1024/1024)
  297. fmt.Printf("║ connRewind (512B hs) │ %5d B │ %5.1f MB │ %5.1f MB║\n",
  298. rewindPerConn, float64(rewindPerConn)*1000/1024/1024, float64(rewindPerConn)*2000/1024/1024)
  299. fmt.Printf("║ streamID generation │ %5d B │ %5.1f KB │ %5.1f KB║\n",
  300. streamIDPerConn, float64(streamIDPerConn)*1000/1024, float64(streamIDPerConn)*2000/1024)
  301. fmt.Printf("╠════════════════════════╪═══════════╪══════════╪═════════╣\n")
  302. fmt.Printf("║ TOTAL (one-time/conn) │ %5d B │ %5.1f MB │ %5.1f MB║\n",
  303. totalPerConn, float64(totalPerConn)*1000/1024/1024, float64(totalPerConn)*2000/1024/1024)
  304. fmt.Printf("╠════════════════════════╪═══════════╪══════════╪═════════╣\n")
  305. fmt.Printf("║ EventTraffic (per op) │ %5d B │ ongoing │ ongoing ║\n", eventPer)
  306. fmt.Printf("║ (rate at 100 ops/s) │ │ %5.1f MB/s ║\n",
  307. float64(eventPer)*1000*100*2/1024/1024)
  308. fmt.Printf("╚══════════════════════════════════════════════════════════╝\n")
  309. runtime.KeepAlive(payloads)
  310. runtime.KeepAlive(bufs)
  311. runtime.KeepAlive(ids)
  312. runtime.KeepAlive(sink)
  313. }