diff --git a/bench_test.go b/bench_test.go index 56c32228..19490945 100644 --- a/bench_test.go +++ b/bench_test.go @@ -6,22 +6,24 @@ import ( "testing" ) -var testDataLoaded bool +var testJoinsDataLoaded bool +var narrowTestDataLoaded bool +var int4TextVsBinaryTestDataLoaded bool +var int8TextVsBinaryTestDataLoaded bool -func loadTestData() { - if testDataLoaded { +func mustPrepare(b *testing.B, conn *Connection, name, sql string) { + if err := conn.Prepare(name, sql); err != nil { + b.Fatalf("Could not prepare %v: %v", name, err) + } +} + +func createNarrowTestData(b *testing.B, conn *Connection) { + if narrowTestDataLoaded { return } - var err error - - conn := getSharedConnection() - - _, err = conn.Execute(` + if _, err := conn.Execute(` drop table if exists narrow; - drop table if exists product_component; - drop table if exists component; - drop table if exists product; create table narrow( id serial primary key, @@ -35,6 +37,98 @@ func loadTestData() { select (random()*1000000)::int, (random()*1000000)::int, (random()*1000000)::int, (random()*1000000)::int from generate_series(1, 10000); + analyze narrow; + `); err != nil { + panic(fmt.Sprintf("Unable to create narrow test data: %v", err)) + } + + mustPrepare(b, conn, "getNarrowById", "select * from narrow where id=$1") + mustPrepare(b, conn, "getMultipleNarrowById", "select * from narrow where id between $1 and $2") + + narrowTestDataLoaded = true +} + +func BenchmarkSelectRowSimpleNarrow(b *testing.B) { + conn := getSharedConnection() + createNarrowTestData(b, conn) + + // Get random ids outside of timing + ids := make([]int32, b.N) + for i := 0; i < b.N; i++ { + ids[i] = 1 + rand.Int31n(9999) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := conn.SelectRow("select * from narrow where id=$1", ids[i]); err != nil { + b.Fatalf("Failure while benchmarking: %v", err) + } + } +} + +func BenchmarkSelectRowPreparedNarrow(b *testing.B) { + conn := getSharedConnection() + createNarrowTestData(b, conn) + + // Get random ids outside of timing + ids := make([]int32, b.N) + for i := 0; i < b.N; i++ { + ids[i] = 1 + rand.Int31n(9999) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := conn.SelectRow("getNarrowById", ids[i]); err != nil { + b.Fatalf("Failure while benchmarking: %v", err) + } + } +} + +func BenchmarkSelectRowsSimpleNarrow(b *testing.B) { + conn := getSharedConnection() + createNarrowTestData(b, conn) + + // Get random ids outside of timing + ids := make([]int32, b.N) + for i := 0; i < b.N; i++ { + ids[i] = 1 + rand.Int31n(9999) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := conn.SelectRows("select * from narrow where id between $1 and $2", ids[i], ids[i]+10); err != nil { + b.Fatalf("Failure while benchmarking: %v", err) + } + } +} + +func BenchmarkSelectRowsPreparedNarrow(b *testing.B) { + conn := getSharedConnection() + createNarrowTestData(b, conn) + + // Get random ids outside of timing + ids := make([]int32, b.N) + for i := 0; i < b.N; i++ { + ids[i] = 1 + rand.Int31n(9999) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := conn.SelectRows("getMultipleNarrowById", ids[i], ids[i]+10); err != nil { + b.Fatalf("Failure while benchmarking: %v", err) + } + } +} + +func createJoinsTestData(b *testing.B, conn *Connection) { + if testJoinsDataLoaded { + return + } + + if _, err := conn.Execute(` + drop table if exists product_component; + drop table if exists component; + drop table if exists product; create table component( id serial primary key, @@ -82,22 +176,11 @@ func loadTestData() { create index on product_component(component_id); analyze; - `) - if err != nil { + `); err != nil { panic(fmt.Sprintf("Unable to create test data: %v", err)) } - err = conn.Prepare("getNarrowById", "select * from narrow where id=$1") - if err != nil { - panic("Unable to prepare getNarrowById") - } - - err = conn.Prepare("getMultipleNarrowById", "select * from narrow where id between $1 and $2") - if err != nil { - panic("Unable to prepare getMultipleNarrowById") - } - - err = conn.Prepare("joinAggregate", ` + mustPrepare(b, conn, "joinAggregate", ` select product.id, sum(cost*quantity) as total_cost from product join product_component on product.id=product_component.product_id @@ -106,88 +189,13 @@ func loadTestData() { having sum(weight*quantity) > 10 order by total_cost desc `) - if err != nil { - panic("Unable to prepare joinAggregate") - } - testDataLoaded = true -} - -func BenchmarkSelectRowSimpleNarrow(b *testing.B) { - loadTestData() - conn := getSharedConnection() - - // Get random ids outside of timing - ids := make([]int32, b.N) - for i := 0; i < b.N; i++ { - ids[i] = 1 + rand.Int31n(9999) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := conn.SelectRow("select * from narrow where id=$1", ids[i]); err != nil { - b.Fatalf("Failure while benchmarking: %v", err) - } - } -} - -func BenchmarkSelectRowPreparedNarrow(b *testing.B) { - loadTestData() - conn := getSharedConnection() - - // Get random ids outside of timing - ids := make([]int32, b.N) - for i := 0; i < b.N; i++ { - ids[i] = 1 + rand.Int31n(9999) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := conn.SelectRow("getNarrowById", ids[i]); err != nil { - b.Fatalf("Failure while benchmarking: %v", err) - } - } -} - -func BenchmarkSelectRowsSimpleNarrow(b *testing.B) { - loadTestData() - conn := getSharedConnection() - - // Get random ids outside of timing - ids := make([]int32, b.N) - for i := 0; i < b.N; i++ { - ids[i] = 1 + rand.Int31n(9999) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := conn.SelectRows("select * from narrow where id between $1 and $2", ids[i], ids[i]+10); err != nil { - b.Fatalf("Failure while benchmarking: %v", err) - } - } -} - -func BenchmarkSelectRowsPreparedNarrow(b *testing.B) { - loadTestData() - conn := getSharedConnection() - - // Get random ids outside of timing - ids := make([]int32, b.N) - for i := 0; i < b.N; i++ { - ids[i] = 1 + rand.Int31n(9999) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := conn.SelectRows("getMultipleNarrowById", ids[i], ids[i]+10); err != nil { - b.Fatalf("Failure while benchmarking: %v", err) - } - } + testJoinsDataLoaded = true } func BenchmarkSelectRowsSimpleJoins(b *testing.B) { - loadTestData() conn := getSharedConnection() + createJoinsTestData(b, conn) sql := ` select product.id, sum(cost*quantity) as total_cost @@ -208,8 +216,8 @@ func BenchmarkSelectRowsSimpleJoins(b *testing.B) { } func BenchmarkSelectRowsPreparedJoins(b *testing.B) { - loadTestData() conn := getSharedConnection() + createJoinsTestData(b, conn) b.ResetTimer() for i := 0; i < b.N; i++ { @@ -218,3 +226,123 @@ func BenchmarkSelectRowsPreparedJoins(b *testing.B) { } } } + +func createInt4TextVsBinaryTestData(b *testing.B, conn *Connection) { + if int4TextVsBinaryTestDataLoaded { + return + } + + if _, err := conn.Execute(` + drop table if exists t; + + create temporary table t( + a int4 not null, + b int4 not null, + c int4 not null, + d int4 not null, + e int4 not null + ); + + insert into t(a, b, c, d, e) + select + (random() * 1000000)::int4, (random() * 1000000)::int4, (random() * 1000000)::int4, (random() * 1000000)::int4, (random() * 1000000)::int4 + from generate_series(1, 10); + `); err != nil { + b.Fatalf("Could not set up test data: %v", err) + } + + int4TextVsBinaryTestDataLoaded = true +} + +func BenchmarkInt4Text(b *testing.B) { + conn := getSharedConnection() + createInt4TextVsBinaryTestData(b, conn) + + binaryDecoder := valueTranscoders[oid(23)].DecodeBinary + valueTranscoders[oid(23)].DecodeBinary = nil + defer func() { valueTranscoders[oid(23)].DecodeBinary = binaryDecoder }() + + mustPrepare(b, conn, "selectInt32", "select * from t") + defer func() { conn.Deallocate("selectInt32") }() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := conn.SelectRows("selectInt32"); err != nil { + b.Fatalf("Failure while benchmarking: %v", err) + } + } +} + +func BenchmarkInt4Binary(b *testing.B) { + conn := getSharedConnection() + createInt4TextVsBinaryTestData(b, conn) + mustPrepare(b, conn, "selectInt32", "select * from t") + defer func() { conn.Deallocate("selectInt32") }() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := conn.SelectRows("selectInt32"); err != nil { + b.Fatalf("Failure while benchmarking: %v", err) + } + } +} + +func createInt8TextVsBinaryTestData(b *testing.B, conn *Connection) { + if int8TextVsBinaryTestDataLoaded { + return + } + + if _, err := conn.Execute(` + drop table if exists t; + + create temporary table t( + a int8 not null, + b int8 not null, + c int8 not null, + d int8 not null, + e int8 not null + ); + + insert into t(a, b, c, d, e) + select + (random() * 1000000)::int8, (random() * 1000000)::int8, (random() * 1000000)::int8, (random() * 1000000)::int8, (random() * 1000000)::int8 + from generate_series(1, 10); + `); err != nil { + b.Fatalf("Could not set up test data: %v", err) + } + + int8TextVsBinaryTestDataLoaded = true +} + +func BenchmarkInt8Text(b *testing.B) { + conn := getSharedConnection() + createInt8TextVsBinaryTestData(b, conn) + + binaryDecoder := valueTranscoders[oid(20)].DecodeBinary + valueTranscoders[oid(20)].DecodeBinary = nil + defer func() { valueTranscoders[oid(20)].DecodeBinary = binaryDecoder }() + + mustPrepare(b, conn, "selectInt64", "select * from t") + defer func() { conn.Deallocate("selectInt64") }() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := conn.SelectRows("selectInt64"); err != nil { + b.Fatalf("Failure while benchmarking: %v", err) + } + } +} + +func BenchmarkInt8Binary(b *testing.B) { + conn := getSharedConnection() + createInt8TextVsBinaryTestData(b, conn) + mustPrepare(b, conn, "selectInt64", "select * from t") + defer func() { conn.Deallocate("selectInt64") }() + + b.ResetTimer() + for i := 0; i < b.N; i++ { + if _, err := conn.SelectRows("selectInt64"); err != nil { + b.Fatalf("Failure while benchmarking: %v", err) + } + } +} diff --git a/connection.go b/connection.go index c1b8ba7c..56e82f5d 100644 --- a/connection.go +++ b/connection.go @@ -281,6 +281,12 @@ func (c *Connection) Prepare(name, sql string) (err error) { ps.ParameterOids = c.rxParameterDescription(r) case rowDescription: ps.FieldDescriptions = c.rxRowDescription(r) + for i := range ps.FieldDescriptions { + oid := ps.FieldDescriptions[i].DataType + if valueTranscoders[oid] != nil && valueTranscoders[oid].DecodeBinary != nil { + ps.FieldDescriptions[i].FormatCode = 1 + } + } case readyForQuery: c.preparedStatements[name] = &ps return @@ -339,6 +345,7 @@ func (c *Connection) sendPreparedQuery(ps *PreparedStatement, arguments ...inter buf.WriteByte(0) buf.WriteString(ps.Name) buf.WriteByte(0) + binary.Write(buf, binary.BigEndian, int16(len(ps.ParameterOids))) for _, oid := range ps.ParameterOids { transcoder := valueTranscoders[oid] @@ -356,7 +363,16 @@ func (c *Connection) sendPreparedQuery(ps *PreparedStatement, arguments ...inter } transcoder.EncodeTo(buf, arguments[i]) } - binary.Write(buf, binary.BigEndian, int16(0)) + + binary.Write(buf, binary.BigEndian, int16(len(ps.FieldDescriptions))) + for _, fd := range ps.FieldDescriptions { + transcoder := valueTranscoders[fd.DataType] + if transcoder != nil && transcoder.DecodeBinary != nil { + binary.Write(buf, binary.BigEndian, int16(1)) + } else { + binary.Write(buf, binary.BigEndian, int16(0)) + } + } err = c.txMsg('B', buf) if err != nil { diff --git a/message_reader.go b/message_reader.go index 7e2c74c6..152d2623 100644 --- a/message_reader.go +++ b/message_reader.go @@ -30,6 +30,12 @@ func (r *MessageReader) ReadInt32() int32 { return n } +func (r *MessageReader) ReadInt64() int64 { + n := int64(binary.BigEndian.Uint64((*r)[:8])) + *r = (*r)[8:] + return n +} + func (r *MessageReader) ReadOid() oid { n := oid(binary.BigEndian.Uint32((*r)[:4])) *r = (*r)[4:] diff --git a/value_transcoder.go b/value_transcoder.go index 0fa98ff5..3b250577 100644 --- a/value_transcoder.go +++ b/value_transcoder.go @@ -34,8 +34,9 @@ func init() { // int8 valueTranscoders[oid(20)] = &valueTranscoder{ - DecodeText: decodeInt8FromText, - EncodeTo: encodeInt8} + DecodeText: decodeInt8FromText, + DecodeBinary: decodeInt8FromBinary, + EncodeTo: encodeInt8} // int2 valueTranscoders[oid(21)] = &valueTranscoder{ @@ -44,8 +45,9 @@ func init() { // int4 valueTranscoders[oid(23)] = &valueTranscoder{ - DecodeText: decodeInt4FromText, - EncodeTo: encodeInt4} + DecodeText: decodeInt4FromText, + DecodeBinary: decodeInt4FromBinary, + EncodeTo: encodeInt4} // text valueTranscoders[oid(25)] = &valueTranscoder{ @@ -97,6 +99,13 @@ func decodeInt8FromText(mr *MessageReader, size int32) interface{} { return n } +func decodeInt8FromBinary(mr *MessageReader, size int32) interface{} { + if size != 8 { + panic("Received an invalid size for an int8") + } + return mr.ReadInt64() +} + func encodeInt8(buf *bytes.Buffer, value interface{}) { v := value.(int64) s := strconv.FormatInt(int64(v), 10) @@ -129,6 +138,13 @@ func decodeInt4FromText(mr *MessageReader, size int32) interface{} { return int32(n) } +func decodeInt4FromBinary(mr *MessageReader, size int32) interface{} { + if size != 4 { + panic("Received an invalid size for an int4") + } + return mr.ReadInt32() +} + func encodeInt4(buf *bytes.Buffer, value interface{}) { v := value.(int32) s := strconv.FormatInt(int64(v), 10)