J'ai récemment comparé les vitesses de traitement de []
et j'ai list()
été surpris de découvrir qu'il []
fonctionne plus de trois fois plus vite que list()
. J'ai effectué le même test avec {}
et dict()
et les résultats étaient pratiquement identiques: []
et les {}
deux ont pris environ 0,128 seconde / million de cycles, tandis que list()
et ont dict()
pris environ 0,428 seconde / million de cycles chacun.
Pourquoi est-ce? Faire []
et {}
(et probablement ()
et ''
aussi) passent immédiatement en arrière une copie de certains littérale des actions vides tandis que leurs homologues explicitement nommées ( list()
, dict()
, tuple()
, str()
) pleinement s'y prendre pour créer un objet, si elles ont effectivement ou non des éléments?
Je n'ai aucune idée de la différence entre ces deux méthodes, mais j'aimerais le découvrir. Je n'ai pas pu trouver de réponse dans la documentation ou sur SO, et la recherche de parenthèses vides s'est avérée plus problématique que ce à quoi je m'attendais.
J'ai obtenu mes résultats de chronométrage en appelant timeit.timeit("[]")
et timeit.timeit("list()")
, et timeit.timeit("{}")
et timeit.timeit("dict()")
, pour comparer les listes et les dictionnaires, respectivement. J'utilise Python 2.7.9.
J'ai récemment découvert « Pourquoi est-ce si vrai plus lent que si 1? » Qui compare les performances de if True
à if 1
et semble toucher à un scénario littéral similaire à celui global; cela vaut peut-être aussi la peine d'être considéré.
la source
()
et''
sont spéciaux, car ils ne sont pas seulement vides, ils sont immuables, et en tant que tels, il est facile de les rendre singletons; ils ne construisent même pas de nouveaux objets, il suffit de charger le singleton pour letuple
/ videstr
. Techniquement, un détail d'implémentation, mais j'ai du mal à imaginer pourquoi ils ne mettraient pas en cache le videtuple
/str
pour des raisons de performances. Donc, votre intuition à propos[]
et le{}
transfert d'un littéral boursier était faux, mais cela s'applique à()
et''
.{}
plus rapide que d'appelerset()
?Réponses:
Parce que
[]
et{}
sont une syntaxe littérale . Python peut créer du bytecode juste pour créer les objets de liste ou de dictionnaire:list()
etdict()
sont des objets séparés. Leurs noms doivent être résolus, la pile doit être impliquée pour pousser les arguments, la trame doit être stockée pour être récupérée plus tard, et un appel doit être effectué. Tout cela prend plus de temps.Pour le cas vide, cela signifie que vous avez au moins un
LOAD_NAME
(qui doit rechercher à travers l'espace de noms global ainsi que le__builtin__
module ) suivi d'unCALL_FUNCTION
, qui doit conserver le cadre actuel:Vous pouvez chronométrer la recherche de nom séparément avec
timeit
:L'écart temporel est probablement une collision de hachage de dictionnaire. Soustrayez ces heures des heures d'appel de ces objets et comparez le résultat avec les heures d'utilisation des littéraux:
Le fait d'appeler l'objet prend donc
1.00 - 0.31 - 0.30 == 0.39
quelques secondes supplémentaires par 10 millions d'appels.Vous pouvez éviter le coût de recherche globale en aliasant les noms globaux en tant que locaux (à l'aide d'une
timeit
configuration, tout ce que vous liez à un nom est un local):mais vous ne pouvez jamais surmonter ce
CALL_FUNCTION
coût.la source
list()
nécessite une recherche globale et un appel de fonction mais se[]
compile en une seule instruction. Voir:la source
Parce que
list
c'est une fonction pour convertir disons une chaîne en un objet liste, tandis que[]
est utilisé pour créer une liste dès le départ. Essayez ceci (pourrait vous sembler plus logique):Tandis que
Vous donne une liste réelle contenant tout ce que vous y mettez.
la source
[]
est plus rapide quelist()
, pas pourquoi['wham bam']
est plus rapide quelist('wham bam')
.[]
/list()
est exactement le même que['wham']
/list('wham')
parce qu'ils ont les mêmes différences de variables tout comme1000/10
c'est le même qu'en100/1
mathématiques. Vous pourriez en théorie retirerwham bam
et le fait serait toujours le même, quilist()
essaie de convertir quelque chose en appelant un nom de fonction tout[]
en convertissant simplement la variable. Les appels de fonction sont différents oui, c'est juste un aperçu logique du problème car par exemple, une carte réseau d'une entreprise est également logique d'une solution / d'un problème. Votez comme vous voulez.Les réponses ici sont excellentes, précises et couvrent entièrement cette question. Je laisserai tomber une étape supplémentaire du code octet pour les personnes intéressées. J'utilise le dépôt le plus récent de CPython; les anciennes versions se comportent de manière similaire à cet égard, mais de légères modifications pourraient être en place.
Voici une ventilation de l'exécution de chacun d'eux,
BUILD_LIST
pour[]
etCALL_FUNCTION
pourlist()
.L'
BUILD_LIST
instruction:Vous devriez juste voir l'horreur:
Terriblement alambiqué, je sais. Voici à quel point c'est simple:
PyList_New
(cela alloue principalement la mémoire pour un nouvel objet liste),oparg
signalant le nombre d'arguments sur la pile. Droit au but.if (list==NULL)
.PyList_SET_ITEM
(une macro).Pas étonnant que ce soit rapide! Il est fait sur mesure pour créer de nouvelles listes, rien d'autre :-)
L'
CALL_FUNCTION
instruction:Voici la première chose que vous voyez lorsque vous jetez un œil à la gestion du code
CALL_FUNCTION
:Semble assez inoffensif, non? Eh bien, non, malheureusement non, ce
call_function
n'est pas un gars simple qui appellera la fonction immédiatement, il ne peut pas. Au lieu de cela, il récupère l'objet de la pile, récupère tous les arguments de la pile, puis bascule en fonction du type de l'objet; est-ce un:PyCFunction_Type
? Non, il estlist
,list
est pas de typePyCFunction
PyMethodType
? Non, voir précédent.PyFunctionType
? Nopee, voir précédent.Nous appelons le
list
type, l'argument transmis àcall_function
estPyList_Type
. CPython doit maintenant appeler une fonction générique pour gérer tous les objets appelables nommés_PyObject_FastCallKeywords
, yay plus d'appels de fonction.Cette fonction vérifie à nouveau certains types de fonctions (dont je ne comprends pas pourquoi) puis, après avoir créé un dict pour kwargs si nécessaire , continue à appeler
_PyObject_FastCallDict
._PyObject_FastCallDict
nous emmène enfin quelque part! Après avoir effectué encore plus de vérifications, il saisit l'tp_call
emplacement de celuitype
quetype
nous avons passé, c'est-à-dire qu'il saisittype.tp_call
. Il procède ensuite à la création d'un tuple à partir des arguments passés avec_PyStack_AsTuple
et, enfin, un appel peut enfin être effectué !tp_call
, qui correspondtype.__call__
prend le relais et crée finalement l'objet liste. Il appelle les listes__new__
qui lui correspondentPyType_GenericNew
et lui alloue de la mémoire avecPyType_GenericAlloc
: C'est en fait la partie où il se rattrapePyList_New
, enfin . Toutes les précédentes sont nécessaires pour gérer les objets de manière générique.En fin de compte,
type_call
appellelist.__init__
et initialise la liste avec tous les arguments disponibles, puis nous revenons sur notre chemin. :-)Enfin, rappelez-vous le
LOAD_NAME
, c'est un autre gars qui contribue ici.Il est facile de voir que, lorsqu'il s'agit de notre entrée, Python doit généralement sauter à travers des cerceaux afin de trouver réellement la
C
fonction appropriée pour faire le travail. Il n'a pas la courtoisie de l'appeler immédiatement car il est dynamique, quelqu'un peut masquerlist
( et le garçon fait beaucoup de gens ) et un autre chemin doit être emprunté.C'est là que
list()
perd beaucoup: l'exploration de Python doit faire pour savoir ce qu'il doit faire.La syntaxe littérale, d'autre part, signifie exactement une chose; il ne peut pas être modifié et se comporte toujours d'une manière prédéterminée.
Note de bas de page: Tous les noms de fonction sont susceptibles de changer d'une version à l'autre. Le point est toujours d'actualité et se maintiendra probablement dans toutes les versions futures, c'est la recherche dynamique qui ralentit les choses.
la source
La principale raison est que Python est traité
list()
comme une fonction définie par l'utilisateur, ce qui signifie que vous pouvez l'intercepter en aliasant quelque chose d'autrelist
et en faisant quelque chose de différent (comme utiliser votre propre liste de sous-classes ou peut-être un deque).Il crée immédiatement une nouvelle instance d'une liste intégrée avec
[]
.Mon explication cherche à vous en donner l'intuition.
Explication
[]
est communément appelée syntaxe littérale.Dans la grammaire, cela est appelé un "affichage de liste". De la documentation :
En bref, cela signifie qu'un objet intégré de type
list
est créé.Il n'y a aucun moyen de contourner cela - ce qui signifie que Python peut le faire aussi rapidement qu'il le peut.
D'un autre côté, il
list()
peut être intercepté de créer une fonction intégréelist
à l'aide du constructeur de liste intégrée.Par exemple, disons que nous voulons que nos listes soient créées bruyamment:
Nous pourrions alors intercepter le nom
list
sur la portée globale au niveau du module, puis lorsque nous créons unlist
, nous créons en fait notre liste de sous-types:De même, nous pourrions le supprimer de l'espace de noms global
et le mettre dans l'espace de noms intégré:
Et maintenant:
Et notez que l'affichage de la liste crée une liste sans condition:
Nous ne le faisons probablement que temporairement, alors annulons nos modifications -
List
supprimons d' abord le nouvel objet des commandes internes:Oh, non, nous avons perdu la trace de l'original.
Ne vous inquiétez pas, nous pouvons toujours obtenir
list
- c'est le type d'une liste littérale:Donc...
Comme nous l'avons vu - nous pouvons écraser
list
- mais nous ne pouvons pas intercepter la création du type littéral. Lorsque nous utilisons,list
nous devons faire des recherches pour voir s'il y a quelque chose.Ensuite, nous devons appeler tout appelable que nous avons recherché. De la grammaire:
Nous pouvons voir qu'il fait la même chose pour n'importe quel nom, pas seulement pour la liste:
Car
[]
il n'y a pas d'appel de fonction au niveau du bytecode Python:Cela va simplement à la construction de la liste sans recherche ni appel au niveau du bytecode.
Conclusion
Nous avons démontré qu'il
list
peut être intercepté avec du code utilisateur à l'aide des règles de portée, et qu'illist()
recherche un appelable, puis l'appelle.Considérant que
[]
est un affichage de liste, ou un littéral, et évite ainsi la recherche de nom et l'appel de fonction.la source
list
et le compilateur python ne peut pas être sûr s'il retournera vraiment une liste vide.