Merge pull request #3992 from svanharmelen/f-change-sets

core: change set internals and make (extreme) performance improvements
This commit is contained in:
Paul Hinze 2015-12-04 09:03:43 -06:00
commit f1e7cec566
8 changed files with 62 additions and 76 deletions

View File

@ -18,10 +18,12 @@ type ConfigFieldReader struct {
Config *terraform.ResourceConfig Config *terraform.ResourceConfig
Schema map[string]*Schema Schema map[string]*Schema
lock sync.Mutex indexMaps map[string]map[string]int
once sync.Once
} }
func (r *ConfigFieldReader) ReadField(address []string) (FieldReadResult, error) { func (r *ConfigFieldReader) ReadField(address []string) (FieldReadResult, error) {
r.once.Do(func() { r.indexMaps = make(map[string]map[string]int) })
return r.readField(address, false) return r.readField(address, false)
} }
@ -55,20 +57,18 @@ func (r *ConfigFieldReader) readField(
continue continue
} }
// Get the code indexMap, ok := r.indexMaps[strings.Join(address[:i+1], ".")]
code, err := strconv.ParseInt(address[i+1], 0, 0) if !ok {
if err != nil {
return FieldReadResult{}, err
}
// Get the set so we can get the index map that tells us the // Get the set so we can get the index map that tells us the
// mapping of the hash code to the list index // mapping of the hash code to the list index
_, indexMap, err := r.readSet(address[:i+1], v) _, err := r.readSet(address[:i+1], v)
if err != nil { if err != nil {
return FieldReadResult{}, err return FieldReadResult{}, err
} }
indexMap = r.indexMaps[strings.Join(address[:i+1], ".")]
}
index, ok := indexMap[int(code)] index, ok := indexMap[address[i+1]]
if !ok { if !ok {
return FieldReadResult{}, nil return FieldReadResult{}, nil
} }
@ -87,8 +87,7 @@ func (r *ConfigFieldReader) readField(
case TypeMap: case TypeMap:
return r.readMap(k) return r.readMap(k)
case TypeSet: case TypeSet:
result, _, err := r.readSet(address, schema) return r.readSet(address, schema)
return result, err
case typeObject: case typeObject:
return readObjectField( return readObjectField(
&nestedConfigFieldReader{r}, &nestedConfigFieldReader{r},
@ -112,7 +111,7 @@ func (r *ConfigFieldReader) readMap(k string) (FieldReadResult, error) {
switch m := mraw.(type) { switch m := mraw.(type) {
case []interface{}: case []interface{}:
for i, innerRaw := range m { for i, innerRaw := range m {
for ik, _ := range innerRaw.(map[string]interface{}) { for ik := range innerRaw.(map[string]interface{}) {
key := fmt.Sprintf("%s.%d.%s", k, i, ik) key := fmt.Sprintf("%s.%d.%s", k, i, ik)
if r.Config.IsComputed(key) { if r.Config.IsComputed(key) {
computed = true computed = true
@ -125,7 +124,7 @@ func (r *ConfigFieldReader) readMap(k string) (FieldReadResult, error) {
} }
case []map[string]interface{}: case []map[string]interface{}:
for i, innerRaw := range m { for i, innerRaw := range m {
for ik, _ := range innerRaw { for ik := range innerRaw {
key := fmt.Sprintf("%s.%d.%s", k, i, ik) key := fmt.Sprintf("%s.%d.%s", k, i, ik)
if r.Config.IsComputed(key) { if r.Config.IsComputed(key) {
computed = true computed = true
@ -137,7 +136,7 @@ func (r *ConfigFieldReader) readMap(k string) (FieldReadResult, error) {
} }
} }
case map[string]interface{}: case map[string]interface{}:
for ik, _ := range m { for ik := range m {
key := fmt.Sprintf("%s.%s", k, ik) key := fmt.Sprintf("%s.%s", k, ik)
if r.Config.IsComputed(key) { if r.Config.IsComputed(key) {
computed = true computed = true
@ -198,17 +197,17 @@ func (r *ConfigFieldReader) readPrimitive(
} }
func (r *ConfigFieldReader) readSet( func (r *ConfigFieldReader) readSet(
address []string, schema *Schema) (FieldReadResult, map[int]int, error) { address []string, schema *Schema) (FieldReadResult, error) {
indexMap := make(map[int]int) indexMap := make(map[string]int)
// Create the set that will be our result // Create the set that will be our result
set := schema.ZeroValue().(*Set) set := schema.ZeroValue().(*Set)
raw, err := readListField(&nestedConfigFieldReader{r}, address, schema) raw, err := readListField(&nestedConfigFieldReader{r}, address, schema)
if err != nil { if err != nil {
return FieldReadResult{}, indexMap, err return FieldReadResult{}, err
} }
if !raw.Exists { if !raw.Exists {
return FieldReadResult{Value: set}, indexMap, nil return FieldReadResult{Value: set}, nil
} }
// If the list is computed, the set is necessarilly computed // If the list is computed, the set is necessarilly computed
@ -217,7 +216,7 @@ func (r *ConfigFieldReader) readSet(
Value: set, Value: set,
Exists: true, Exists: true,
Computed: raw.Computed, Computed: raw.Computed,
}, indexMap, nil }, nil
} }
// Build up the set from the list elements // Build up the set from the list elements
@ -226,19 +225,16 @@ func (r *ConfigFieldReader) readSet(
computed := r.hasComputedSubKeys( computed := r.hasComputedSubKeys(
fmt.Sprintf("%s.%d", strings.Join(address, "."), i), schema) fmt.Sprintf("%s.%d", strings.Join(address, "."), i), schema)
code := set.add(v) code := set.add(v, computed)
indexMap[code] = i indexMap[code] = i
if computed {
set.m[-code] = set.m[code]
delete(set.m, code)
code = -code
}
} }
r.indexMaps[strings.Join(address, ".")] = indexMap
return FieldReadResult{ return FieldReadResult{
Value: set, Value: set,
Exists: true, Exists: true,
}, indexMap, nil }, nil
} }
// hasComputedSubKeys walks through a schema and returns whether or not the // hasComputedSubKeys walks through a schema and returns whether or not the

View File

@ -228,8 +228,8 @@ func TestConfigFieldReader_ComputedSet(t *testing.T) {
"set, normal": { "set, normal": {
[]string{"strSet"}, []string{"strSet"},
FieldReadResult{ FieldReadResult{
Value: map[int]interface{}{ Value: map[string]interface{}{
2356372769: "foo", "2356372769": "foo",
}, },
Exists: true, Exists: true,
Computed: false, Computed: false,

View File

@ -298,8 +298,7 @@ func (w *MapFieldWriter) setSet(
} }
for code, elem := range value.(*Set).m { for code, elem := range value.(*Set).m {
codeStr := strconv.FormatInt(int64(code), 10) if err := w.set(append(addrCopy, code), elem); err != nil {
if err := w.set(append(addrCopy, codeStr), elem); err != nil {
return err return err
} }
} }

View File

@ -228,7 +228,7 @@ func (d *ResourceData) State() *terraform.InstanceState {
// attribute set as a map[string]interface{}, write it to a MapFieldWriter, // attribute set as a map[string]interface{}, write it to a MapFieldWriter,
// and then use that map. // and then use that map.
rawMap := make(map[string]interface{}) rawMap := make(map[string]interface{})
for k, _ := range d.schema { for k := range d.schema {
source := getSourceSet source := getSourceSet
if d.partial { if d.partial {
source = getSourceState source = getSourceState
@ -343,13 +343,13 @@ func (d *ResourceData) diffChange(
} }
func (d *ResourceData) getChange( func (d *ResourceData) getChange(
key string, k string,
oldLevel getSource, oldLevel getSource,
newLevel getSource) (getResult, getResult) { newLevel getSource) (getResult, getResult) {
var parts, parts2 []string var parts, parts2 []string
if key != "" { if k != "" {
parts = strings.Split(key, ".") parts = strings.Split(k, ".")
parts2 = strings.Split(key, ".") parts2 = strings.Split(k, ".")
} }
o := d.get(parts, oldLevel) o := d.get(parts, oldLevel)
@ -374,13 +374,6 @@ func (d *ResourceData) get(addr []string, source getSource) getResult {
level = "state" level = "state"
} }
// Build the address of the key we're looking for and ask the FieldReader
for i, v := range addr {
if v[0] == '~' {
addr[i] = v[1:]
}
}
var result FieldReadResult var result FieldReadResult
var err error var err error
if exact { if exact {

View File

@ -1509,9 +1509,9 @@ func TestResourceDataSet(t *testing.T) {
Key: "ports", Key: "ports",
Value: &Set{ Value: &Set{
m: map[int]interface{}{ m: map[string]interface{}{
1: 1, "1": 1,
2: 2, "2": 2,
}, },
}, },
@ -1546,7 +1546,7 @@ func TestResourceDataSet(t *testing.T) {
Err: true, Err: true,
GetKey: "ports", GetKey: "ports",
GetValue: []interface{}{80, 100}, GetValue: []interface{}{100, 80},
}, },
// #11: Set with nested set // #11: Set with nested set

View File

@ -866,23 +866,16 @@ func (m schemaMap) diffSet(
// Build the list of codes that will make up our set. This is the // Build the list of codes that will make up our set. This is the
// removed codes as well as all the codes in the new codes. // removed codes as well as all the codes in the new codes.
codes := make([][]int, 2) codes := make([][]string, 2)
codes[0] = os.Difference(ns).listCode() codes[0] = os.Difference(ns).listCode()
codes[1] = ns.listCode() codes[1] = ns.listCode()
for _, list := range codes { for _, list := range codes {
for _, code := range list { for _, code := range list {
// If the code is negative (first character is -) then
// replace it with "~" for our computed set stuff.
codeStr := strconv.Itoa(code)
if codeStr[0] == '-' {
codeStr = string('~') + codeStr[1:]
}
switch t := schema.Elem.(type) { switch t := schema.Elem.(type) {
case *Resource: case *Resource:
// This is a complex resource // This is a complex resource
for k2, schema := range t.Schema { for k2, schema := range t.Schema {
subK := fmt.Sprintf("%s.%s.%s", k, codeStr, k2) subK := fmt.Sprintf("%s.%s.%s", k, code, k2)
err := m.diff(subK, schema, diff, d, true) err := m.diff(subK, schema, diff, d, true)
if err != nil { if err != nil {
return err return err
@ -896,7 +889,7 @@ func (m schemaMap) diffSet(
// This is just a primitive element, so go through each and // This is just a primitive element, so go through each and
// just diff each. // just diff each.
subK := fmt.Sprintf("%s.%s", k, codeStr) subK := fmt.Sprintf("%s.%s", k, code)
err := m.diff(subK, &t2, diff, d, true) err := m.diff(subK, &t2, diff, d, true)
if err != nil { if err != nil {
return err return err

View File

@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"reflect" "reflect"
"sort" "sort"
"strconv"
"sync" "sync"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
@ -43,7 +44,7 @@ func HashSchema(schema *Schema) SchemaSetFunc {
type Set struct { type Set struct {
F SchemaSetFunc F SchemaSetFunc
m map[int]interface{} m map[string]interface{}
once sync.Once once sync.Once
} }
@ -65,7 +66,7 @@ func CopySet(otherSet *Set) *Set {
// Add adds an item to the set if it isn't already in the set. // Add adds an item to the set if it isn't already in the set.
func (s *Set) Add(item interface{}) { func (s *Set) Add(item interface{}) {
s.add(item) s.add(item, false)
} }
// Remove removes an item if it's already in the set. Idempotent. // Remove removes an item if it's already in the set. Idempotent.
@ -157,13 +158,17 @@ func (s *Set) GoString() string {
} }
func (s *Set) init() { func (s *Set) init() {
s.m = make(map[int]interface{}) s.m = make(map[string]interface{})
} }
func (s *Set) add(item interface{}) int { func (s *Set) add(item interface{}, computed bool) string {
s.once.Do(s.init) s.once.Do(s.init)
code := s.hash(item) code := s.hash(item)
if computed {
code = "~" + code
}
if _, ok := s.m[code]; !ok { if _, ok := s.m[code]; !ok {
s.m[code] = item s.m[code] = item
} }
@ -171,34 +176,34 @@ func (s *Set) add(item interface{}) int {
return code return code
} }
func (s *Set) hash(item interface{}) int { func (s *Set) hash(item interface{}) string {
code := s.F(item) code := s.F(item)
// Always return a nonnegative hashcode. // Always return a nonnegative hashcode.
if code < 0 { if code < 0 {
return -code code = -code
} }
return code return strconv.Itoa(code)
} }
func (s *Set) remove(item interface{}) int { func (s *Set) remove(item interface{}) string {
s.once.Do(s.init) s.once.Do(s.init)
code := s.F(item) code := s.hash(item)
delete(s.m, code) delete(s.m, code)
return code return code
} }
func (s *Set) index(item interface{}) int { func (s *Set) index(item interface{}) int {
return sort.SearchInts(s.listCode(), s.hash(item)) return sort.SearchStrings(s.listCode(), s.hash(item))
} }
func (s *Set) listCode() []int { func (s *Set) listCode() []string {
// Sort the hash codes so the order of the list is deterministic // Sort the hash codes so the order of the list is deterministic
keys := make([]int, 0, len(s.m)) keys := make([]string, 0, len(s.m))
for k, _ := range s.m { for k := range s.m {
keys = append(keys, k) keys = append(keys, k)
} }
sort.Sort(sort.IntSlice(keys)) sort.Sort(sort.StringSlice(keys))
return keys return keys
} }

View File

@ -11,7 +11,7 @@ func TestSetAdd(t *testing.T) {
s.Add(5) s.Add(5)
s.Add(25) s.Add(25)
expected := []interface{}{1, 5, 25} expected := []interface{}{1, 25, 5}
actual := s.List() actual := s.List()
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual) t.Fatalf("bad: %#v", actual)
@ -101,7 +101,7 @@ func TestSetUnion(t *testing.T) {
union := s1.Union(s2) union := s1.Union(s2)
union.Add(2) union.Add(2)
expected := []interface{}{1, 2, 5, 25} expected := []interface{}{1, 2, 25, 5}
actual := union.List() actual := union.List()
if !reflect.DeepEqual(actual, expected) { if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual) t.Fatalf("bad: %#v", actual)