bench_test.go 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. // +build go1.1
  2. package pq
  3. import (
  4. "bufio"
  5. "bytes"
  6. "database/sql"
  7. "database/sql/driver"
  8. "github.com/lib/pq/oid"
  9. "io"
  10. "math/rand"
  11. "net"
  12. "runtime"
  13. "strconv"
  14. "strings"
  15. "sync"
  16. "testing"
  17. "time"
  18. )
  19. var (
  20. selectStringQuery = "SELECT '" + strings.Repeat("0123456789", 10) + "'"
  21. selectSeriesQuery = "SELECT generate_series(1, 100)"
  22. )
  23. func BenchmarkSelectString(b *testing.B) {
  24. var result string
  25. benchQuery(b, selectStringQuery, &result)
  26. }
  27. func BenchmarkSelectSeries(b *testing.B) {
  28. var result int
  29. benchQuery(b, selectSeriesQuery, &result)
  30. }
  31. func benchQuery(b *testing.B, query string, result interface{}) {
  32. b.StopTimer()
  33. db := openTestConn(b)
  34. defer db.Close()
  35. b.StartTimer()
  36. for i := 0; i < b.N; i++ {
  37. benchQueryLoop(b, db, query, result)
  38. }
  39. }
  40. func benchQueryLoop(b *testing.B, db *sql.DB, query string, result interface{}) {
  41. rows, err := db.Query(query)
  42. if err != nil {
  43. b.Fatal(err)
  44. }
  45. defer rows.Close()
  46. for rows.Next() {
  47. err = rows.Scan(result)
  48. if err != nil {
  49. b.Fatal("failed to scan", err)
  50. }
  51. }
  52. }
  53. // reading from circularConn yields content[:prefixLen] once, followed by
  54. // content[prefixLen:] over and over again. It never returns EOF.
  55. type circularConn struct {
  56. content string
  57. prefixLen int
  58. pos int
  59. net.Conn // for all other net.Conn methods that will never be called
  60. }
  61. func (r *circularConn) Read(b []byte) (n int, err error) {
  62. n = copy(b, r.content[r.pos:])
  63. r.pos += n
  64. if r.pos >= len(r.content) {
  65. r.pos = r.prefixLen
  66. }
  67. return
  68. }
  69. func (r *circularConn) Write(b []byte) (n int, err error) { return len(b), nil }
  70. func (r *circularConn) Close() error { return nil }
  71. func fakeConn(content string, prefixLen int) *conn {
  72. c := &circularConn{content: content, prefixLen: prefixLen}
  73. return &conn{buf: bufio.NewReader(c), c: c}
  74. }
  75. // This benchmark is meant to be the same as BenchmarkSelectString, but takes
  76. // out some of the factors this package can't control. The numbers are less noisy,
  77. // but also the costs of network communication aren't accurately represented.
  78. func BenchmarkMockSelectString(b *testing.B) {
  79. b.StopTimer()
  80. // taken from a recorded run of BenchmarkSelectString
  81. // See: http://www.postgresql.org/docs/current/static/protocol-message-formats.html
  82. const response = "1\x00\x00\x00\x04" +
  83. "t\x00\x00\x00\x06\x00\x00" +
  84. "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
  85. "Z\x00\x00\x00\x05I" +
  86. "2\x00\x00\x00\x04" +
  87. "D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
  88. "C\x00\x00\x00\rSELECT 1\x00" +
  89. "Z\x00\x00\x00\x05I" +
  90. "3\x00\x00\x00\x04" +
  91. "Z\x00\x00\x00\x05I"
  92. c := fakeConn(response, 0)
  93. b.StartTimer()
  94. for i := 0; i < b.N; i++ {
  95. benchMockQuery(b, c, selectStringQuery)
  96. }
  97. }
  98. var seriesRowData = func() string {
  99. var buf bytes.Buffer
  100. for i := 1; i <= 100; i++ {
  101. digits := byte(2)
  102. if i >= 100 {
  103. digits = 3
  104. } else if i < 10 {
  105. digits = 1
  106. }
  107. buf.WriteString("D\x00\x00\x00")
  108. buf.WriteByte(10 + digits)
  109. buf.WriteString("\x00\x01\x00\x00\x00")
  110. buf.WriteByte(digits)
  111. buf.WriteString(strconv.Itoa(i))
  112. }
  113. return buf.String()
  114. }()
  115. func BenchmarkMockSelectSeries(b *testing.B) {
  116. b.StopTimer()
  117. var response = "1\x00\x00\x00\x04" +
  118. "t\x00\x00\x00\x06\x00\x00" +
  119. "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
  120. "Z\x00\x00\x00\x05I" +
  121. "2\x00\x00\x00\x04" +
  122. seriesRowData +
  123. "C\x00\x00\x00\x0fSELECT 100\x00" +
  124. "Z\x00\x00\x00\x05I" +
  125. "3\x00\x00\x00\x04" +
  126. "Z\x00\x00\x00\x05I"
  127. c := fakeConn(response, 0)
  128. b.StartTimer()
  129. for i := 0; i < b.N; i++ {
  130. benchMockQuery(b, c, selectSeriesQuery)
  131. }
  132. }
  133. func benchMockQuery(b *testing.B, c *conn, query string) {
  134. stmt, err := c.Prepare(query)
  135. if err != nil {
  136. b.Fatal(err)
  137. }
  138. defer stmt.Close()
  139. rows, err := stmt.Query(nil)
  140. if err != nil {
  141. b.Fatal(err)
  142. }
  143. defer rows.Close()
  144. var dest [1]driver.Value
  145. for {
  146. if err := rows.Next(dest[:]); err != nil {
  147. if err == io.EOF {
  148. break
  149. }
  150. b.Fatal(err)
  151. }
  152. }
  153. }
  154. func BenchmarkPreparedSelectString(b *testing.B) {
  155. var result string
  156. benchPreparedQuery(b, selectStringQuery, &result)
  157. }
  158. func BenchmarkPreparedSelectSeries(b *testing.B) {
  159. var result int
  160. benchPreparedQuery(b, selectSeriesQuery, &result)
  161. }
  162. func benchPreparedQuery(b *testing.B, query string, result interface{}) {
  163. b.StopTimer()
  164. db := openTestConn(b)
  165. defer db.Close()
  166. stmt, err := db.Prepare(query)
  167. if err != nil {
  168. b.Fatal(err)
  169. }
  170. defer stmt.Close()
  171. b.StartTimer()
  172. for i := 0; i < b.N; i++ {
  173. benchPreparedQueryLoop(b, db, stmt, result)
  174. }
  175. }
  176. func benchPreparedQueryLoop(b *testing.B, db *sql.DB, stmt *sql.Stmt, result interface{}) {
  177. rows, err := stmt.Query()
  178. if err != nil {
  179. b.Fatal(err)
  180. }
  181. if !rows.Next() {
  182. rows.Close()
  183. b.Fatal("no rows")
  184. }
  185. defer rows.Close()
  186. for rows.Next() {
  187. err = rows.Scan(&result)
  188. if err != nil {
  189. b.Fatal("failed to scan")
  190. }
  191. }
  192. }
  193. // See the comment for BenchmarkMockSelectString.
  194. func BenchmarkMockPreparedSelectString(b *testing.B) {
  195. b.StopTimer()
  196. const parseResponse = "1\x00\x00\x00\x04" +
  197. "t\x00\x00\x00\x06\x00\x00" +
  198. "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
  199. "Z\x00\x00\x00\x05I"
  200. const responses = parseResponse +
  201. "2\x00\x00\x00\x04" +
  202. "D\x00\x00\x00n\x00\x01\x00\x00\x00d0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +
  203. "C\x00\x00\x00\rSELECT 1\x00" +
  204. "Z\x00\x00\x00\x05I"
  205. c := fakeConn(responses, len(parseResponse))
  206. stmt, err := c.Prepare(selectStringQuery)
  207. if err != nil {
  208. b.Fatal(err)
  209. }
  210. b.StartTimer()
  211. for i := 0; i < b.N; i++ {
  212. benchPreparedMockQuery(b, c, stmt)
  213. }
  214. }
  215. func BenchmarkMockPreparedSelectSeries(b *testing.B) {
  216. b.StopTimer()
  217. const parseResponse = "1\x00\x00\x00\x04" +
  218. "t\x00\x00\x00\x06\x00\x00" +
  219. "T\x00\x00\x00!\x00\x01?column?\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xc1\xff\xfe\xff\xff\xff\xff\x00\x00" +
  220. "Z\x00\x00\x00\x05I"
  221. var responses = parseResponse +
  222. "2\x00\x00\x00\x04" +
  223. seriesRowData +
  224. "C\x00\x00\x00\x0fSELECT 100\x00" +
  225. "Z\x00\x00\x00\x05I"
  226. c := fakeConn(responses, len(parseResponse))
  227. stmt, err := c.Prepare(selectSeriesQuery)
  228. if err != nil {
  229. b.Fatal(err)
  230. }
  231. b.StartTimer()
  232. for i := 0; i < b.N; i++ {
  233. benchPreparedMockQuery(b, c, stmt)
  234. }
  235. }
  236. func benchPreparedMockQuery(b *testing.B, c *conn, stmt driver.Stmt) {
  237. rows, err := stmt.Query(nil)
  238. if err != nil {
  239. b.Fatal(err)
  240. }
  241. defer rows.Close()
  242. var dest [1]driver.Value
  243. for {
  244. if err := rows.Next(dest[:]); err != nil {
  245. if err == io.EOF {
  246. break
  247. }
  248. b.Fatal(err)
  249. }
  250. }
  251. }
  252. func BenchmarkEncodeInt64(b *testing.B) {
  253. for i := 0; i < b.N; i++ {
  254. encode(&parameterStatus{}, int64(1234), oid.T_int8)
  255. }
  256. }
  257. func BenchmarkEncodeFloat64(b *testing.B) {
  258. for i := 0; i < b.N; i++ {
  259. encode(&parameterStatus{}, 3.14159, oid.T_float8)
  260. }
  261. }
  262. var testByteString = []byte("abcdefghijklmnopqrstuvwxyz")
  263. func BenchmarkEncodeByteaHex(b *testing.B) {
  264. for i := 0; i < b.N; i++ {
  265. encode(&parameterStatus{serverVersion: 90000}, testByteString, oid.T_bytea)
  266. }
  267. }
  268. func BenchmarkEncodeByteaEscape(b *testing.B) {
  269. for i := 0; i < b.N; i++ {
  270. encode(&parameterStatus{serverVersion: 84000}, testByteString, oid.T_bytea)
  271. }
  272. }
  273. func BenchmarkEncodeBool(b *testing.B) {
  274. for i := 0; i < b.N; i++ {
  275. encode(&parameterStatus{}, true, oid.T_bool)
  276. }
  277. }
  278. var testTimestamptz = time.Date(2001, time.January, 1, 0, 0, 0, 0, time.Local)
  279. func BenchmarkEncodeTimestamptz(b *testing.B) {
  280. for i := 0; i < b.N; i++ {
  281. encode(&parameterStatus{}, testTimestamptz, oid.T_timestamptz)
  282. }
  283. }
  284. var testIntBytes = []byte("1234")
  285. func BenchmarkDecodeInt64(b *testing.B) {
  286. for i := 0; i < b.N; i++ {
  287. decode(&parameterStatus{}, testIntBytes, oid.T_int8)
  288. }
  289. }
  290. var testFloatBytes = []byte("3.14159")
  291. func BenchmarkDecodeFloat64(b *testing.B) {
  292. for i := 0; i < b.N; i++ {
  293. decode(&parameterStatus{}, testFloatBytes, oid.T_float8)
  294. }
  295. }
  296. var testBoolBytes = []byte{'t'}
  297. func BenchmarkDecodeBool(b *testing.B) {
  298. for i := 0; i < b.N; i++ {
  299. decode(&parameterStatus{}, testBoolBytes, oid.T_bool)
  300. }
  301. }
  302. func TestDecodeBool(t *testing.T) {
  303. db := openTestConn(t)
  304. rows, err := db.Query("select true")
  305. if err != nil {
  306. t.Fatal(err)
  307. }
  308. rows.Close()
  309. }
  310. var testTimestamptzBytes = []byte("2013-09-17 22:15:32.360754-07")
  311. func BenchmarkDecodeTimestamptz(b *testing.B) {
  312. for i := 0; i < b.N; i++ {
  313. decode(&parameterStatus{}, testTimestamptzBytes, oid.T_timestamptz)
  314. }
  315. }
  316. func BenchmarkDecodeTimestamptzMultiThread(b *testing.B) {
  317. oldProcs := runtime.GOMAXPROCS(0)
  318. defer runtime.GOMAXPROCS(oldProcs)
  319. runtime.GOMAXPROCS(runtime.NumCPU())
  320. globalLocationCache = newLocationCache()
  321. f := func(wg *sync.WaitGroup, loops int) {
  322. defer wg.Done()
  323. for i := 0; i < loops; i++ {
  324. decode(&parameterStatus{}, testTimestamptzBytes, oid.T_timestamptz)
  325. }
  326. }
  327. wg := &sync.WaitGroup{}
  328. b.ResetTimer()
  329. for j := 0; j < 10; j++ {
  330. wg.Add(1)
  331. go f(wg, b.N/10)
  332. }
  333. wg.Wait()
  334. }
  335. func BenchmarkLocationCache(b *testing.B) {
  336. globalLocationCache = newLocationCache()
  337. for i := 0; i < b.N; i++ {
  338. globalLocationCache.getLocation(rand.Intn(10000))
  339. }
  340. }
  341. func BenchmarkLocationCacheMultiThread(b *testing.B) {
  342. oldProcs := runtime.GOMAXPROCS(0)
  343. defer runtime.GOMAXPROCS(oldProcs)
  344. runtime.GOMAXPROCS(runtime.NumCPU())
  345. globalLocationCache = newLocationCache()
  346. f := func(wg *sync.WaitGroup, loops int) {
  347. defer wg.Done()
  348. for i := 0; i < loops; i++ {
  349. globalLocationCache.getLocation(rand.Intn(10000))
  350. }
  351. }
  352. wg := &sync.WaitGroup{}
  353. b.ResetTimer()
  354. for j := 0; j < 10; j++ {
  355. wg.Add(1)
  356. go f(wg, b.N/10)
  357. }
  358. wg.Wait()
  359. }
  360. // Stress test the performance of parsing results from the wire.
  361. func BenchmarkResultParsing(b *testing.B) {
  362. b.StopTimer()
  363. db := openTestConn(b)
  364. defer db.Close()
  365. _, err := db.Exec("BEGIN")
  366. if err != nil {
  367. b.Fatal(err)
  368. }
  369. b.StartTimer()
  370. for i := 0; i < b.N; i++ {
  371. res, err := db.Query("SELECT generate_series(1, 50000)")
  372. if err != nil {
  373. b.Fatal(err)
  374. }
  375. res.Close()
  376. }
  377. }