Je regardais un série de conférences sur "Bit Hacking" et je suis tombé sur l'optimisation suivante pour trouver le minimum de deux entiers :
return x ^ ((y ^ x) & -(x > y))
Qui dit être plus rapide que :
if x < y:
return x
else:
return y
Depuis le min
peut traiter plus que deux entiers (des flottants, des chaînes de caractères, des listes et même des objets personnalisés), j'ai supposé que l'appel à la fonction min(x, y)
prendrait plus de temps que le hack optimisé ci-dessus. À ma grande surprise, ils étaient presque identiques :
>>> python -m timeit "min(4, 5)"
1000000 loops, best of 3: 0.203 usec per loop
>>> python -m timeit "4 ^ ((5 ^ 4) & -(4 > 5))"
10000000 loops, best of 3: 0.19 usec per loop
Ceci est vrai même pour les nombres supérieurs à 255
(objets entiers python pré-alloués)
>>> python -m timeit "min(15456, 54657)"
10000000 loops, best of 3: 0.191 usec per loop
python -m timeit "15456 ^ ((54657 ^ 15456) & -(54657 > 15456))"
10000000 loops, best of 3: 0.18 usec per loop
Comment se fait-il qu'une fonction aussi polyvalente que min
peut toujours être aussi rapide et optimisé ?
Note : J'ai exécuté le code ci-dessus en utilisant Python 3.5. Je suppose que c'est la même chose pour Python 2.7+ mais je n'ai pas testé.
J'ai créé le module c suivant :
#include <Python.h>
static PyObject * my_min(PyObject *self, PyObject *args){
const long x;
const long y;
if (!PyArg_ParseTuple(args, "ll", &x, &y))
return NULL;
return PyLong_FromLong(x ^ ((y ^ x) & -(x > y)));
}
static PyMethodDef MyMinMethods[] =
{
{ "my_min", my_min, METH_VARARGS, "bit hack min"
},
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC
initmymin(void)
{
PyObject *m;
m = Py_InitModule("mymin", MyMinMethods);
if (m == NULL)
return;
}
Je l'ai compilé et installé sur mon système (une machine VM ubuntu). J'ai ensuite exécuté ce qui suit :
>>> python -m timeit 'min(4, 5)'
10000000 loops, best of 3: 0.11 usec per loop
>>> python -m timeit -s 'import mymin' 'mymin.my_min(4,5)'
10000000 loops, best of 3: 0.129 usec per loop
Bien que je comprenne qu'il s'agisse d'une machine virtuelle, ne devrait-il pas y avoir un écart plus important dans le temps d'exécution avec le "piratage de bits" qui est déchargé dans le langage c natif ?
41 votes
Utiliser une telle micro-optimisation avec Python est un peu ridicule. Chaque opération que vous effectuez dans un langage interprété est beaucoup plus coûteuse que dans un langage compilé, de sorte que vous pouvez finir par faire beaucoup plus de travail en utilisant de telles astuces.
0 votes
@interjay Bien que ce soit vrai, cela ne peut pas faire de mal d'entendre parler de la mise en œuvre pour mieux la comprendre. L'OP ne dit pas "Je veux que mon programme de somme soit 0,2s plus rapide", il veut juste savoir comment la fonction intégrée peut être si rapide.
0 votes
@SuperBiasedMan Ce que je veux dire, c'est que le buildin n'est pas nécessairement rapide, c'est juste qu'essayer de le remplacer par des bidouillages destinés au langage C ou assembleur ne donnera pas un code rapide.
0 votes
@interjay c'est en fait plus une curiosité. Je n'ai pas l'intention d'optimiser mon code en utilisant le bit hack... ce n'est pas très pythonique. Mais je suis d'accord avec votre déclaration, que je pourrais comparer des pommes et des oranges ici.
1 votes
@KronoS Au fait, vos tests sont faussés, car Python effectue certaines optimisations de pliage constant sur les expressions impliquant des nombres constants, donc en réalité la méthode du hack sera encore plus lente.