Skip to content

Commit

Permalink
Whitelist recursive deletion to avoid extra allocs
Browse files Browse the repository at this point in the history
Add benchmark test for deeper and broader whitelists.
  • Loading branch information
dhontecillas committed Jan 15, 2018
1 parent 4eae11b commit e7dd1eb
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 33 deletions.
64 changes: 31 additions & 33 deletions proxy/formatter.go
Expand Up @@ -80,47 +80,45 @@ func extractTarget(target string, entity *Response) {
}
}

func whitelistPrune(wlDict map[string]interface{}, inDict map[string]interface{}) bool {
canDelete := true
var deleteSibling bool
for k, v := range inDict {
deleteSibling = true
if subWl, ok := wlDict[k]; ok {
if subWlDict, okk := subWl.(map[string]interface{}); okk {
if subInDict, isDict := v.(map[string]interface{}); isDict && !whitelistPrune(subWlDict, subInDict) {
deleteSibling = false
}
} else {
// whitelist leaf, maintain this branch
deleteSibling = false
}
}
if deleteSibling {
delete(inDict, k)
} else {
canDelete = false
}
}
return canDelete
}

func newWhitelistingFilter(whitelist []string) propertyFilter {
numFields := 0
wlDict := make(map[string]interface{})
for _, k := range whitelist {
numFields += len(strings.Split(k, "."))
}
wlIndices := make([]int, len(whitelist))
wlFields := make([]string, numFields)
fIdx := 0
for wIdx, k := range whitelist {
for _, key := range strings.Split(k, ".") {
wlFields[fIdx] = key
fIdx++
}
wlIndices[wIdx] = fIdx
wlFields := strings.Split(k, ".")
d := buildDictPath(wlDict, wlFields[:len(wlFields)-1])
d[wlFields[len(wlFields)-1]] = true
}

return func(entity *Response) {
accumulator := make(map[string]interface{}, len(whitelist))
start := 0
for _, end := range wlIndices {
dEnd := end - 1
p := findDictPath(entity.Data, wlFields[start:dEnd])
if value, ok := p[wlFields[dEnd]]; ok {
d := buildDictPath(accumulator, wlFields[start:dEnd])
d[wlFields[dEnd]] = value
if whitelistPrune(wlDict, entity.Data) {
for k := range entity.Data {
delete(entity.Data, k)
}
start = end
}
*entity = Response{Data: accumulator, IsComplete: entity.IsComplete}
}
}

func findDictPath(root map[string]interface{}, fields []string) map[string]interface{} {
ok := true
p := root
for _, field := range fields {
if p, ok = p[field].(map[string]interface{}); !ok {
return nil
}
}
return p
}

func buildDictPath(accumulator map[string]interface{}, fields []string) map[string]interface{} {
Expand Down
59 changes: 59 additions & 0 deletions proxy/formatter_benchmark_test.go
@@ -1,6 +1,7 @@
package proxy

import (
"bytes"
"fmt"
"testing"
)
Expand Down Expand Up @@ -41,6 +42,64 @@ func BenchmarkEntityFormatter_whitelistingFilter(b *testing.B) {

}

func benchmarkDeepChilds(depth int, extraSiblings int) map[string]interface{} {
data := make(map[string]interface{}, extraSiblings+1)
for i := 0; i < extraSiblings; i++ {
data[fmt.Sprintf("extra%d", i)] = "sibling_value"
}
if depth > 0 {
data[fmt.Sprintf("child%d", depth)] = benchmarkDeepChilds(depth-1, extraSiblings)
} else {
data["child0"] = 1
}
return data
}

func benchmarkDeepStructure(numTargets int, targetDepth int, extraFields int, extraSiblings int) (map[string]interface{}, []string) {
data := make(map[string]interface{}, numTargets+extraFields)
target_keys := make([]string, numTargets)
for i := 0; i < numTargets; i++ {
data[fmt.Sprintf("target%d", i)] = benchmarkDeepChilds(targetDepth-1, extraSiblings)
}
for j := 0; j < extraFields; j++ {
data[fmt.Sprintf("extra%d", j)] = benchmarkDeepChilds(targetDepth-1, extraSiblings)
}
// create the target list
for i := 0; i < numTargets; i++ {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("target%d", i))
for j := targetDepth - 1; j >= 0; j-- {
buffer.WriteString(fmt.Sprintf(".child%d", j))
}
target_keys[i] = buffer.String()
}
return data, target_keys
}

func BenchmarkEntityFormatter_deepWhitelistingFilter(b *testing.B) {
numTargets := []int{0, 1, 2, 5, 10}
depths := []int{1, 3, 7}
for _, nTargets := range numTargets {
for _, depth := range depths {
extraFields := nTargets + depth*2
extraSiblings := nTargets
data, whitelist := benchmarkDeepStructure(nTargets, depth, extraFields, extraSiblings)
sample := Response{
Data: data,
IsComplete: true,
}
f := NewEntityFormatter("", whitelist, []string{}, "", map[string]string{})
b.Run(fmt.Sprintf("numTargets:%d,depth:%d,extraFields:%d,extraSiblings:%d", nTargets, depth, extraFields, extraSiblings), func(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
f.Format(sample)
}
})
}
}
}

func BenchmarkEntityFormatter_blacklistingFilter(b *testing.B) {
data := map[string]interface{}{
"supu": 42,
Expand Down

0 comments on commit e7dd1eb

Please sign in to comment.