Commit 1913fdab authored by Marcel van Lohuizen's avatar Marcel van Lohuizen

exp/norm: changed trie to produce smaller tables.

Trie now uses sparse block when this makes sense.

R=r, r
CC=golang-dev
https://golang.org/cl/5010043
parent a3990402
......@@ -21,6 +21,7 @@ var testRunes = []int{
0x80, 0x100, 0x7FF, // 2-byte sequences
0x800, 0x999, 0xFFFF, // 3-byte sequences
0x10000, 0x10101, 0x10FFFF, // 4-byte sequences
0x200, 0x201, 0x202, 0x210, 0x215, // five entries in one sparse block
}
const fileHeader = `// Generated by running
......
This diff is collapsed.
......@@ -4,9 +4,44 @@
package norm
type valueRange struct {
value uint16 // header: value:stride
lo, hi byte // header: lo:n
}
type trie struct {
index []uint8
values []uint16
index []uint8
values []uint16
sparse []valueRange
sparseOffset []uint16
cutoff uint8 // indices >= cutoff are sparse
}
// lookupValue determines the type of block n and looks up the value for b.
// For n < t.cutoff, the block is a simple lookup table. Otherwise, the block
// is a list of ranges with an accompanying value. Given a matching range r,
// the value for b is by r.value + (b - r.lo) * stride.
func (t *trie) lookupValue(n uint8, b byte) uint16 {
if n < t.cutoff {
return t.values[uint16(n)<<6+uint16(b&maskx)]
}
offset := t.sparseOffset[n-t.cutoff]
header := t.sparse[offset]
lo := offset + 1
hi := lo + uint16(header.lo)
for lo < hi {
m := lo + (hi-lo)/2
r := t.sparse[m]
if r.lo <= b && b <= r.hi {
return r.value + uint16(b-r.lo)*header.value
}
if b < r.lo {
hi = m
} else {
lo = m + 1
}
}
return 0
}
const (
......@@ -44,8 +79,7 @@ func (t *trie) lookup(s []byte) (v uint16, sz int) {
if c1 < tx || t2 <= c1 {
return 0, 1
}
o := uint16(i)<<6 + uint16(c1)&maskx
return t.values[o], 2
return t.lookupValue(i, c1), 2
case c0 < t4:
if len(s) < 3 {
return 0, 0
......@@ -61,8 +95,7 @@ func (t *trie) lookup(s []byte) (v uint16, sz int) {
if c2 < tx || t2 <= c2 {
return 0, 2
}
o = uint16(i)<<6 + uint16(c2)&maskx
return t.values[o], 3
return t.lookupValue(i, c2), 3
case c0 < t5:
if len(s) < 4 {
return 0, 0
......@@ -84,8 +117,7 @@ func (t *trie) lookup(s []byte) (v uint16, sz int) {
if c3 < tx || t2 <= c3 {
return 0, 3
}
o = uint16(i)<<6 + uint16(c3)&maskx
return t.values[o], 4
return t.lookupValue(i, c3), 4
}
// Illegal rune
return 0, 1
......@@ -110,8 +142,7 @@ func (t *trie) lookupString(s string) (v uint16, sz int) {
if c1 < tx || t2 <= c1 {
return 0, 1
}
o := uint16(i)<<6 + uint16(c1)&maskx
return t.values[o], 2
return t.lookupValue(i, c1), 2
case c0 < t4:
if len(s) < 3 {
return 0, 0
......@@ -127,8 +158,7 @@ func (t *trie) lookupString(s string) (v uint16, sz int) {
if c2 < tx || t2 <= c2 {
return 0, 2
}
o = uint16(i)<<6 + uint16(c2)&maskx
return t.values[o], 3
return t.lookupValue(i, c2), 3
case c0 < t5:
if len(s) < 4 {
return 0, 0
......@@ -150,8 +180,7 @@ func (t *trie) lookupString(s string) (v uint16, sz int) {
if c3 < tx || t2 <= c3 {
return 0, 3
}
o = uint16(i)<<6 + uint16(c3)&maskx
return t.values[o], 4
return t.lookupValue(i, c3), 4
}
// Illegal rune
return 0, 1
......@@ -168,19 +197,16 @@ func (t *trie) lookupUnsafe(s []byte) uint16 {
return 0
}
i := t.index[c0]
o := uint16(i)<<6 + uint16(s[1])&maskx
if c0 < t3 {
return t.values[o]
return t.lookupValue(i, s[1])
}
i = t.index[o]
o = uint16(i)<<6 + uint16(s[2])&maskx
i = t.index[uint16(i)<<6+uint16(s[1])&maskx]
if c0 < t4 {
return t.values[o]
return t.lookupValue(i, s[2])
}
i = t.index[o]
o = uint16(i)<<6 + uint16(s[3])&maskx
i = t.index[uint16(i)<<6+uint16(s[2])&maskx]
if c0 < t5 {
return t.values[o]
return t.lookupValue(i, s[3])
}
return 0
}
......@@ -196,19 +222,16 @@ func (t *trie) lookupStringUnsafe(s string) uint16 {
return 0
}
i := t.index[c0]
o := uint16(i)<<6 + uint16(s[1])&maskx
if c0 < t3 {
return t.values[o]
return t.lookupValue(i, s[1])
}
i = t.index[o]
o = uint16(i)<<6 + uint16(s[2])&maskx
i = t.index[uint16(i)<<6+uint16(s[1])&maskx]
if c0 < t4 {
return t.values[o]
return t.lookupValue(i, s[2])
}
i = t.index[o]
o = uint16(i)<<6 + uint16(s[3])&maskx
i = t.index[uint16(i)<<6+uint16(s[2])&maskx]
if c0 < t5 {
return t.values[o]
return t.lookupValue(i, s[3])
}
return 0
}
......@@ -8,6 +8,41 @@ import (
// Test data is located in triedata_test.go; generated by maketesttables.
var testdata = testdataTrie
type rangeTest struct {
block uint8
lookup byte
result uint16
table []valueRange
offsets []uint16
}
var range1Off = []uint16{0, 2}
var range1 = []valueRange{
{0, 1, 0},
{1, 0x80, 0x80},
{0, 2, 0},
{1, 0x80, 0x80},
{9, 0xff, 0xff},
}
var rangeTests = []rangeTest{
{10, 0x80, 1, range1, range1Off},
{10, 0x00, 0, range1, range1Off},
{11, 0x80, 1, range1, range1Off},
{11, 0xff, 9, range1, range1Off},
{11, 0x00, 0, range1, range1Off},
}
func TestLookupSparse(t *testing.T) {
for i, test := range rangeTests {
n := trie{sparse: test.table, sparseOffset: test.offsets, cutoff: 10}
v := n.lookupValue(test.block, test.lookup)
if v != test.result {
t.Errorf("LookupSparse:%d: found %X; want %X", i, v, test.result)
}
}
}
// Test cases for illegal runes.
type trietest struct {
size int
......@@ -49,10 +84,10 @@ func TestLookup(t *testing.T) {
b, szg := mkUtf8(tt)
v, szt := testdata.lookup(b)
if int(v) != i {
t.Errorf("lookup(%U): found value %#x, expected %#x", i, v, i)
t.Errorf("lookup(%U): found value %#x, expected %#x", tt, v, i)
}
if szt != szg {
t.Errorf("lookup(%U): found size %d, expected %d", i, szt, szg)
t.Errorf("lookup(%U): found size %d, expected %d", tt, szt, szg)
}
}
for i, tt := range tests {
......
......@@ -4,34 +4,55 @@
package norm
var testRunes = []int{1, 12, 127, 128, 256, 2047, 2048, 2457, 65535, 65536, 65793, 1114111}
var testRunes = []int{1, 12, 127, 128, 256, 2047, 2048, 2457, 65535, 65536, 65793, 1114111, 512, 513, 514, 528, 533}
// testdataValues: 768 entries, 1536 bytes
// testdataValues: 192 entries, 384 bytes
// Block 2 is the null block.
var testdataValues = [768]uint16{
var testdataValues = [192]uint16{
// Block 0x0, offset 0x0
0x000c: 0x0001,
// Block 0x1, offset 0x40
0x007f: 0x0002,
// Block 0x2, offset 0x80
// Block 0x3, offset 0xc0
0x00c0: 0x0003,
// Block 0x4, offset 0x100
0x0100: 0x0004,
// Block 0x5, offset 0x140
0x017f: 0x0005,
// Block 0x6, offset 0x180
0x0180: 0x0006,
// Block 0x7, offset 0x1c0
0x01d9: 0x0007,
// Block 0x8, offset 0x200
0x023f: 0x0008,
// Block 0x9, offset 0x240
0x0240: 0x0009,
// Block 0xa, offset 0x280
0x0281: 0x000a,
// Block 0xb, offset 0x2c0
0x02ff: 0x000b,
}
// testdataSparseOffset: 10 entries, 20 bytes
var testdataSparseOffset = []uint16{0x0, 0x2, 0x4, 0x8, 0xa, 0xc, 0xe, 0x10, 0x12, 0x14}
// testdataSparseValues: 22 entries, 88 bytes
var testdataSparseValues = [22]valueRange{
// Block 0x0, offset 0x1
{value: 0x0000, lo: 0x01},
{value: 0x0003, lo: 0x80, hi: 0x80},
// Block 0x1, offset 0x2
{value: 0x0000, lo: 0x01},
{value: 0x0004, lo: 0x80, hi: 0x80},
// Block 0x2, offset 0x3
{value: 0x0001, lo: 0x03},
{value: 0x000c, lo: 0x80, hi: 0x82},
{value: 0x000f, lo: 0x90, hi: 0x90},
{value: 0x0010, lo: 0x95, hi: 0x95},
// Block 0x3, offset 0x4
{value: 0x0000, lo: 0x01},
{value: 0x0005, lo: 0xbf, hi: 0xbf},
// Block 0x4, offset 0x5
{value: 0x0000, lo: 0x01},
{value: 0x0006, lo: 0x80, hi: 0x80},
// Block 0x5, offset 0x6
{value: 0x0000, lo: 0x01},
{value: 0x0007, lo: 0x99, hi: 0x99},
// Block 0x6, offset 0x7
{value: 0x0000, lo: 0x01},
{value: 0x0008, lo: 0xbf, hi: 0xbf},
// Block 0x7, offset 0x8
{value: 0x0000, lo: 0x01},
{value: 0x0009, lo: 0x80, hi: 0x80},
// Block 0x8, offset 0x9
{value: 0x0000, lo: 0x01},
{value: 0x000a, lo: 0x81, hi: 0x81},
// Block 0x9, offset 0xa
{value: 0x0000, lo: 0x01},
{value: 0x000b, lo: 0xbf, hi: 0xbf},
}
// testdataLookup: 640 bytes
......@@ -42,22 +63,23 @@ var testdataLookup = [640]uint8{
// Block 0x2, offset 0x80
// Block 0x3, offset 0xc0
0x0c2: 0x03, 0x0c4: 0x04,
0x0df: 0x05,
0x0c8: 0x05,
0x0df: 0x06,
0x0e0: 0x04,
0x0ef: 0x05,
0x0f0: 0x07, 0x0f4: 0x09,
// Block 0x4, offset 0x100
0x120: 0x06, 0x126: 0x07,
0x120: 0x07, 0x126: 0x08,
// Block 0x5, offset 0x140
0x17f: 0x08,
0x17f: 0x09,
// Block 0x6, offset 0x180
0x180: 0x09, 0x184: 0x0a,
0x180: 0x0a, 0x184: 0x0b,
// Block 0x7, offset 0x1c0
0x1d0: 0x06,
// Block 0x8, offset 0x200
0x23f: 0x0b,
0x23f: 0x0c,
// Block 0x9, offset 0x240
0x24f: 0x08,
}
var testdataTrie = trie{testdataLookup[:], testdataValues[:]}
var testdataTrie = trie{testdataLookup[:], testdataValues[:], testdataSparseValues[:], testdataSparseOffset[:], 3}
......@@ -17,10 +17,13 @@ import (
"utf8"
)
const blockSize = 64
const maxSparseEntries = 16
// Intermediate trie structure
type trieNode struct {
table [256]*trieNode
value uint16
value int
b byte
leaf bool
}
......@@ -53,6 +56,44 @@ func (n trieNode) isInternal() bool {
return internal
}
func (n trieNode) mostFrequentStride() int {
counts := make(map[int]int)
v := 0
for _, t := range n.table[0x80 : 0x80+blockSize] {
if t != nil {
if stride := t.value - v; v != 0 && stride >= 0 {
counts[stride]++
}
v = t.value
}
}
var maxs, maxc int
for stride, cnt := range counts {
if cnt > maxc {
maxs, maxc = stride, cnt
}
}
return maxs
}
func (n trieNode) countSparseEntries() int {
stride := n.mostFrequentStride()
var count, v int
for _, t := range n.table[0x80 : 0x80+blockSize] {
tv := 0
if t != nil {
tv = t.value
}
if tv-v != stride {
if tv != 0 {
count++
}
}
v = tv
}
return count
}
func (n *trieNode) insert(rune int, value uint16) {
var p [utf8.UTFMax]byte
sz := utf8.EncodeRune(p[:], rune)
......@@ -69,35 +110,40 @@ func (n *trieNode) insert(rune int, value uint16) {
}
n = nn
}
n.value = value
n.value = int(value)
n.leaf = true
}
type nodeIndex struct {
lookupBlocks []*trieNode
valueBlocks []*trieNode
sparseBlocks []*trieNode
sparseOffset []uint16
sparseCount int
lookupBlockIdx map[uint32]uint16
valueBlockIdx map[uint32]uint16
lookupBlockIdx map[uint32]int
valueBlockIdx map[uint32]int
}
func newIndex() *nodeIndex {
index := &nodeIndex{}
index.lookupBlocks = make([]*trieNode, 0)
index.valueBlocks = make([]*trieNode, 0)
index.lookupBlockIdx = make(map[uint32]uint16)
index.valueBlockIdx = make(map[uint32]uint16)
index.sparseBlocks = make([]*trieNode, 0)
index.sparseOffset = make([]uint16, 1)
index.lookupBlockIdx = make(map[uint32]int)
index.valueBlockIdx = make(map[uint32]int)
return index
}
func computeOffsets(index *nodeIndex, n *trieNode) uint16 {
func computeOffsets(index *nodeIndex, n *trieNode) int {
if n.leaf {
return n.value
}
hasher := crc32.New(crc32.MakeTable(crc32.IEEE))
// We only index continuation bytes.
for i := 0; i < 64; i++ {
var v uint16 = 0
for i := 0; i < blockSize; i++ {
v := 0
if nn := n.table[0x80+i]; nn != nil {
v = computeOffsets(index, nn)
}
......@@ -107,7 +153,7 @@ func computeOffsets(index *nodeIndex, n *trieNode) uint16 {
if n.isInternal() {
v, ok := index.lookupBlockIdx[h]
if !ok {
v = uint16(len(index.lookupBlocks))
v = len(index.lookupBlocks)
index.lookupBlocks = append(index.lookupBlocks, n)
index.lookupBlockIdx[h] = v
}
......@@ -115,9 +161,17 @@ func computeOffsets(index *nodeIndex, n *trieNode) uint16 {
} else {
v, ok := index.valueBlockIdx[h]
if !ok {
v = uint16(len(index.valueBlocks))
index.valueBlocks = append(index.valueBlocks, n)
index.valueBlockIdx[h] = v
if c := n.countSparseEntries(); c > maxSparseEntries {
v = len(index.valueBlocks)
index.valueBlocks = append(index.valueBlocks, n)
index.valueBlockIdx[h] = v
} else {
v = -len(index.sparseOffset)
index.sparseBlocks = append(index.sparseBlocks, n)
index.sparseOffset = append(index.sparseOffset, uint16(index.sparseCount))
index.sparseCount += c + 1
index.valueBlockIdx[h] = v
}
}
n.value = v
}
......@@ -125,14 +179,14 @@ func computeOffsets(index *nodeIndex, n *trieNode) uint16 {
}
func printValueBlock(nr int, n *trieNode, offset int) {
boff := nr * 64
boff := nr * blockSize
fmt.Printf("\n// Block %#x, offset %#x", nr, boff)
var printnewline bool
for i := 0; i < 64; i++ {
for i := 0; i < blockSize; i++ {
if i%6 == 0 {
printnewline = true
}
v := uint16(0)
v := 0
if nn := n.table[i+offset]; nn != nil {
v = nn.value
}
......@@ -141,24 +195,55 @@ func printValueBlock(nr int, n *trieNode, offset int) {
fmt.Printf("\n")
printnewline = false
}
fmt.Printf("%#04x:%#04x, ", nr*64+i, v)
fmt.Printf("%#04x:%#04x, ", boff+i, v)
}
}
}
func printLookupBlock(nr int, n *trieNode, offset int) {
boff := nr * 64
func printSparseBlock(nr int, n *trieNode) {
boff := -n.value
fmt.Printf("\n// Block %#x, offset %#x", nr, boff)
v := 0
//stride := f(n)
stride := n.mostFrequentStride()
c := n.countSparseEntries()
fmt.Printf("\n{value:%#04x,lo:%#02x},", stride, uint8(c))
for i, nn := range n.table[0x80 : 0x80+blockSize] {
nv := 0
if nn != nil {
nv = nn.value
}
if nv-v != stride {
if v != 0 {
fmt.Printf(",hi:%#02x},", 0x80+i-1)
}
if nv != 0 {
fmt.Printf("\n{value:%#04x,lo:%#02x", nv, nn.b)
}
}
v = nv
}
if v != 0 {
fmt.Printf(",hi:%#02x},", 0x80+blockSize-1)
}
}
func printLookupBlock(nr int, n *trieNode, offset, cutoff int) {
boff := nr * blockSize
fmt.Printf("\n// Block %#x, offset %#x", nr, boff)
var printnewline bool
for i := 0; i < 64; i++ {
for i := 0; i < blockSize; i++ {
if i%8 == 0 {
printnewline = true
}
v := uint16(0)
v := 0
if nn := n.table[i+offset]; nn != nil {
v = nn.value
}
if v != 0 {
if v < 0 {
v = -v - 1 + cutoff
}
if printnewline {
fmt.Printf("\n")
printnewline = false
......@@ -182,7 +267,7 @@ func (t *trieNode) printTables(name string) int {
}
}
nv := len(index.valueBlocks) * 64
nv := len(index.valueBlocks) * blockSize
fmt.Printf("// %sValues: %d entries, %d bytes\n", name, nv, nv*2)
fmt.Printf("// Block 2 is the null block.\n")
fmt.Printf("var %sValues = [%d]uint16 {", name, nv)
......@@ -194,18 +279,32 @@ func (t *trieNode) printTables(name string) int {
}
fmt.Print("\n}\n\n")
ni := len(index.lookupBlocks) * 64
ls := len(index.sparseBlocks)
fmt.Printf("// %sSparseOffset: %d entries, %d bytes\n", name, ls, ls*2)
fmt.Printf("var %sSparseOffset = %#v\n\n", name, index.sparseOffset[1:])
ns := index.sparseCount
fmt.Printf("// %sSparseValues: %d entries, %d bytes\n", name, ns, ns*4)
fmt.Printf("var %sSparseValues = [%d]valueRange {", name, ns)
for i, n := range index.sparseBlocks {
printSparseBlock(i, n)
}
fmt.Print("\n}\n\n")
cutoff := len(index.valueBlocks)
ni := len(index.lookupBlocks) * blockSize
fmt.Printf("// %sLookup: %d bytes\n", name, ni)
fmt.Printf("// Block 0 is the null block.\n")
fmt.Printf("var %sLookup = [%d]uint8 {", name, ni)
printLookupBlock(0, newNode(), 0)
printLookupBlock(1, newNode(), 0)
printLookupBlock(2, newNode(), 0)
printLookupBlock(3, t, 0xC0)
printLookupBlock(0, newNode(), 0, cutoff)
printLookupBlock(1, newNode(), 0, cutoff)
printLookupBlock(2, newNode(), 0, cutoff)
printLookupBlock(3, t, 0xC0, cutoff)
for i := 4; i < len(index.lookupBlocks); i++ {
printLookupBlock(i, index.lookupBlocks[i], 0x80)
printLookupBlock(i, index.lookupBlocks[i], 0x80, cutoff)
}
fmt.Print("\n}\n\n")
fmt.Printf("var %sTrie = trie{ %sLookup[:], %sValues[:] }\n\n", name, name, name)
return nv*2 + ni
fmt.Printf("var %sTrie = trie{ %sLookup[:], %sValues[:], %sSparseValues[:], %sSparseOffset[:], %d}\n\n",
name, name, name, name, name, cutoff)
return nv*2 + ns*4 + ni + ls*2
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment