web-dev-qa-db-fra.com

Performances: tranches de structures vs tranches de pointeurs vers des structures

Je travaille souvent avec des tranches de structs. Voici un exemple pour une telle structure:

type MyStruct struct {
    val1, val2, val3    int
    text1, text2, text3 string
    list                []SomeType
}

Je définis donc mes tranches comme suit:

[]MyStruct

Disons que j'ai environ un million d'éléments là-dedans et que je travaille beaucoup avec la tranche:

  • J'ajoute souvent de nouveaux éléments. (Le nombre total d'éléments est inconnu.)
  • Je le trie de temps en temps.
  • Je supprime également des éléments (mais pas autant que l'ajout de nouveaux éléments).
  • Je lis souvent des éléments et je les passe (comme arguments de fonction).
  • Le contenu des éléments eux-mêmes n'est pas modifié.

Ma compréhension est que cela conduit à beaucoup de remaniements autour de la structure réelle. L'alternative est de créer une tranche de pointeurs vers la structure:

[]*MyStruct

Maintenant, les structures restent où elles sont et nous ne traitons que des pointeurs qui, je suppose, ont une empreinte plus petite et rendront donc mes opérations plus rapides. Mais maintenant, je donne beaucoup plus de travail au ramasse-miettes.

  • Pouvez-vous fournir des directives générales sur le moment de travailler directement avec des structures par rapport au moment de travailler avec des pointeurs vers des structures?
  • Dois-je m'inquiéter de la quantité de travail que je laisse au GC?
  • Le surcoût de performance de la copie d'un struct vs la copie d'un pointeur est-il négligeable?
  • Peut-être qu'un million d'éléments, ce n'est pas beaucoup. Comment tout cela change-t-il lorsque la tranche devient beaucoup plus grande (mais tient toujours dans la RAM, bien sûr)?
50
Oliver

Je suis juste devenu curieux à ce sujet. Ran quelques repères:

type MyStruct struct {
    F1, F2, F3, F4, F5, F6, F7 string
    I1, I2, I3, I4, I5, I6, I7 int64
}

func BenchmarkAppendingStructs(b *testing.B) {
    var s []MyStruct

    for i := 0; i < b.N; i++ {
        s = append(s, MyStruct{})
    }
}

func BenchmarkAppendingPointers(b *testing.B) {
    var s []*MyStruct

    for i := 0; i < b.N; i++ {
        s = append(s, &MyStruct{})
    }
}

Résultats:

BenchmarkAppendingStructs  1000000        3528 ns/op
BenchmarkAppendingPointers 5000000         246 ns/op

À retenir: nous sommes en nanosecondes. Probablement négligeable pour les petites tranches. Mais pour des millions d'opérations, c'est la différence entre les millisecondes et les microsecondes.

Btw, j'ai essayé de réexécuter le benchmark avec des tranches qui ont été préallouées (avec une capacité de 1000000) pour éliminer la surcharge de append () en copiant périodiquement le tableau sous-jacent. L'ajout de structures a chuté de 1000 ns, l'ajout de pointeurs n'a pas changé du tout.

31
Russ Egan

Pouvez-vous fournir des directives générales sur le moment de travailler directement avec des structures par rapport au moment de travailler avec des pointeurs vers des structures?

Non, cela dépend trop de tous les autres facteurs que vous avez déjà mentionnés.

La seule vraie réponse est: comparer et voir. Chaque cas est différent et toute la théorie du monde ne fait aucune différence lorsque vous avez des horaires réels avec lesquels travailler.

(Cela dit, mon intuition serait d'utiliser des pointeurs, et éventuellement un sync.Pool pour aider le garbage collector: http://golang.org/pkg/sync/#Pool )

9
Evan