Heim  >  Artikel  >  Backend-Entwicklung  >  Go |. Effiziente und lesbare Möglichkeit, ein Slice anzuhängen und an eine Variadic-Funktion zu senden

Go |. Effiziente und lesbare Möglichkeit, ein Slice anzuhängen und an eine Variadic-Funktion zu senden

王林
王林nach vorne
2024-02-05 21:30:11556Durchsuche

去 |附加切片并发送到可变参数函数的高效且可读的方法

Frageninhalt

Angenommen, ich habe die folgende funktionale Pipeline:

func func3(opts ...functionobject) {
    for _, opt := range opts {
        opt()
    }
}

func func2(opts ...functionobject) {
   var functions []functionobject
   functions = append(functions, somefunction3)
   functions = append(functions, somefunction4)
...
...
...
    func3(append(functions, opts...)...)
}


func func1(opts ...functionobject) {
   var functions []functionobject
   functions = append(functions, somefunction)
   functions = append(functions, somefunction2)
...
...
...
    func2(append(functions, opts...)...)
}

Da das Problem, das ich lösen möchte, vererbt wird, sendet functions 中的函数应该在 opts 中的函数之前调用,所以我不能只附加到 opts 但我必须前置 functionsopts (通过 append(functions, opts...) ),然后再次使用 ... es an die nächste Funktion in der Pipeline, sodass ich den seltsamen Ausdruck erhalte:

func2(append(functions, opts...)...)

Ich weiß nicht, wie effizient es ist, aber ich bin sicher, es sieht seltsam aus,

Es muss einen besseren Weg geben, das zu tun, das ist es, wonach ich suche. ​​p>

Aber ich würde mich über die begleitende Erklärung zur Effizienz freuen :)

Bearbeiten: Ich kann den Parametertyp von opts ...functionobject 更改为 opts []functionobject (如@dev.bmax 在评论中建议的那样),因为我在现有代码库中进行了更改,所以我无法更改调用 func{ 的函数1,2,3}

nicht ändern
  1. Mit „sieht seltsam aus“ meine ich nicht nur „sieht aus“, ich meine, dass es seltsam aussieht und ineffizient erscheint, wenn man das zweimal macht (Ellipsen) (liege ich da falsch?)

Richtige Antwort


Das Hinzufügen vor einem Slice ist grundsätzlich ineffizient, da es eine Kombination aus Folgendem erfordert:

  • Weisen Sie ein größeres Backing-Array zu
  • Element an das Ende des Slice verschieben
  • …oder beides.

Es wäre effizienter, wenn Sie die Aufrufkonvention zwischen Funktionen ändern könnten, um nur Optionen anzuhängen und sie dann in umgekehrter Reihenfolge zu verarbeiten. Dies vermeidet das wiederholte Verschieben von Elementen an das Ende des Slice und vermeidet alle bis auf die erste Zuweisung (sofern im Voraus genügend Speicherplatz zugewiesen wird).

func func3(opts ...functionobject) {
    for i := len(opts) - 1; i >= 0; i-- {
        opts[i]()
    }
}

Hinweis: func3(opts ...functionobject) / func3(opts...)func3(opts []functionobject) / func3(opts) sind in der Leistung gleichwertig. Ersteres ist ein effektiver syntaktischer Zucker zum Übergeben von Slices.

Sie haben jedoch erwähnt, dass Sie die Aufrufkonvention beibehalten müssen...

Ihr Beispielcode führt zu ersten, zweiten, dritten, fünften ... zusätzlichen Zuweisungen innerhalb jeder Funktion – Zuweisungen sind erforderlich, um die Größe des Hintergrundarrays zu verdoppeln (für kleine Slices). append(functions, opts...) kann auch zugewiesen werden, wenn frühere Anhänge nicht genügend freie Kapazität geschaffen haben.

Hilfsfunktionen können Code lesbarer machen. Es ist auch wiederverwendbar opts Unterstützt freie Kapazität in Arrays:

func func2(opts ...functionobject) {
    // 1-2 allocations. always allocate the variadic slice containings 
    // prepend items. prepend reallocates the backing array for `opts`
    // if needed.
    opts = prepend(opts, somefunction3, somefunction4)
    func3(opts...)
}

// generics requires go1.18+. otherwise change t to functionobject.
func prepend[t any](base []t, items ...t) []t {
    if size := len(items) + len(base); size <= cap(base) {
        // extend base using spare slice capacity.
        out := base[:size]
        // move elements from the start to the end of the slice (handles overlaps).
        copy(out[len(items):], base)
        // copy prepended elements.
        copy(out, items)
        return out
    }
    return append(items, base...) // always re-allocate.
}

Einige Alternativen ohne Hilfsfunktionen, um die Zuordnung genauer zu beschreiben:

// Directly allocate the items to prepend (2 allocations).
func func1(opts ...FunctionObject) {
    // Allocate slice to prepend with no spare capacity, then append re-allocates the backing array
    // since it is not large enough for the additional `opts`.
    // In future, Go could allocate enough space initially to avoid the
    // reallocation, but it doesn't do it yet (as of Go1.20rc1).
    functions := append([]FunctionObject{
        someFunction,
        someFunction2,
        ...
    }, opts...)
    // Does not allocate -- the slice is simply passed to the next function.
    func2(functions...)
}

// Minimise allocations (1 allocation).
func func2(opts ...FunctionObject) {
   // Pre-allocate the required space to avoid any further append
   // allocations within this function.
   functions := make([]FunctionObject, 0, 2 + len(opts))
   functions = append(functions, someFunction3)
   functions = append(functions, someFunction4)
   functions = append(functions, opts...)
   func3(functions...)
}

Sie können noch einen Schritt weiter gehen und die freie Kapazität in opts wiederverwenden, ohne den Slice mit den voranzustellenden Elementen zuzuweisen (0-1 Zuweisung pro Funktion). Dies ist jedoch aufwendig und fehleranfällig – ich empfehle es nicht.

Das obige ist der detaillierte Inhalt vonGo |. Effiziente und lesbare Möglichkeit, ein Slice anzuhängen und an eine Variadic-Funktion zu senden. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:stackoverflow.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen