Restore performance for the simple case

```
BenchmarkGetBool-4            1021          481           -52.89%
BenchmarkGet-4                879           403           -54.15%
BenchmarkGetBoolFromMap-4     6.56          6.40          -2.44%

benchmark                     old allocs     new allocs     delta
BenchmarkGetBool-4            6              4              -33.33%
BenchmarkGet-4                6              4              -33.33%
BenchmarkGetBoolFromMap-4     0              0              +0.00%

benchmark                     old bytes     new bytes     delta
BenchmarkGetBool-4            113           49            -56.64%
BenchmarkGet-4                112           48            -57.14%
BenchmarkGetBoolFromMap-4     0             0             +0.00%
```

Fixes #249
Fixes https://github.com/spf13/hugo/issues/2536
This commit is contained in:
Bjørn Erik Pedersen 2016-10-10 13:40:38 +02:00 committed by GitHub
parent 54b81535af
commit 51f23d1f1c
2 changed files with 133 additions and 40 deletions

View file

@ -399,17 +399,42 @@ func (v *Viper) providerPathExists(p *defaultRemoteProvider) bool {
return false return false
} }
// searchMapForKey may end up traversing the map if the key references a nested
// item (foo.bar), but will use a fast path for the common case.
// Note: This assumes that the key given is already lowercase.
func (v *Viper) searchMapForKey(source map[string]interface{}, lcaseKey string) interface{} {
if !strings.Contains(lcaseKey, v.keyDelim) {
v, ok := source[lcaseKey]
if ok {
return v
}
return nil
}
path := strings.Split(lcaseKey, v.keyDelim)
return v.searchMap(source, path)
}
// searchMap recursively searches for a value for path in source map. // searchMap recursively searches for a value for path in source map.
// Returns nil if not found. // Returns nil if not found.
// Note: This assumes that the path entries are lower cased.
func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} { func (v *Viper) searchMap(source map[string]interface{}, path []string) interface{} {
if len(path) == 0 { if len(path) == 0 {
return source return source
} }
// Fast path
if len(path) == 1 {
if v, ok := source[path[0]]; ok {
return v
}
return nil
}
var ok bool var ok bool
var next interface{} var next interface{}
for k, v := range source { for k, v := range source {
if strings.ToLower(k) == strings.ToLower(path[0]) { if k == path[0] {
ok = true ok = true
next = v next = v
break break
@ -594,8 +619,8 @@ func (v *Viper) Get(key string) interface{} {
valType := val valType := val
if v.typeByDefValue { if v.typeByDefValue {
path := strings.Split(lcaseKey, v.keyDelim) // TODO(bep) this branch isn't covered by a single test.
defVal := v.searchMap(v.defaults, path) defVal := v.searchMapForKey(v.defaults, lcaseKey)
if defVal != nil { if defVal != nil {
valType = defVal valType = defVal
} }
@ -841,32 +866,39 @@ func (v *Viper) BindEnv(input ...string) error {
// Viper will check in the following order: // Viper will check in the following order:
// flag, env, config file, key/value store, default. // flag, env, config file, key/value store, default.
// Viper will check to see if an alias exists first. // Viper will check to see if an alias exists first.
func (v *Viper) find(key string) interface{} { // Note: this assumes a lower-cased key given.
var val interface{} func (v *Viper) find(lcaseKey string) interface{} {
var exists bool
var (
val interface{}
exists bool
path = strings.Split(lcaseKey, v.keyDelim)
nested = len(path) > 1
)
// compute the path through the nested maps to the nested value // compute the path through the nested maps to the nested value
path := strings.Split(key, v.keyDelim) if nested && v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)) != "" {
if shadow := v.isPathShadowedInDeepMap(path, castMapStringToMapInterface(v.aliases)); shadow != "" {
return nil return nil
} }
// if the requested key is an alias, then return the proper key // if the requested key is an alias, then return the proper key
key = v.realKey(key) lcaseKey = v.realKey(lcaseKey)
// re-compute the path
path = strings.Split(key, v.keyDelim)
// Set() override first // Set() override first
val = v.searchMap(v.override, path) val = v.searchMapForKey(v.override, lcaseKey)
if val != nil { if val != nil {
return val return val
} }
if shadow := v.isPathShadowedInDeepMap(path, v.override); shadow != "" {
path = strings.Split(lcaseKey, v.keyDelim)
nested = len(path) > 1
if nested && v.isPathShadowedInDeepMap(path, v.override) != "" {
return nil return nil
} }
// PFlag override next // PFlag override next
flag, exists := v.pflags[key] flag, exists := v.pflags[lcaseKey]
if exists && flag.HasChanged() { if exists && flag.HasChanged() {
switch flag.ValueType() { switch flag.ValueType() {
case "int", "int8", "int16", "int32", "int64": case "int", "int8", "int16", "int32", "int64":
@ -880,7 +912,8 @@ func (v *Viper) find(key string) interface{} {
return flag.ValueString() return flag.ValueString()
} }
} }
if shadow := v.isPathShadowedInFlatMap(path, v.pflags); shadow != "" {
if nested && v.isPathShadowedInFlatMap(path, v.pflags) != "" {
return nil return nil
} }
@ -888,14 +921,14 @@ func (v *Viper) find(key string) interface{} {
if v.automaticEnvApplied { if v.automaticEnvApplied {
// even if it hasn't been registered, if automaticEnv is used, // even if it hasn't been registered, if automaticEnv is used,
// check any Get request // check any Get request
if val = v.getEnv(v.mergeWithEnvPrefix(key)); val != "" { if val = v.getEnv(v.mergeWithEnvPrefix(lcaseKey)); val != "" {
return val return val
} }
if shadow := v.isPathShadowedInAutoEnv(path); shadow != "" { if nested && v.isPathShadowedInAutoEnv(path) != "" {
return nil return nil
} }
} }
envkey, exists := v.env[key] envkey, exists := v.env[lcaseKey]
if exists { if exists {
if val = v.getEnv(envkey); val != "" { if val = v.getEnv(envkey); val != "" {
return val return val
@ -934,7 +967,7 @@ func (v *Viper) find(key string) interface{} {
// last chance: if no other value is returned and a flag does exist for the value, // last chance: if no other value is returned and a flag does exist for the value,
// get the flag's value even if the flag's value has not changed // get the flag's value even if the flag's value has not changed
if flag, exists := v.pflags[key]; exists { if flag, exists := v.pflags[lcaseKey]; exists {
switch flag.ValueType() { switch flag.ValueType() {
case "int", "int8", "int16", "int32", "int64": case "int", "int8", "int16", "int32", "int64":
return cast.ToInt(flag.ValueString()) return cast.ToInt(flag.ValueString())

View file

@ -18,6 +18,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/spf13/cast"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -131,12 +133,18 @@ func initConfigs() {
unmarshalReader(remote, v.kvstore) unmarshalReader(remote, v.kvstore)
} }
func initYAML() { func initConfig(typ, config string) {
Reset() Reset()
SetConfigType("yaml") SetConfigType(typ)
r := bytes.NewReader(yamlExample) r := strings.NewReader(config)
unmarshalReader(r, v.config) if err := unmarshalReader(r, v.config); err != nil {
panic(err)
}
}
func initYAML() {
initConfig("yaml", string(yamlExample))
} }
func initJSON() { func initJSON() {
@ -435,13 +443,8 @@ func TestAllKeys(t *testing.T) {
assert.Equal(t, all, AllSettings()) assert.Equal(t, all, AllSettings())
} }
func TestCaseInSensitive(t *testing.T) {
assert.Equal(t, true, Get("hacker"))
Set("Title", "Checking Case")
assert.Equal(t, "Checking Case", Get("tItle"))
}
func TestAliasesOfAliases(t *testing.T) { func TestAliasesOfAliases(t *testing.T) {
Set("Title", "Checking Case")
RegisterAlias("Foo", "Bar") RegisterAlias("Foo", "Bar")
RegisterAlias("Bar", "Title") RegisterAlias("Bar", "Title")
assert.Equal(t, "Checking Case", Get("FOO")) assert.Equal(t, "Checking Case", Get("FOO"))
@ -538,7 +541,6 @@ func TestBindPFlag(t *testing.T) {
} }
func TestBoundCaseSensitivity(t *testing.T) { func TestBoundCaseSensitivity(t *testing.T) {
assert.Equal(t, "brown", Get("eyes")) assert.Equal(t, "brown", Get("eyes"))
BindEnv("eYEs", "TURTLE_EYES") BindEnv("eYEs", "TURTLE_EYES")
@ -917,8 +919,19 @@ func TestSetConfigNameClearsFileCache(t *testing.T) {
} }
func TestShadowedNestedValue(t *testing.T) { func TestShadowedNestedValue(t *testing.T) {
config := `name: steve
clothing:
jacket: leather
trousers: denim
pants:
size: large
`
initConfig("yaml", config)
assert.Equal(t, "steve", GetString("name"))
polyester := "polyester" polyester := "polyester"
initYAML()
SetDefault("clothing.shirt", polyester) SetDefault("clothing.shirt", polyester)
SetDefault("clothing.jacket.price", 100) SetDefault("clothing.jacket.price", 100)
@ -942,18 +955,65 @@ func TestDotParameter(t *testing.T) {
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
func TestGetBool(t *testing.T) { func TestCaseInSensitive(t *testing.T) {
key := "BooleanKey" for _, config := range []struct {
v = New() typ string
v.Set(key, true) content string
if !v.GetBool(key) { }{
t.Fatal("GetBool returned false") {"yaml", `
} aBcD: 1
if v.GetBool("NotFound") { eF:
t.Fatal("GetBool returned true") gH: 2
iJk: 3
Lm:
nO: 4
P:
Q: 5
R: 6
`},
{"json", `{
"aBcD": 1,
"eF": {
"iJk": 3,
"Lm": {
"P": {
"Q": 5,
"R": 6
},
"nO": 4
},
"gH": 2
}
}`},
{"toml", `aBcD = 1
[eF]
gH = 2
iJk = 3
[eF.Lm]
nO = 4
[eF.Lm.P]
Q = 5
R = 6
`},
} {
doTestCaseInSensitive(t, config.typ, config.content)
} }
} }
func doTestCaseInSensitive(t *testing.T, typ, config string) {
initConfig(typ, config)
Set("RfD", true)
assert.Equal(t, true, Get("rfd"))
assert.Equal(t, true, Get("rFD"))
assert.Equal(t, 1, cast.ToInt(Get("abcd")))
assert.Equal(t, 1, cast.ToInt(Get("Abcd")))
assert.Equal(t, 2, cast.ToInt(Get("ef.gh")))
assert.Equal(t, 3, cast.ToInt(Get("ef.ijk")))
assert.Equal(t, 4, cast.ToInt(Get("ef.lm.no")))
assert.Equal(t, 5, cast.ToInt(Get("ef.lm.p.q")))
}
func BenchmarkGetBool(b *testing.B) { func BenchmarkGetBool(b *testing.B) {
key := "BenchmarkGetBool" key := "BenchmarkGetBool"
v = New() v = New()