1
0
mirror of https://github.com/golang/go synced 2024-11-22 03:34:40 -07:00

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
This commit is contained in:
Marcel van Lohuizen 2011-09-16 11:27:05 +02:00
parent a399040226
commit 1913fdab98
6 changed files with 3377 additions and 3011 deletions

View File

@ -21,6 +21,7 @@ var testRunes = []int{
0x80, 0x100, 0x7FF, // 2-byte sequences 0x80, 0x100, 0x7FF, // 2-byte sequences
0x800, 0x999, 0xFFFF, // 3-byte sequences 0x800, 0x999, 0xFFFF, // 3-byte sequences
0x10000, 0x10101, 0x10FFFF, // 4-byte sequences 0x10000, 0x10101, 0x10FFFF, // 4-byte sequences
0x200, 0x201, 0x202, 0x210, 0x215, // five entries in one sparse block
} }
const fileHeader = `// Generated by running const fileHeader = `// Generated by running

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,44 @@
package norm package norm
type valueRange struct {
value uint16 // header: value:stride
lo, hi byte // header: lo:n
}
type trie struct { type trie struct {
index []uint8 index []uint8
values []uint16 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 ( const (
@ -44,8 +79,7 @@ func (t *trie) lookup(s []byte) (v uint16, sz int) {
if c1 < tx || t2 <= c1 { if c1 < tx || t2 <= c1 {
return 0, 1 return 0, 1
} }
o := uint16(i)<<6 + uint16(c1)&maskx return t.lookupValue(i, c1), 2
return t.values[o], 2
case c0 < t4: case c0 < t4:
if len(s) < 3 { if len(s) < 3 {
return 0, 0 return 0, 0
@ -61,8 +95,7 @@ func (t *trie) lookup(s []byte) (v uint16, sz int) {
if c2 < tx || t2 <= c2 { if c2 < tx || t2 <= c2 {
return 0, 2 return 0, 2
} }
o = uint16(i)<<6 + uint16(c2)&maskx return t.lookupValue(i, c2), 3
return t.values[o], 3
case c0 < t5: case c0 < t5:
if len(s) < 4 { if len(s) < 4 {
return 0, 0 return 0, 0
@ -84,8 +117,7 @@ func (t *trie) lookup(s []byte) (v uint16, sz int) {
if c3 < tx || t2 <= c3 { if c3 < tx || t2 <= c3 {
return 0, 3 return 0, 3
} }
o = uint16(i)<<6 + uint16(c3)&maskx return t.lookupValue(i, c3), 4
return t.values[o], 4
} }
// Illegal rune // Illegal rune
return 0, 1 return 0, 1
@ -110,8 +142,7 @@ func (t *trie) lookupString(s string) (v uint16, sz int) {
if c1 < tx || t2 <= c1 { if c1 < tx || t2 <= c1 {
return 0, 1 return 0, 1
} }
o := uint16(i)<<6 + uint16(c1)&maskx return t.lookupValue(i, c1), 2
return t.values[o], 2
case c0 < t4: case c0 < t4:
if len(s) < 3 { if len(s) < 3 {
return 0, 0 return 0, 0
@ -127,8 +158,7 @@ func (t *trie) lookupString(s string) (v uint16, sz int) {
if c2 < tx || t2 <= c2 { if c2 < tx || t2 <= c2 {
return 0, 2 return 0, 2
} }
o = uint16(i)<<6 + uint16(c2)&maskx return t.lookupValue(i, c2), 3
return t.values[o], 3
case c0 < t5: case c0 < t5:
if len(s) < 4 { if len(s) < 4 {
return 0, 0 return 0, 0
@ -150,8 +180,7 @@ func (t *trie) lookupString(s string) (v uint16, sz int) {
if c3 < tx || t2 <= c3 { if c3 < tx || t2 <= c3 {
return 0, 3 return 0, 3
} }
o = uint16(i)<<6 + uint16(c3)&maskx return t.lookupValue(i, c3), 4
return t.values[o], 4
} }
// Illegal rune // Illegal rune
return 0, 1 return 0, 1
@ -168,19 +197,16 @@ func (t *trie) lookupUnsafe(s []byte) uint16 {
return 0 return 0
} }
i := t.index[c0] i := t.index[c0]
o := uint16(i)<<6 + uint16(s[1])&maskx
if c0 < t3 { if c0 < t3 {
return t.values[o] return t.lookupValue(i, s[1])
} }
i = t.index[o] i = t.index[uint16(i)<<6+uint16(s[1])&maskx]
o = uint16(i)<<6 + uint16(s[2])&maskx
if c0 < t4 { if c0 < t4 {
return t.values[o] return t.lookupValue(i, s[2])
} }
i = t.index[o] i = t.index[uint16(i)<<6+uint16(s[2])&maskx]
o = uint16(i)<<6 + uint16(s[3])&maskx
if c0 < t5 { if c0 < t5 {
return t.values[o] return t.lookupValue(i, s[3])
} }
return 0 return 0
} }
@ -196,19 +222,16 @@ func (t *trie) lookupStringUnsafe(s string) uint16 {
return 0 return 0
} }
i := t.index[c0] i := t.index[c0]
o := uint16(i)<<6 + uint16(s[1])&maskx
if c0 < t3 { if c0 < t3 {
return t.values[o] return t.lookupValue(i, s[1])
} }
i = t.index[o] i = t.index[uint16(i)<<6+uint16(s[1])&maskx]
o = uint16(i)<<6 + uint16(s[2])&maskx
if c0 < t4 { if c0 < t4 {
return t.values[o] return t.lookupValue(i, s[2])
} }
i = t.index[o] i = t.index[uint16(i)<<6+uint16(s[2])&maskx]
o = uint16(i)<<6 + uint16(s[3])&maskx
if c0 < t5 { if c0 < t5 {
return t.values[o] return t.lookupValue(i, s[3])
} }
return 0 return 0
} }

View File

@ -8,6 +8,41 @@ import (
// Test data is located in triedata_test.go; generated by maketesttables. // Test data is located in triedata_test.go; generated by maketesttables.
var testdata = testdataTrie 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. // Test cases for illegal runes.
type trietest struct { type trietest struct {
size int size int
@ -49,10 +84,10 @@ func TestLookup(t *testing.T) {
b, szg := mkUtf8(tt) b, szg := mkUtf8(tt)
v, szt := testdata.lookup(b) v, szt := testdata.lookup(b)
if int(v) != i { 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 { 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 { for i, tt := range tests {

View File

@ -4,34 +4,55 @@
package norm 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. // Block 2 is the null block.
var testdataValues = [768]uint16{ var testdataValues = [192]uint16{
// Block 0x0, offset 0x0 // Block 0x0, offset 0x0
0x000c: 0x0001, 0x000c: 0x0001,
// Block 0x1, offset 0x40 // Block 0x1, offset 0x40
0x007f: 0x0002, 0x007f: 0x0002,
// Block 0x2, offset 0x80 // Block 0x2, offset 0x80
// Block 0x3, offset 0xc0 }
0x00c0: 0x0003,
// Block 0x4, offset 0x100 // testdataSparseOffset: 10 entries, 20 bytes
0x0100: 0x0004, var testdataSparseOffset = []uint16{0x0, 0x2, 0x4, 0x8, 0xa, 0xc, 0xe, 0x10, 0x12, 0x14}
// Block 0x5, offset 0x140
0x017f: 0x0005, // testdataSparseValues: 22 entries, 88 bytes
// Block 0x6, offset 0x180 var testdataSparseValues = [22]valueRange{
0x0180: 0x0006, // Block 0x0, offset 0x1
// Block 0x7, offset 0x1c0 {value: 0x0000, lo: 0x01},
0x01d9: 0x0007, {value: 0x0003, lo: 0x80, hi: 0x80},
// Block 0x8, offset 0x200 // Block 0x1, offset 0x2
0x023f: 0x0008, {value: 0x0000, lo: 0x01},
// Block 0x9, offset 0x240 {value: 0x0004, lo: 0x80, hi: 0x80},
0x0240: 0x0009, // Block 0x2, offset 0x3
// Block 0xa, offset 0x280 {value: 0x0001, lo: 0x03},
0x0281: 0x000a, {value: 0x000c, lo: 0x80, hi: 0x82},
// Block 0xb, offset 0x2c0 {value: 0x000f, lo: 0x90, hi: 0x90},
0x02ff: 0x000b, {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 // testdataLookup: 640 bytes
@ -42,22 +63,23 @@ var testdataLookup = [640]uint8{
// Block 0x2, offset 0x80 // Block 0x2, offset 0x80
// Block 0x3, offset 0xc0 // Block 0x3, offset 0xc0
0x0c2: 0x03, 0x0c4: 0x04, 0x0c2: 0x03, 0x0c4: 0x04,
0x0df: 0x05, 0x0c8: 0x05,
0x0df: 0x06,
0x0e0: 0x04, 0x0e0: 0x04,
0x0ef: 0x05, 0x0ef: 0x05,
0x0f0: 0x07, 0x0f4: 0x09, 0x0f0: 0x07, 0x0f4: 0x09,
// Block 0x4, offset 0x100 // Block 0x4, offset 0x100
0x120: 0x06, 0x126: 0x07, 0x120: 0x07, 0x126: 0x08,
// Block 0x5, offset 0x140 // Block 0x5, offset 0x140
0x17f: 0x08, 0x17f: 0x09,
// Block 0x6, offset 0x180 // Block 0x6, offset 0x180
0x180: 0x09, 0x184: 0x0a, 0x180: 0x0a, 0x184: 0x0b,
// Block 0x7, offset 0x1c0 // Block 0x7, offset 0x1c0
0x1d0: 0x06, 0x1d0: 0x06,
// Block 0x8, offset 0x200 // Block 0x8, offset 0x200
0x23f: 0x0b, 0x23f: 0x0c,
// Block 0x9, offset 0x240 // Block 0x9, offset 0x240
0x24f: 0x08, 0x24f: 0x08,
} }
var testdataTrie = trie{testdataLookup[:], testdataValues[:]} var testdataTrie = trie{testdataLookup[:], testdataValues[:], testdataSparseValues[:], testdataSparseOffset[:], 3}

View File

@ -17,10 +17,13 @@ import (
"utf8" "utf8"
) )
const blockSize = 64
const maxSparseEntries = 16
// Intermediate trie structure // Intermediate trie structure
type trieNode struct { type trieNode struct {
table [256]*trieNode table [256]*trieNode
value uint16 value int
b byte b byte
leaf bool leaf bool
} }
@ -53,6 +56,44 @@ func (n trieNode) isInternal() bool {
return internal 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) { func (n *trieNode) insert(rune int, value uint16) {
var p [utf8.UTFMax]byte var p [utf8.UTFMax]byte
sz := utf8.EncodeRune(p[:], rune) sz := utf8.EncodeRune(p[:], rune)
@ -69,35 +110,40 @@ func (n *trieNode) insert(rune int, value uint16) {
} }
n = nn n = nn
} }
n.value = value n.value = int(value)
n.leaf = true n.leaf = true
} }
type nodeIndex struct { type nodeIndex struct {
lookupBlocks []*trieNode lookupBlocks []*trieNode
valueBlocks []*trieNode valueBlocks []*trieNode
sparseBlocks []*trieNode
sparseOffset []uint16
sparseCount int
lookupBlockIdx map[uint32]uint16 lookupBlockIdx map[uint32]int
valueBlockIdx map[uint32]uint16 valueBlockIdx map[uint32]int
} }
func newIndex() *nodeIndex { func newIndex() *nodeIndex {
index := &nodeIndex{} index := &nodeIndex{}
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]uint16) index.sparseBlocks = make([]*trieNode, 0)
index.valueBlockIdx = make(map[uint32]uint16) index.sparseOffset = make([]uint16, 1)
index.lookupBlockIdx = make(map[uint32]int)
index.valueBlockIdx = make(map[uint32]int)
return index return index
} }
func computeOffsets(index *nodeIndex, n *trieNode) uint16 { func computeOffsets(index *nodeIndex, n *trieNode) int {
if n.leaf { if n.leaf {
return n.value return n.value
} }
hasher := crc32.New(crc32.MakeTable(crc32.IEEE)) hasher := crc32.New(crc32.MakeTable(crc32.IEEE))
// We only index continuation bytes. // We only index continuation bytes.
for i := 0; i < 64; i++ { for i := 0; i < blockSize; i++ {
var v uint16 = 0 v := 0
if nn := n.table[0x80+i]; nn != nil { if nn := n.table[0x80+i]; nn != nil {
v = computeOffsets(index, nn) v = computeOffsets(index, nn)
} }
@ -107,7 +153,7 @@ func computeOffsets(index *nodeIndex, n *trieNode) uint16 {
if n.isInternal() { if n.isInternal() {
v, ok := index.lookupBlockIdx[h] v, ok := index.lookupBlockIdx[h]
if !ok { if !ok {
v = uint16(len(index.lookupBlocks)) v = len(index.lookupBlocks)
index.lookupBlocks = append(index.lookupBlocks, n) index.lookupBlocks = append(index.lookupBlocks, n)
index.lookupBlockIdx[h] = v index.lookupBlockIdx[h] = v
} }
@ -115,9 +161,17 @@ func computeOffsets(index *nodeIndex, n *trieNode) uint16 {
} else { } else {
v, ok := index.valueBlockIdx[h] v, ok := index.valueBlockIdx[h]
if !ok { if !ok {
v = uint16(len(index.valueBlocks)) if c := n.countSparseEntries(); c > maxSparseEntries {
v = len(index.valueBlocks)
index.valueBlocks = append(index.valueBlocks, n) index.valueBlocks = append(index.valueBlocks, n)
index.valueBlockIdx[h] = v 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 n.value = v
} }
@ -125,14 +179,14 @@ func computeOffsets(index *nodeIndex, n *trieNode) uint16 {
} }
func printValueBlock(nr int, n *trieNode, offset int) { func printValueBlock(nr int, n *trieNode, offset int) {
boff := nr * 64 boff := nr * blockSize
fmt.Printf("\n// Block %#x, offset %#x", nr, boff) fmt.Printf("\n// Block %#x, offset %#x", nr, boff)
var printnewline bool var printnewline bool
for i := 0; i < 64; i++ { for i := 0; i < blockSize; i++ {
if i%6 == 0 { if i%6 == 0 {
printnewline = true printnewline = true
} }
v := uint16(0) v := 0
if nn := n.table[i+offset]; nn != nil { if nn := n.table[i+offset]; nn != nil {
v = nn.value v = nn.value
} }
@ -141,24 +195,55 @@ func printValueBlock(nr int, n *trieNode, offset int) {
fmt.Printf("\n") fmt.Printf("\n")
printnewline = false 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) { func printSparseBlock(nr int, n *trieNode) {
boff := nr * 64 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) fmt.Printf("\n// Block %#x, offset %#x", nr, boff)
var printnewline bool var printnewline bool
for i := 0; i < 64; i++ { for i := 0; i < blockSize; i++ {
if i%8 == 0 { if i%8 == 0 {
printnewline = true printnewline = true
} }
v := uint16(0) v := 0
if nn := n.table[i+offset]; nn != nil { if nn := n.table[i+offset]; nn != nil {
v = nn.value v = nn.value
} }
if v != 0 { if v != 0 {
if v < 0 {
v = -v - 1 + cutoff
}
if printnewline { if printnewline {
fmt.Printf("\n") fmt.Printf("\n")
printnewline = false 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("// %sValues: %d entries, %d bytes\n", name, nv, nv*2)
fmt.Printf("// Block 2 is the null block.\n") fmt.Printf("// Block 2 is the null block.\n")
fmt.Printf("var %sValues = [%d]uint16 {", name, nv) fmt.Printf("var %sValues = [%d]uint16 {", name, nv)
@ -194,18 +279,32 @@ func (t *trieNode) printTables(name string) int {
} }
fmt.Print("\n}\n\n") 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("// %sLookup: %d bytes\n", name, ni)
fmt.Printf("// Block 0 is the null block.\n") fmt.Printf("// Block 0 is the null block.\n")
fmt.Printf("var %sLookup = [%d]uint8 {", name, ni) fmt.Printf("var %sLookup = [%d]uint8 {", name, ni)
printLookupBlock(0, newNode(), 0) printLookupBlock(0, newNode(), 0, cutoff)
printLookupBlock(1, newNode(), 0) printLookupBlock(1, newNode(), 0, cutoff)
printLookupBlock(2, newNode(), 0) printLookupBlock(2, newNode(), 0, cutoff)
printLookupBlock(3, t, 0xC0) printLookupBlock(3, t, 0xC0, cutoff)
for i := 4; i < len(index.lookupBlocks); i++ { 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.Print("\n}\n\n")
fmt.Printf("var %sTrie = trie{ %sLookup[:], %sValues[:] }\n\n", name, name, name) fmt.Printf("var %sTrie = trie{ %sLookup[:], %sValues[:], %sSparseValues[:], %sSparseOffset[:], %d}\n\n",
return nv*2 + ni name, name, name, name, name, cutoff)
return nv*2 + ns*4 + ni + ls*2
} }