Skip to content

Commit c5413de

Browse files
committed
llb: convert envlist from slice to linked list
The semantics of envlist is that it gets many adds, and reads but reads often only appear on a specific state of the list, usually when all insertions have been completed. Previous implementation did a full copy on each insert. For lookups the options were to do a O(N) lookup per key or copy to a string slice and then externally try to convert it to map with another copy. In new version, the insertions are cheap and on first access optimized map table is created that is then reused for next lookups. Signed-off-by: Tonis Tiigi <[email protected]>
1 parent f0563a5 commit c5413de

File tree

3 files changed

+80
-43
lines changed

3 files changed

+80
-43
lines changed

client/llb/meta.go

Lines changed: 66 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"net"
77
"path"
8+
"slices"
9+
"sync"
810

911
"github.com/containerd/containerd/platforms"
1012
"github.com/google/shlex"
@@ -111,16 +113,16 @@ func Reset(other State) StateOption {
111113
}
112114
}
113115

114-
func getEnv(s State) func(context.Context, *Constraints) (EnvList, error) {
115-
return func(ctx context.Context, c *Constraints) (EnvList, error) {
116+
func getEnv(s State) func(context.Context, *Constraints) (*EnvList, error) {
117+
return func(ctx context.Context, c *Constraints) (*EnvList, error) {
116118
v, err := s.getValue(keyEnv)(ctx, c)
117119
if err != nil {
118120
return nil, err
119121
}
120122
if v != nil {
121-
return v.(EnvList), nil
123+
return v.(*EnvList), nil
122124
}
123-
return EnvList{}, nil
125+
return &EnvList{}, nil
124126
}
125127
}
126128

@@ -346,54 +348,83 @@ func getSecurity(s State) func(context.Context, *Constraints) (pb.SecurityMode,
346348
}
347349
}
348350

349-
type EnvList []KeyValue
350-
351-
type KeyValue struct {
352-
key string
353-
value string
351+
type EnvList struct {
352+
parent *EnvList
353+
key string
354+
value string
355+
del bool
356+
once sync.Once
357+
l int
358+
values map[string]string
359+
keys []string
354360
}
355361

356-
func (e EnvList) AddOrReplace(k, v string) EnvList {
357-
e = e.Delete(k)
358-
e = append(e, KeyValue{key: k, value: v})
359-
return e
362+
func (e *EnvList) AddOrReplace(k, v string) *EnvList {
363+
return &EnvList{
364+
parent: e,
365+
key: k,
366+
value: v,
367+
l: e.l + 1,
368+
}
360369
}
361370

362-
func (e EnvList) SetDefault(k, v string) EnvList {
371+
func (e *EnvList) SetDefault(k, v string) *EnvList {
363372
if _, ok := e.Get(k); !ok {
364-
e = append(e, KeyValue{key: k, value: v})
373+
return e.AddOrReplace(k, v)
365374
}
366375
return e
367376
}
368377

369-
func (e EnvList) Delete(k string) EnvList {
370-
e = append([]KeyValue(nil), e...)
371-
if i, ok := e.Index(k); ok {
372-
return append(e[:i], e[i+1:]...)
378+
func (e *EnvList) Delete(k string) EnvList {
379+
return EnvList{
380+
parent: e,
381+
key: k,
382+
del: true,
383+
l: e.l + 1,
373384
}
374-
return e
375385
}
376386

377-
func (e EnvList) Get(k string) (string, bool) {
378-
if index, ok := e.Index(k); ok {
379-
return e[index].value, true
380-
}
381-
return "", false
387+
func (e *EnvList) makeValues() {
388+
m := make(map[string]string, e.l)
389+
seen := make(map[string]struct{}, e.l)
390+
keys := make([]string, 0, e.l)
391+
e.keys = e.addValue(keys, m, seen)
392+
e.values = m
393+
slices.Reverse(e.keys)
382394
}
383395

384-
func (e EnvList) Index(k string) (int, bool) {
385-
for i, kv := range e {
386-
if kv.key == k {
387-
return i, true
388-
}
396+
func (e *EnvList) addValue(keys []string, vals map[string]string, seen map[string]struct{}) []string {
397+
if e.parent == nil {
398+
return keys
389399
}
390-
return -1, false
400+
if _, ok := seen[e.key]; !e.del && !ok {
401+
vals[e.key] = e.value
402+
keys = append(keys, e.key)
403+
}
404+
seen[e.key] = struct{}{}
405+
if e.parent != nil {
406+
keys = e.parent.addValue(keys, vals, seen)
407+
}
408+
return keys
409+
}
410+
411+
func (e *EnvList) Get(k string) (string, bool) {
412+
e.once.Do(e.makeValues)
413+
v, ok := e.values[k]
414+
return v, ok
415+
}
416+
417+
func (e *EnvList) Keys() []string {
418+
e.once.Do(e.makeValues)
419+
return e.keys
391420
}
392421

393-
func (e EnvList) ToArray() []string {
394-
out := make([]string, 0, len(e))
395-
for _, kv := range e {
396-
out = append(out, kv.key+"="+kv.value)
422+
func (e *EnvList) ToArray() []string {
423+
keys := e.Keys()
424+
out := make([]string, 0, len(keys))
425+
for _, k := range keys {
426+
v, _ := e.Get(k)
427+
out = append(out, k+"="+v)
397428
}
398429
return out
399430
}

client/llb/state.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -351,16 +351,12 @@ func (s State) GetEnv(ctx context.Context, key string, co ...ConstraintsOpt) (st
351351

352352
// Env returns a new [State] with the provided environment variable set.
353353
// See [Env]
354-
func (s State) Env(ctx context.Context, co ...ConstraintsOpt) ([]string, error) {
354+
func (s State) Env(ctx context.Context, co ...ConstraintsOpt) (*EnvList, error) {
355355
c := &Constraints{}
356356
for _, f := range co {
357357
f.SetConstraintsOption(c)
358358
}
359-
env, err := getEnv(s)(ctx, c)
360-
if err != nil {
361-
return nil, err
362-
}
363-
return env.ToArray(), nil
359+
return getEnv(s)(ctx, c)
364360
}
365361

366362
// GetDir returns the current working directory for the state.

frontend/dockerfile/dockerfile2llb/convert.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
613613
dfCmd(d.stage.SourceCode),
614614
llb.Platform(*platform),
615615
opt.ImageResolveMode,
616-
llb.WithCustomName(prefixCommand(d, "FROM "+d.stage.BaseName, opt.MultiPlatformRequested, platform, nil)),
616+
llb.WithCustomName(prefixCommand(d, "FROM "+d.stage.BaseName, opt.MultiPlatformRequested, platform, emptyEnvs{})),
617617
location(opt.SourceMap, d.stage.Location),
618618
)
619619
if reachable {
@@ -884,7 +884,7 @@ func (e *envsFromState) init() {
884884
if err != nil {
885885
return
886886
}
887-
e.env = shell.EnvsFromSlice(env)
887+
e.env = env
888888
}
889889

890890
func (e *envsFromState) Get(key string) (string, bool) {
@@ -2352,3 +2352,13 @@ func validateBaseImagePlatform(name string, expected, actual ocispecs.Platform,
23522352
lint.Run(&linter.RuleInvalidBaseImagePlatform, location, msg)
23532353
}
23542354
}
2355+
2356+
type emptyEnvs struct{}
2357+
2358+
func (emptyEnvs) Get(string) (string, bool) {
2359+
return "", false
2360+
}
2361+
2362+
func (emptyEnvs) Keys() []string {
2363+
return nil
2364+
}

0 commit comments

Comments
 (0)