diff --git a/pool/bench_test.go b/pool/bench_test.go new file mode 100644 index 00000000..ad76fe4a --- /dev/null +++ b/pool/bench_test.go @@ -0,0 +1,69 @@ +package pool_test + +import ( + "context" + "os" + "testing" + + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pool" + "github.com/stretchr/testify/require" +) + +func BenchmarkMinimalPreparedSelectBaseline(b *testing.B) { + config, err := pool.ParseConfig(os.Getenv("PGX_TEST_DATABASE")) + require.NoError(b, err) + + config.AfterConnect = func(ctx context.Context, c *pgx.Conn) error { + _, err := c.Prepare(ctx, "ps1", "select $1::int8") + return err + } + + db, err := pool.ConnectConfig(context.Background(), config) + require.NoError(b, err) + + conn, err := db.Acquire(context.Background()) + require.NoError(b, err) + defer conn.Release() + + var n int64 + + b.ResetTimer() + for i := 0; i < b.N; i++ { + err = conn.QueryRow(context.Background(), "ps1", i).Scan(&n) + if err != nil { + b.Fatal(err) + } + + if n != int64(i) { + b.Fatalf("expected %d, got %d", i, n) + } + } +} + +func BenchmarkMinimalPreparedSelect(b *testing.B) { + config, err := pool.ParseConfig(os.Getenv("PGX_TEST_DATABASE")) + require.NoError(b, err) + + config.AfterConnect = func(ctx context.Context, c *pgx.Conn) error { + _, err := c.Prepare(ctx, "ps1", "select $1::int8") + return err + } + + db, err := pool.ConnectConfig(context.Background(), config) + require.NoError(b, err) + + var n int64 + + b.ResetTimer() + for i := 0; i < b.N; i++ { + err = db.QueryRow(context.Background(), "ps1", i).Scan(&n) + if err != nil { + b.Fatal(err) + } + + if n != int64(i) { + b.Fatalf("expected %d, got %d", i, n) + } + } +} diff --git a/pool/pool.go b/pool/pool.go index 360ef24e..da12ddff 100644 --- a/pool/pool.go +++ b/pool/pool.go @@ -4,6 +4,7 @@ import ( "context" "runtime" "strconv" + "sync" "time" "github.com/jackc/pgconn" @@ -24,6 +25,9 @@ type Pool struct { maxConnLifetime time.Duration healthCheckPeriod time.Duration closeChan chan struct{} + + preallocatedConnsMux sync.Mutex + preallocatedConns []Conn } // Config is the configuration struct for creating a pool. It is highly recommended to modify a Config returned by @@ -212,6 +216,24 @@ func (p *Pool) checkIdleConnsHealth() { } } +func (p *Pool) getConn(res *puddle.Resource) *Conn { + p.preallocatedConnsMux.Lock() + + if len(p.preallocatedConns) == 0 { + p.preallocatedConns = make([]Conn, 128) + } + + c := &p.preallocatedConns[len(p.preallocatedConns)-1] + p.preallocatedConns = p.preallocatedConns[0 : len(p.preallocatedConns)-1] + + p.preallocatedConnsMux.Unlock() + + c.res = res + c.p = p + + return c +} + func (p *Pool) Acquire(ctx context.Context) (*Conn, error) { for { res, err := p.p.Acquire(ctx) @@ -220,7 +242,7 @@ func (p *Pool) Acquire(ctx context.Context) (*Conn, error) { } if p.beforeAcquire == nil || p.beforeAcquire(res.Value().(*pgx.Conn)) { - return &Conn{res: res, p: p}, nil + return p.getConn(res), nil } res.Destroy() @@ -234,7 +256,7 @@ func (p *Pool) AcquireAllIdle() []*Conn { conns := make([]*Conn, 0, len(resources)) for _, res := range resources { if p.beforeAcquire == nil || p.beforeAcquire(res.Value().(*pgx.Conn)) { - conns = append(conns, &Conn{res: res, p: p}) + conns = append(conns, p.getConn(res)) } else { res.Destroy() }