tpl/collections: Use MapRange/SetIterKey/SetIterValue for Where, Sort and Merge

Some relevant benchmarks:

Where with maps:

```
cpu: Apple M1 Pro
            │ master.bench │         fix-mapkeys.bench          │
            │    sec/op    │   sec/op     vs base               │
WhereMap-10    79.26µ ± 1%   26.58µ ± 1%  -66.46% (p=0.002 n=6)

            │ master.bench │         fix-mapkeys.bench         │
            │     B/op     │    B/op     vs base               │
WhereMap-10   56685.0 ± 0%   111.0 ± 1%  -99.80% (p=0.002 n=6)

            │ master.bench  │         fix-mapkeys.bench         │
            │   allocs/op   │ allocs/op   vs base               │
WhereMap-10   2003.000 ± 0%   4.000 ± 0%  -99.80% (p=0.002 n=6)
```

Merge:

```
         │ master.bench │         fix-mapkeys.bench          │
         │    sec/op    │   sec/op     vs base               │
Merge-10    3.285µ ± 0%   2.268µ ± 1%  -30.96% (p=0.002 n=6)

         │ master.bench │          fix-mapkeys.bench          │
         │     B/op     │     B/op      vs base               │
Merge-10   3.079Ki ± 0%   1.891Ki ± 0%  -38.58% (p=0.002 n=6)

         │ master.bench │         fix-mapkeys.bench         │
         │  allocs/op   │ allocs/op   vs base               │
Merge-10     64.00 ± 0%   26.00 ± 0%  -59.38% (p=0.002 n=6)
```
Sort:

```
cpu: Apple M1 Pro
           │ master.bench │         fix-mapkeys.bench         │
           │    sec/op    │   sec/op     vs base              │
SortMap-10   1008.0n ± 1%   915.5n ± 0%  -9.18% (p=0.002 n=6)

           │ master.bench │         fix-mapkeys.bench         │
           │     B/op     │    B/op     vs base               │
SortMap-10     640.0 ± 0%   512.0 ± 0%  -20.00% (p=0.002 n=6)

           │ master.bench │        fix-mapkeys.bench         │
           │  allocs/op   │ allocs/op   vs base              │
SortMap-10     16.00 ± 0%   15.00 ± 0%  -6.25% (p=0.002 n=6)
```
This commit is contained in:
Bjørn Erik Pedersen
2025-01-12 17:58:11 +02:00
parent a2edf04c27
commit de7137cc35
3 changed files with 45 additions and 23 deletions

View File

@@ -75,9 +75,13 @@ func caseInsensitiveLookup(m, k reflect.Value) (reflect.Value, bool) {
return v, hreflect.IsTruthfulValue(v) return v, hreflect.IsTruthfulValue(v)
} }
for _, key := range m.MapKeys() { k2 := reflect.New(m.Type().Key()).Elem()
if strings.EqualFold(k.String(), key.String()) {
return m.MapIndex(key), true iter := m.MapRange()
for iter.Next() {
k2.SetIterKey(iter)
if strings.EqualFold(k.String(), k2.String()) {
return iter.Value(), true
} }
} }
@@ -90,17 +94,28 @@ func mergeMap(dst, src reflect.Value) reflect.Value {
// If the destination is Params, we must lower case all keys. // If the destination is Params, we must lower case all keys.
_, lowerCase := dst.Interface().(maps.Params) _, lowerCase := dst.Interface().(maps.Params)
k := reflect.New(dst.Type().Key()).Elem()
v := reflect.New(dst.Type().Elem()).Elem()
// Copy the destination map. // Copy the destination map.
for _, key := range dst.MapKeys() { iter := dst.MapRange()
v := dst.MapIndex(key) for iter.Next() {
out.SetMapIndex(key, v) k.SetIterKey(iter)
v.SetIterValue(iter)
out.SetMapIndex(k, v)
} }
// Add all keys in src not already in destination. // Add all keys in src not already in destination.
// Maps of the same type will be merged. // Maps of the same type will be merged.
for _, key := range src.MapKeys() { k = reflect.New(src.Type().Key()).Elem()
sv := src.MapIndex(key) sv := reflect.New(src.Type().Elem()).Elem()
dv, found := caseInsensitiveLookup(dst, key)
iter = src.MapRange()
for iter.Next() {
sv.SetIterValue(iter)
k.SetIterKey(iter)
dv, found := caseInsensitiveLookup(dst, k)
if found { if found {
// If both are the same map key type, merge. // If both are the same map key type, merge.
@@ -112,14 +127,15 @@ func mergeMap(dst, src reflect.Value) reflect.Value {
} }
if dve.Type().Key() == sve.Type().Key() { if dve.Type().Key() == sve.Type().Key() {
out.SetMapIndex(key, mergeMap(dve, sve)) out.SetMapIndex(k, mergeMap(dve, sve))
} }
} }
} else { } else {
if lowerCase && key.Kind() == reflect.String { kk := k
key = reflect.ValueOf(strings.ToLower(key.String())) if lowerCase && k.Kind() == reflect.String {
kk = reflect.ValueOf(strings.ToLower(k.String()))
} }
out.SetMapIndex(key, sv) out.SetMapIndex(kk, sv)
} }
} }

View File

@@ -99,18 +99,21 @@ func (ns *Namespace) Sort(ctx context.Context, l any, args ...any) (any, error)
} }
case reflect.Map: case reflect.Map:
keys := seqv.MapKeys()
for i := 0; i < seqv.Len(); i++ {
p.Pairs[i].Value = seqv.MapIndex(keys[i])
iter := seqv.MapRange()
i := 0
for iter.Next() {
key := iter.Key()
value := iter.Value()
p.Pairs[i].Value = value
if sortByField == "" { if sortByField == "" {
p.Pairs[i].Key = keys[i] p.Pairs[i].Key = key
} else if sortByField == "value" { } else if sortByField == "value" {
p.Pairs[i].Key = p.Pairs[i].Value p.Pairs[i].Key = p.Pairs[i].Value
} else { } else {
v := p.Pairs[i].Value v := p.Pairs[i].Value
var err error var err error
for i, elemName := range path { for j, elemName := range path {
v, err = evaluateSubElem(ctxv, v, elemName) v, err = evaluateSubElem(ctxv, v, elemName)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -120,12 +123,13 @@ func (ns *Namespace) Sort(ctx context.Context, l any, args ...any) (any, error)
} }
// Special handling of lower cased maps. // Special handling of lower cased maps.
if params, ok := v.Interface().(maps.Params); ok { if params, ok := v.Interface().(maps.Params); ok {
v = reflect.ValueOf(params.GetNested(path[i+1:]...)) v = reflect.ValueOf(params.GetNested(path[j+1:]...))
break break
} }
} }
p.Pairs[i].Key = v p.Pairs[i].Key = v
} }
i++
} }
} }

View File

@@ -409,7 +409,6 @@ func (ns *Namespace) checkWhereArray(ctxv, seqv, kv, mv reflect.Value, path []st
for i, elemName := range path { for i, elemName := range path {
var err error var err error
vvv, err = evaluateSubElem(ctxv, vvv, elemName) vvv, err = evaluateSubElem(ctxv, vvv, elemName)
if err != nil { if err != nil {
continue continue
} }
@@ -442,9 +441,12 @@ func (ns *Namespace) checkWhereArray(ctxv, seqv, kv, mv reflect.Value, path []st
// checkWhereMap handles the where-matching logic when the seqv value is a Map. // checkWhereMap handles the where-matching logic when the seqv value is a Map.
func (ns *Namespace) checkWhereMap(ctxv, seqv, kv, mv reflect.Value, path []string, op string) (any, error) { func (ns *Namespace) checkWhereMap(ctxv, seqv, kv, mv reflect.Value, path []string, op string) (any, error) {
rv := reflect.MakeMap(seqv.Type()) rv := reflect.MakeMap(seqv.Type())
keys := seqv.MapKeys() k := reflect.New(seqv.Type().Key()).Elem()
for _, k := range keys { elemv := reflect.New(seqv.Type().Elem()).Elem()
elemv := seqv.MapIndex(k) iter := seqv.MapRange()
for iter.Next() {
k.SetIterKey(iter)
elemv.SetIterValue(iter)
switch elemv.Kind() { switch elemv.Kind() {
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
r, err := ns.checkWhereArray(ctxv, elemv, kv, mv, path, op) r, err := ns.checkWhereArray(ctxv, elemv, kv, mv, path, op)