From 782ead0dbf3095d0e843c2036bf40cc8ecd9b4d1 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Mon, 19 May 2014 12:08:33 -0600 Subject: [PATCH] Fix freelist allocation direction. This commit fixes the freelist so that it frees from the beginning of the data file instead of the end. It also adds a fast path for pages which can be allocated from the first free pages and it includes read transaction stats. --- Makefile | 2 +- bucket_test.go | 25 ++++++++----------------- cursor.go | 2 +- db.go | 17 ++++++++++++++++- freelist.go | 43 +++++++++++++++++++++++++------------------ freelist_test.go | 17 ++++++++--------- page.go | 6 ++++++ 7 files changed, 65 insertions(+), 47 deletions(-) diff --git a/Makefile b/Makefile index 2c0dc97..67635ae 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ cover: fmt cpuprofile: fmt @go test -c - @./bolt.test -test.v -test.run="^X" -test.bench=$(BENCH) -test.cpuprofile cpu.prof + @./bolt.test -test.v -test.run=$(TEST) -test.cpuprofile cpu.prof # go get github.com/kisielk/errcheck errcheck: diff --git a/bucket_test.go b/bucket_test.go index f6bd13f..16172a5 100644 --- a/bucket_test.go +++ b/bucket_test.go @@ -899,30 +899,21 @@ func TestBucket_Delete_Quick(t *testing.T) { assert.NoError(t, err) // Remove items one at a time and check consistency. - for i, item := range items { + for _, item := range items { err := db.Update(func(tx *Tx) error { return tx.Bucket([]byte("widgets")).Delete(item.Key) }) assert.NoError(t, err) + } - // Anything before our deletion index should be nil. - db.View(func(tx *Tx) error { - b := tx.Bucket([]byte("widgets")) - for j, exp := range items { - value := b.Get(exp.Key) - if j > i { - if !assert.Equal(t, exp.Value, value) { - t.FailNow() - } - } else { - if !assert.Nil(t, value) { - t.FailNow() - } - } - } + // Anything before our deletion index should be nil. + db.View(func(tx *Tx) error { + tx.Bucket([]byte("widgets")).ForEach(func(k, v []byte) error { + t.Fatalf("bucket should be empty; found: %06x", trunc(k, 3)) return nil }) - } + return nil + }) }) return true } diff --git a/cursor.go b/cursor.go index 31a3d5f..1c14d05 100644 --- a/cursor.go +++ b/cursor.go @@ -193,7 +193,7 @@ func (c *Cursor) last() { func (c *Cursor) search(key []byte, pgid pgid) { p, n := c.bucket.pageNode(pgid) if p != nil { - _assert((p.flags&(branchPageFlag|leafPageFlag)) != 0, "invalid page type: %d: %s", p.id, p.typ()) + _assert((p.flags&(branchPageFlag|leafPageFlag)) != 0, "invalid page type: %d: %x", p.id, p.flags) } e := elemRef{page: p, node: n} c.stack = append(c.stack, e) diff --git a/db.go b/db.go index c768ce3..ed0f2cd 100644 --- a/db.go +++ b/db.go @@ -351,7 +351,6 @@ func (db *DB) beginTx() (*Tx, error) { // Lock the meta pages while we initialize the transaction. db.metalock.Lock() - defer db.metalock.Unlock() // Exit if the database is not open yet. if !db.opened { @@ -365,6 +364,16 @@ func (db *DB) beginTx() (*Tx, error) { // Keep track of transaction until it closes. db.txs = append(db.txs, t) + n := len(db.txs) + + // Unlock the meta pages. + db.metalock.Unlock() + + // Update the transaction stats. + db.statlock.Lock() + db.stats.TxN++ + db.stats.OpenTxN = n + db.statlock.Unlock() return t, nil } @@ -418,12 +427,14 @@ func (db *DB) removeTx(tx *Tx) { break } } + n := len(db.txs) // Unlock the meta pages. db.metalock.Unlock() // Merge statistics. db.statlock.Lock() + db.stats.OpenTxN = n db.stats.TxStats.add(&tx.stats) db.statlock.Unlock() } @@ -612,6 +623,10 @@ func (db *DB) allocate(count int) (*page, error) { // Stats represents statistics about the database. type Stats struct { + // Transaction stats + TxN int // total number of completed read transactions + OpenTxN int // number of currently open read transactions + TxStats TxStats // global, ongoing stats. } diff --git a/freelist.go b/freelist.go index ebe2810..0d79bb4 100644 --- a/freelist.go +++ b/freelist.go @@ -26,30 +26,42 @@ func (f *freelist) all() []pgid { ids = append(ids, list...) } - sort.Sort(reverseSortedPgids(ids)) + sort.Sort(pgids(ids)) return ids } // allocate returns the starting page id of a contiguous list of pages of a given size. // If a contiguous block cannot be found then 0 is returned. func (f *freelist) allocate(n int) pgid { - var count int - var previd pgid + if len(f.ids) == 0 { + return 0 + } + + var initial, previd pgid for i, id := range f.ids { - // Reset count if this is not contiguous. - if previd == 0 || previd-id != 1 { - count = 1 + _assert(id > 1, "invalid page allocation: %d", id) + + // Reset initial page if this is not contiguous. + if previd == 0 || id-previd != 1 { + initial = id } // If we found a contiguous block then remove it and return it. - if count == n { - f.ids = append(f.ids[:i-(n-1)], f.ids[i+1:]...) - _assert(id > 1, "cannot allocate page 0 or 1: %d", id) - return id + if (id-initial)+1 == pgid(n) { + // If we're allocating off the beginning then take the fast path + // and just adjust the existing slice. This will use extra memory + // temporarily but the append() in free() will realloc the slice + // as is necessary. + if (i + 1) == n { + f.ids = f.ids[i+1:] + } else { + copy(f.ids[i-1:], f.ids[i+n-1:]) + f.ids = f.ids[:len(f.ids)-n] + } + return initial } previd = id - count++ } return 0 } @@ -74,7 +86,7 @@ func (f *freelist) release(txid txid) { delete(f.pending, tid) } } - sort.Sort(reverseSortedPgids(f.ids)) + sort.Sort(pgids(f.ids)) } // isFree returns whether a given page is in the free list. @@ -99,6 +111,7 @@ func (f *freelist) read(p *page) { ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[0:p.count] f.ids = make([]pgid, len(ids)) copy(f.ids, ids) + sort.Sort(pgids(ids)) } // write writes the page ids onto a freelist page. All free and pending ids are @@ -133,9 +146,3 @@ func (f *freelist) check() { } } */ - -type reverseSortedPgids []pgid - -func (s reverseSortedPgids) Len() int { return len(s) } -func (s reverseSortedPgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s reverseSortedPgids) Less(i, j int) bool { return s[i] > s[j] } diff --git a/freelist_test.go b/freelist_test.go index 2b321a4..00c71cf 100644 --- a/freelist_test.go +++ b/freelist_test.go @@ -36,15 +36,14 @@ func TestFreelist_release(t *testing.T) { // Ensure that a freelist can find contiguous blocks of pages. func TestFreelist_allocate(t *testing.T) { - f := &freelist{ids: []pgid{18, 13, 12, 9, 7, 6, 5, 4, 3}} - assert.Equal(t, f.allocate(2), pgid(12)) - assert.Equal(t, f.allocate(1), pgid(18)) - assert.Equal(t, f.allocate(3), pgid(5)) - assert.Equal(t, f.allocate(3), pgid(0)) - assert.Equal(t, f.allocate(2), pgid(3)) - assert.Equal(t, f.allocate(1), pgid(9)) - assert.Equal(t, f.allocate(0), pgid(0)) - assert.Equal(t, f.ids, []pgid{}) + f := &freelist{ids: []pgid{3, 4, 5, 6, 7, 9, 12, 13, 18}} + assert.Equal(t, 3, int(f.allocate(3))) + assert.Equal(t, 6, int(f.allocate(1))) + assert.Equal(t, 0, int(f.allocate(3))) + assert.Equal(t, 12, int(f.allocate(2))) + assert.Equal(t, 7, int(f.allocate(1))) + assert.Equal(t, 0, int(f.allocate(0))) + assert.Equal(t, []pgid{9, 18}, f.ids) } // Ensure that a freelist can deserialize from a freelist page. diff --git a/page.go b/page.go index 56cf064..cd213a4 100644 --- a/page.go +++ b/page.go @@ -128,3 +128,9 @@ type PageInfo struct { Count int OverflowCount int } + +type pgids []pgid + +func (s pgids) Len() int { return len(s) } +func (s pgids) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s pgids) Less(i, j int) bool { return s[i] > s[j] }