Commit 601045e8 authored by Marcel van Lohuizen's avatar Marcel van Lohuizen

exp/locale/collate: changed trie in first step towards support for multiple locales.

- Allow handles into the trie for different locales.  Multiple tables share the same
  try to allow for reuse of blocks.
- Significantly improved memory footprint and reduced allocations of trieNodes.
  This speeds up generation by about 30% and allows keeping trieNodes around
  for multiple locales during generation.
- Renamed print method to fprint.

R=r
CC=golang-dev
https://golang.org/cl/6408052
parent 48ca3f28
...@@ -66,6 +66,7 @@ func (e *entry) contractionStarter() bool { ...@@ -66,6 +66,7 @@ func (e *entry) contractionStarter() bool {
// tables using Add and AddTailoring before making any call to Build. This allows // tables using Add and AddTailoring before making any call to Build. This allows
// Builder to ensure that a root table can support tailorings for each locale. // Builder to ensure that a root table can support tailorings for each locale.
type Builder struct { type Builder struct {
index *trieBuilder
entryMap map[string]*entry entryMap map[string]*entry
entry []*entry entry []*entry
t *table t *table
...@@ -76,6 +77,7 @@ type Builder struct { ...@@ -76,6 +77,7 @@ type Builder struct {
// NewBuilder returns a new Builder. // NewBuilder returns a new Builder.
func NewBuilder() *Builder { func NewBuilder() *Builder {
b := &Builder{ b := &Builder{
index: newTrieBuilder(),
entryMap: make(map[string]*entry), entryMap: make(map[string]*entry),
} }
return b return b
...@@ -218,7 +220,7 @@ func (b *Builder) Print(w io.Writer) (int, error) { ...@@ -218,7 +220,7 @@ func (b *Builder) Print(w io.Writer) (int, error) {
return 0, err return 0, err
} }
// TODO: support multiple locales // TODO: support multiple locales
n, _, err := t.print(w, "root") n, _, err := t.fprint(w, "root")
return n, err return n, err
} }
...@@ -510,7 +512,8 @@ func (b *Builder) buildTrie() { ...@@ -510,7 +512,8 @@ func (b *Builder) buildTrie() {
t.insert(e.runes[0], ce) t.insert(e.runes[0], ce)
} }
} }
i, err := t.generate() b.t.root = b.index.addTrie(t)
i, err := b.index.generate()
b.t.index = *i b.t.index = *i
b.error(err) b.error(err)
} }
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
// It implements the non-exported interface collate.tableInitializer // It implements the non-exported interface collate.tableInitializer
type table struct { type table struct {
index trie // main trie index trie // main trie
root *trieHandle
// expansion info // expansion info
expandElem []uint32 expandElem []uint32
...@@ -32,6 +33,10 @@ func (t *table) TrieValues() []uint32 { ...@@ -32,6 +33,10 @@ func (t *table) TrieValues() []uint32 {
return t.index.values return t.index.values
} }
func (t *table) FirstBlockOffsets() (i, v uint16) {
return t.root.lookupStart, t.root.valueStart
}
func (t *table) ExpandElems() []uint32 { func (t *table) ExpandElems() []uint32 {
return t.expandElem return t.expandElem
} }
...@@ -51,7 +56,7 @@ func (t *table) MaxContractLen() int { ...@@ -51,7 +56,7 @@ func (t *table) MaxContractLen() int {
// print writes the table as Go compilable code to w. It prefixes the // print writes the table as Go compilable code to w. It prefixes the
// variable names with name. It returns the number of bytes written // variable names with name. It returns the number of bytes written
// and the size of the resulting table. // and the size of the resulting table.
func (t *table) print(w io.Writer, name string) (n, size int, err error) { func (t *table) fprint(w io.Writer, name string) (n, size int, err error) {
update := func(nn, sz int, e error) { update := func(nn, sz int, e error) {
n += nn n += nn
if err == nil { if err == nil {
...@@ -66,7 +71,7 @@ func (t *table) print(w io.Writer, name string) (n, size int, err error) { ...@@ -66,7 +71,7 @@ func (t *table) print(w io.Writer, name string) (n, size int, err error) {
// Write main table. // Write main table.
size += int(reflect.TypeOf(*t).Size()) size += int(reflect.TypeOf(*t).Size())
p("var %sTable = table{\n", name) p("var %sTable = table{\n", name)
update(t.index.printStruct(w, name)) update(t.index.printStruct(w, t.root, name))
p(",\n") p(",\n")
p("%sExpandElem[:],\n", name) p("%sExpandElem[:],\n", name)
update(t.contractTries.printStruct(w, name)) update(t.contractTries.printStruct(w, name))
......
...@@ -15,7 +15,6 @@ import ( ...@@ -15,7 +15,6 @@ import (
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"io" "io"
"log"
"reflect" "reflect"
) )
...@@ -24,6 +23,11 @@ const ( ...@@ -24,6 +23,11 @@ const (
blockOffset = 2 // Substract 2 blocks to compensate for the 0x80 added to continuation bytes. blockOffset = 2 // Substract 2 blocks to compensate for the 0x80 added to continuation bytes.
) )
type trieHandle struct {
lookupStart uint16 // offset in table for first byte
valueStart uint16 // offset in table for first byte
}
type trie struct { type trie struct {
index []uint16 index []uint16
values []uint32 values []uint32
...@@ -31,181 +35,189 @@ type trie struct { ...@@ -31,181 +35,189 @@ type trie struct {
// trieNode is the intermediate trie structure used for generating a trie. // trieNode is the intermediate trie structure used for generating a trie.
type trieNode struct { type trieNode struct {
table [256]*trieNode index []*trieNode
value int64 value []uint32
b byte b byte
leaf bool ref uint16
} }
func newNode() *trieNode { func newNode() *trieNode {
return new(trieNode) return &trieNode{
index: make([]*trieNode, 64),
value: make([]uint32, 128), // root node size is 128 instead of 64
}
} }
func (n *trieNode) isInternal() bool { func (n *trieNode) isInternal() bool {
internal := true return n.value != nil
for i := 0; i < 256; i++ {
if nn := n.table[i]; nn != nil {
if !internal && !nn.leaf {
log.Fatalf("trie:isInternal: node contains both leaf and non-leaf children (%v)", n)
}
internal = internal && !nn.leaf
}
}
return internal
} }
func (n *trieNode) insert(r rune, value uint32) { func (n *trieNode) insert(r rune, value uint32) {
for _, b := range []byte(string(r)) { const maskx = 0x3F // mask out two most-significant bits
if n.leaf { str := string(r)
log.Fatalf("trie:insert: node (%#v) should not be a leaf", n) if len(str) == 1 {
n.value[str[0]] = value
return
}
for i := 0; i < len(str)-1; i++ {
b := str[i] & maskx
if n.index == nil {
n.index = make([]*trieNode, blockSize)
} }
nn := n.table[b] nn := n.index[b]
if nn == nil { if nn == nil {
nn = newNode() nn = &trieNode{}
nn.b = b nn.b = b
n.table[b] = nn n.index[b] = nn
} }
n = nn n = nn
} }
n.value = int64(value) if n.value == nil {
n.leaf = true n.value = make([]uint32, blockSize)
}
b := str[len(str)-1] & maskx
n.value[b] = value
} }
type nodeIndex struct { type trieBuilder struct {
t *trie
roots []*trieHandle
lookupBlocks []*trieNode lookupBlocks []*trieNode
valueBlocks []*trieNode valueBlocks []*trieNode
lookupBlockIdx map[uint32]int64 lookupBlockIdx map[uint32]*trieNode
valueBlockIdx map[uint32]int64 valueBlockIdx map[uint32]*trieNode
} }
func newIndex() *nodeIndex { func newTrieBuilder() *trieBuilder {
index := &nodeIndex{} index := &trieBuilder{}
index.lookupBlocks = make([]*trieNode, 0) index.lookupBlocks = make([]*trieNode, 0)
index.valueBlocks = make([]*trieNode, 0) index.valueBlocks = make([]*trieNode, 0)
index.lookupBlockIdx = make(map[uint32]int64) index.lookupBlockIdx = make(map[uint32]*trieNode)
index.valueBlockIdx = make(map[uint32]int64) index.valueBlockIdx = make(map[uint32]*trieNode)
// The third nil is the default null block. The other two blocks
// are used to guarantee an offset of at least 3 for each block.
index.lookupBlocks = append(index.lookupBlocks, nil, nil, nil)
index.t = &trie{}
return index return index
} }
func computeOffsets(index *nodeIndex, n *trieNode) int64 { func (b *trieBuilder) computeOffsets(n *trieNode) *trieNode {
if n.leaf {
return n.value
}
hasher := fnv.New32() hasher := fnv.New32()
// We only index continuation bytes. if n.index != nil {
for i := 0; i < blockSize; i++ { for i, nn := range n.index {
v := int64(0) v := uint16(0)
if nn := n.table[0x80+i]; nn != nil { if nn != nil {
v = computeOffsets(index, nn) nn = b.computeOffsets(nn)
n.index[i] = nn
v = nn.ref
}
hasher.Write([]byte{byte(v >> 8), byte(v)})
} }
hasher.Write([]byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)}) h := hasher.Sum32()
} nn, ok := b.lookupBlockIdx[h]
h := hasher.Sum32()
if n.isInternal() {
v, ok := index.lookupBlockIdx[h]
if !ok { if !ok {
v = int64(len(index.lookupBlocks)) - blockOffset n.ref = uint16(len(b.lookupBlocks)) - blockOffset
index.lookupBlocks = append(index.lookupBlocks, n) b.lookupBlocks = append(b.lookupBlocks, n)
index.lookupBlockIdx[h] = v b.lookupBlockIdx[h] = n
} else {
n = nn
} }
n.value = v
} else { } else {
v, ok := index.valueBlockIdx[h] for _, v := range n.value {
hasher.Write([]byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)})
}
h := hasher.Sum32()
nn, ok := b.valueBlockIdx[h]
if !ok { if !ok {
v = int64(len(index.valueBlocks)) - blockOffset n.ref = uint16(len(b.valueBlocks)) - blockOffset
index.valueBlocks = append(index.valueBlocks, n) b.valueBlocks = append(b.valueBlocks, n)
index.valueBlockIdx[h] = v b.valueBlockIdx[h] = n
} else {
n = nn
} }
n.value = v
} }
return n.value return n
} }
func genValueBlock(t *trie, n *trieNode, offset int) error { func (b *trieBuilder) addStartValueBlock(n *trieNode) uint16 {
for i := 0; i < blockSize; i++ { hasher := fnv.New32()
v := int64(0) for _, v := range n.value[:2*blockSize] {
if nn := n.table[i+offset]; nn != nil { hasher.Write([]byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)})
v = nn.value
}
if v >= 1<<32 {
return fmt.Errorf("value %d at index %d does not fit in uint32", v, len(t.values))
}
t.values = append(t.values, uint32(v))
} }
return nil h := hasher.Sum32()
nn, ok := b.valueBlockIdx[h]
if !ok {
n.ref = uint16(len(b.valueBlocks))
b.valueBlocks = append(b.valueBlocks, n)
// Add a dummy block to accommodate the double block size.
b.valueBlocks = append(b.valueBlocks, nil)
b.valueBlockIdx[h] = n
} else {
n = nn
}
return n.ref
} }
func genLookupBlock(t *trie, n *trieNode, offset int) error { func genValueBlock(t *trie, n *trieNode) {
for i := 0; i < blockSize; i++ { if n != nil {
v := int64(0) for _, v := range n.value {
if nn := n.table[i+offset]; nn != nil { t.values = append(t.values, v)
v = nn.value
}
if v >= 1<<16 {
return fmt.Errorf("value %d at index %d does not fit in uint16", v, len(t.index))
} }
t.index = append(t.index, uint16(v))
} }
return nil
} }
// generate generates and returns the trie for n. func genLookupBlock(t *trie, n *trieNode) {
func (n *trieNode) generate() (t *trie, err error) { for _, nn := range n.index {
seterr := func(e error) { v := uint16(0)
if err == nil { if nn != nil {
err = e v = nn.ref
} }
t.index = append(t.index, v)
} }
index := newIndex() }
// Values for 7-bit ASCII are stored in the first of two blocks, followed by a nil block.
index.valueBlocks = append(index.valueBlocks, nil, nil, nil) func (b *trieBuilder) addTrie(n *trieNode) *trieHandle {
// First byte of multi-byte UTF-8 codepoints are indexed in 4th block. h := &trieHandle{}
index.lookupBlocks = append(index.lookupBlocks, nil, nil, nil, nil) b.roots = append(b.roots, h)
// Index starter bytes of multi-byte UTF-8. h.valueStart = b.addStartValueBlock(n)
for i := 0xC0; i < 0x100; i++ { if len(b.roots) == 1 {
if n.table[i] != nil { // We insert a null block after the first start value block.
computeOffsets(index, n.table[i]) // This ensures that continuation bytes UTF-8 sequences of length
} // greater than 2 will automatically hit a null block if there
// was an undefined entry.
b.valueBlocks = append(b.valueBlocks, nil)
} }
t = &trie{} n = b.computeOffsets(n)
seterr(genValueBlock(t, n, 0)) // Offset by one extra block as the first byte starts at 0xC0 instead of 0x80.
seterr(genValueBlock(t, n, 64)) h.lookupStart = n.ref - 1
seterr(genValueBlock(t, newNode(), 0)) return h
for i := 3; i < len(index.valueBlocks); i++ { }
seterr(genValueBlock(t, index.valueBlocks[i], 0x80))
// generate generates and returns the trie for n.
func (b *trieBuilder) generate() (t *trie, err error) {
t = b.t
if len(b.valueBlocks) >= 1<<16 {
return nil, fmt.Errorf("maximum number of value blocks exceeded (%d > %d)", len(b.valueBlocks), 1<<16)
} }
if len(index.valueBlocks) >= 1<<16 { if len(b.lookupBlocks) >= 1<<16 {
seterr(fmt.Errorf("maximum number of value blocks exceeded (%d > %d)", len(index.valueBlocks), 1<<16)) return nil, fmt.Errorf("maximum number of lookup blocks exceeded (%d > %d)", len(b.lookupBlocks), 1<<16)
return
} }
seterr(genLookupBlock(t, newNode(), 0)) genValueBlock(t, b.valueBlocks[0])
seterr(genLookupBlock(t, newNode(), 0)) genValueBlock(t, &trieNode{value: make([]uint32, 64)})
seterr(genLookupBlock(t, newNode(), 0)) for i := 2; i < len(b.valueBlocks); i++ {
seterr(genLookupBlock(t, n, 0xC0)) genValueBlock(t, b.valueBlocks[i])
for i := 4; i < len(index.lookupBlocks); i++ {
seterr(genLookupBlock(t, index.lookupBlocks[i], 0x80))
} }
return n := &trieNode{index: make([]*trieNode, 64)}
} genLookupBlock(t, n)
genLookupBlock(t, n)
// print writes a compilable trie to w. It returns the number of characters genLookupBlock(t, n)
// printed and the size of the generated structure in bytes. for i := 3; i < len(b.lookupBlocks); i++ {
func (t *trie) print(w io.Writer, name string) (n, size int, err error) { genLookupBlock(t, b.lookupBlocks[i])
update3 := func(nn, sz int, e error) {
n += nn
if err == nil {
err = e
}
size += sz
} }
update2 := func(nn int, e error) { update3(nn, 0, e) } return b.t, nil
update3(t.printArrays(w, name))
update2(fmt.Fprintf(w, "var %sTrie = ", name))
update3(t.printStruct(w, name))
update2(fmt.Fprintln(w))
return
} }
func (t *trie) printArrays(w io.Writer, name string) (n, size int, err error) { func (t *trie) printArrays(w io.Writer, name string) (n, size int, err error) {
...@@ -261,8 +273,9 @@ func (t *trie) printArrays(w io.Writer, name string) (n, size int, err error) { ...@@ -261,8 +273,9 @@ func (t *trie) printArrays(w io.Writer, name string) (n, size int, err error) {
return n, nv*4 + ni*2, err return n, nv*4 + ni*2, err
} }
func (t *trie) printStruct(w io.Writer, name string) (n, sz int, err error) { func (t *trie) printStruct(w io.Writer, handle *trieHandle, name string) (n, sz int, err error) {
n, err = fmt.Fprintf(w, "trie{ %sLookup[:], %sValues[:]}", name, name) const msg = "trie{ %sLookup[%d:], %sValues[%d:], %sLookup[:], %sValues[:]}"
n, err = fmt.Fprintf(w, msg, name, handle.lookupStart*blockSize, name, handle.valueStart*blockSize, name, name)
sz += int(reflect.TypeOf(trie{}).Size()) sz += int(reflect.TypeOf(trie{}).Size())
return return
} }
...@@ -6,6 +6,7 @@ package build ...@@ -6,6 +6,7 @@ package build
import ( import (
"bytes" "bytes"
"fmt"
"testing" "testing"
) )
...@@ -24,7 +25,9 @@ func makeTestTrie(t *testing.T) trie { ...@@ -24,7 +25,9 @@ func makeTestTrie(t *testing.T) trie {
for i, r := range testRunes { for i, r := range testRunes {
n.insert(r, uint32(i)) n.insert(r, uint32(i))
} }
tr, err := n.generate() idx := newTrieBuilder()
idx.addTrie(n)
tr, err := idx.generate()
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
} }
...@@ -34,9 +37,11 @@ func makeTestTrie(t *testing.T) trie { ...@@ -34,9 +37,11 @@ func makeTestTrie(t *testing.T) trie {
func TestGenerateTrie(t *testing.T) { func TestGenerateTrie(t *testing.T) {
testdata := makeTestTrie(t) testdata := makeTestTrie(t)
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
testdata.print(buf, "test") testdata.printArrays(buf, "test")
fmt.Fprintf(buf, "var testTrie = ")
testdata.printStruct(buf, &trieHandle{19, 0}, "test")
if output != buf.String() { if output != buf.String() {
t.Errorf("output differs") t.Error("output differs")
} }
} }
...@@ -79,25 +84,24 @@ var testLookup = [640]uint16 { ...@@ -79,25 +84,24 @@ var testLookup = [640]uint16 {
// Block 0x1, offset 0x40 // Block 0x1, offset 0x40
// Block 0x2, offset 0x80 // Block 0x2, offset 0x80
// Block 0x3, offset 0xc0 // Block 0x3, offset 0xc0
0x0c2:0x01, 0x0c4:0x02, 0x0e0:0x05, 0x0e6:0x06,
0x0c8:0x03,
0x0df:0x04,
0x0e0:0x02,
0x0ef:0x03,
0x0f0:0x05, 0x0f4:0x07,
// Block 0x4, offset 0x100 // Block 0x4, offset 0x100
0x120:0x05, 0x126:0x06, 0x13f:0x07,
// Block 0x5, offset 0x140 // Block 0x5, offset 0x140
0x17f:0x07, 0x140:0x08, 0x144:0x09,
// Block 0x6, offset 0x180 // Block 0x6, offset 0x180
0x180:0x08, 0x184:0x09, 0x190:0x03,
// Block 0x7, offset 0x1c0 // Block 0x7, offset 0x1c0
0x1d0:0x04, 0x1ff:0x0a,
// Block 0x8, offset 0x200 // Block 0x8, offset 0x200
0x23f:0x0a, 0x20f:0x05,
// Block 0x9, offset 0x240 // Block 0x9, offset 0x240
0x24f:0x06, 0x242:0x01, 0x244:0x02,
0x248:0x03,
0x25f:0x04,
0x260:0x01,
0x26f:0x02,
0x270:0x04, 0x274:0x06,
} }
var testTrie = trie{ testLookup[:], testValues[:]} var testTrie = trie{ testLookup[1216:], testValues[0:], testLookup[:], testValues[:]}`
`
...@@ -12,8 +12,11 @@ func Init(data interface{}) *Collator { ...@@ -12,8 +12,11 @@ func Init(data interface{}) *Collator {
return nil return nil
} }
t := &table{} t := &table{}
loff, voff := init.FirstBlockOffsets()
t.index.index = init.TrieIndex() t.index.index = init.TrieIndex()
t.index.index0 = t.index.index[blockSize*loff:]
t.index.values = init.TrieValues() t.index.values = init.TrieValues()
t.index.values0 = t.index.values[blockSize*voff:]
t.expandElem = init.ExpandElems() t.expandElem = init.ExpandElems()
t.contractTries = init.ContractTries() t.contractTries = init.ContractTries()
t.contractElem = init.ContractElems() t.contractElem = init.ContractElems()
...@@ -24,6 +27,7 @@ func Init(data interface{}) *Collator { ...@@ -24,6 +27,7 @@ func Init(data interface{}) *Collator {
type tableInitializer interface { type tableInitializer interface {
TrieIndex() []uint16 TrieIndex() []uint16
TrieValues() []uint32 TrieValues() []uint32
FirstBlockOffsets() (lookup, value uint16)
ExpandElems() []uint32 ExpandElems() []uint32
ContractTries() []struct{ l, h, n, i uint8 } ContractTries() []struct{ l, h, n, i uint8 }
ContractElems() []uint32 ContractElems() []uint32
......
...@@ -45,9 +45,10 @@ func makeTable(in []input) (*collate.Collator, error) { ...@@ -45,9 +45,10 @@ func makeTable(in []input) (*collate.Collator, error) {
b.Add([]rune(r.str), r.ces) b.Add([]rune(r.str), r.ces)
} }
c, err := b.Build("") c, err := b.Build("")
if err == nil { if c == nil {
collate.InitCollator(c) return nil, err
} }
collate.InitCollator(c)
return c, err return c, err
} }
......
This diff is collapsed.
...@@ -14,8 +14,10 @@ package collate ...@@ -14,8 +14,10 @@ package collate
const blockSize = 64 const blockSize = 64
type trie struct { type trie struct {
index []uint16 index0 []uint16 // index for first byte (0xC0-0xFF)
values []uint32 values0 []uint32 // index for first byte (0x00-0x7F)
index []uint16
values []uint32
} }
const ( const (
...@@ -40,14 +42,14 @@ func (t *trie) lookup(s []byte) (v colElem, sz int) { ...@@ -40,14 +42,14 @@ func (t *trie) lookup(s []byte) (v colElem, sz int) {
c0 := s[0] c0 := s[0]
switch { switch {
case c0 < tx: case c0 < tx:
return colElem(t.values[c0]), 1 return colElem(t.values0[c0]), 1
case c0 < t2: case c0 < t2:
return 0, 1 return 0, 1
case c0 < t3: case c0 < t3:
if len(s) < 2 { if len(s) < 2 {
return 0, 0 return 0, 0
} }
i := t.index[c0] i := t.index0[c0]
c1 := s[1] c1 := s[1]
if c1 < tx || t2 <= c1 { if c1 < tx || t2 <= c1 {
return 0, 1 return 0, 1
...@@ -57,7 +59,7 @@ func (t *trie) lookup(s []byte) (v colElem, sz int) { ...@@ -57,7 +59,7 @@ func (t *trie) lookup(s []byte) (v colElem, sz int) {
if len(s) < 3 { if len(s) < 3 {
return 0, 0 return 0, 0
} }
i := t.index[c0] i := t.index0[c0]
c1 := s[1] c1 := s[1]
if c1 < tx || t2 <= c1 { if c1 < tx || t2 <= c1 {
return 0, 1 return 0, 1
...@@ -73,7 +75,7 @@ func (t *trie) lookup(s []byte) (v colElem, sz int) { ...@@ -73,7 +75,7 @@ func (t *trie) lookup(s []byte) (v colElem, sz int) {
if len(s) < 4 { if len(s) < 4 {
return 0, 0 return 0, 0
} }
i := t.index[c0] i := t.index0[c0]
c1 := s[1] c1 := s[1]
if c1 < tx || t2 <= c1 { if c1 < tx || t2 <= c1 {
return 0, 1 return 0, 1
......
...@@ -89,18 +89,18 @@ var testValues = [832]uint32{ ...@@ -89,18 +89,18 @@ var testValues = [832]uint32{
} }
var testLookup = [640]uint16{ var testLookup = [640]uint16{
0x0c2: 0x01, 0x0c4: 0x02, 0x0e0: 0x05, 0x0e6: 0x06,
0x0c8: 0x03, 0x13f: 0x07,
0x0df: 0x04, 0x140: 0x08, 0x144: 0x09,
0x0e0: 0x02, 0x190: 0x03,
0x0ef: 0x03, 0x1ff: 0x0a,
0x0f0: 0x05, 0x0f4: 0x07, 0x20f: 0x05,
0x120: 0x05, 0x126: 0x06, 0x242: 0x01, 0x244: 0x02,
0x17f: 0x07, 0x248: 0x03,
0x180: 0x08, 0x184: 0x09, 0x25f: 0x04,
0x1d0: 0x04, 0x260: 0x01,
0x23f: 0x0a, 0x26f: 0x02,
0x24f: 0x06, 0x270: 0x04, 0x274: 0x06,
} }
var testTrie = trie{testLookup[:], testValues[:]} var testTrie = trie{testLookup[6*blockSize:], testValues[:], testLookup[:], testValues[:]}
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