Maison >développement back-end >Golang >Aller | Un moyen efficace et lisible d'ajouter une tranche et de l'envoyer à une fonction variadique

Aller | Un moyen efficace et lisible d'ajouter une tranche et de l'envoyer à une fonction variadique

王林
王林avant
2024-02-05 21:30:11623parcourir

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

Contenu de la question

Supposons que j'ai le pipeline fonctionnel suivant :

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...)...)
}

Parce que le problème que je veux résoudre est hérité, functions 中的函数应该在 opts 中的函数之前调用,所以我不能只附加到 opts 但我必须前置 functionsopts (通过 append(functions, opts...) ),然后再次使用 ... l'envoie à la fonction suivante dans le pipeline, j'obtiens donc l'expression étrange :

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

Je ne sais pas à quel point c'est efficace, mais je suis sûr que ça a l'air bizarre,

Il doit y avoir une meilleure façon de procéder, c'est ce que je recherche. ​​p>

Mais j'apprécierais l'explication qui l'accompagne sur l'efficacité :)

Modifier : Je ne peux pas changer le type de paramètre de opts ...functionobject 更改为 opts []functionobject (如@dev.bmax 在评论中建议的那样),因为我在现有代码库中进行了更改,所以我无法更改调用 func{ 的函数1,2,3}

  1. Par « ça a l'air bizarre », je ne veux pas seulement dire « ça a l'air », je veux dire que faire cela deux fois (ellipses) a l'air bizarre et semble inefficace (je me trompe ?)

Bonne réponse


Ajouter devant une tranche est fondamentalement inefficace car il nécessite une combinaison de :

  • Allouez un réseau de sauvegarde plus grand
  • Déplacer l'élément à la fin de la tranche
  • …ou les deux.

Il serait plus efficace si vous pouviez modifier la convention d'appel entre les fonctions pour simplement ajouter des options, puis les traiter à l'envers. Cela évite de déplacer à plusieurs reprises les éléments vers la fin de la tranche et évite toutes les allocations sauf la première (si suffisamment d'espace est alloué à l'avance).

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

Remarque : func3(opts ...functionobject) / func3(opts...)func3(opts []functionobject) / func3(opts) sont équivalentes en performances. Le premier est un sucre syntaxique efficace pour passer des tranches.

Cependant, vous avez mentionné que vous devez préserver la convention d'appel...

Votre exemple de code entraînera des première, deuxième, troisième, cinquième... allocations supplémentaires au sein de chaque fonction - des allocations sont nécessaires pour doubler la taille du tableau de support (pour les petites tranches). append(functions, opts...) peut également être alloué si les ajouts antérieurs n'ont pas créé suffisamment de capacité disponible.

Les fonctions d'assistance peuvent rendre le code plus lisible. Il est également réutilisable opts Prend en charge la capacité disponible dans les baies :

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.
}

Quelques alternatives sans fonctions d'assistance pour décrire l'allocation plus en détail :

// 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...)
}

Vous pouvez aller plus loin et réutiliser la capacité disponible dans opts sans allouer la tranche contenant les éléments à ajouter (allocation 0-1 par fonction). Cependant, c'est complexe et sujet aux erreurs – je ne le recommande pas.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer