uniqid est lent !!

uniqid est lent !!

C’est un constat que j’ai fait depuis un certain moment, mais le problème ayant refait surface il y a peu de temps sur l’application d’un de mes collègues, je me suis dit qu’il serait sympa de vous le partager !

En utilisant la fonction uniqid() je me suis aperçu qu’elle était beaucoup plus lente, lorsqu’elle était utilisée sans entropie. Et quand je dis plus lente, il s’agit de quasiment 100 fois plus lente !!

Le test est très simple, le voici:

$begin = microtime(true);
for($i = 0; $i < 100000; $i++) {
        uniqid("", false);
}
echo "entropy false: ".(microtime(true)-$begin)."\n";

$begin = microtime(true);
for($i = 0; $i < 100000; $i++) {
        uniqid("", true);
}
echo "entropy true: ".(microtime(true)-$begin)."\n";

// résultat:
//     entropy false: 6.04270195961
//     entropy true: 0.072710037231445

Testé sur du php 5.3 et 5.4, le résultat est le même.

Etant de nature curieux, je suis quand même allez jeter un oeil dans le code source pour savoir pourquoi cette différence.

Le code de la fonction uniqid():

#ifdef HAVE_GETTIMEOFDAY
PHP_FUNCTION(uniqid)
{
        char *prefix = "";
#if defined(__CYGWIN__)
        zend_bool more_entropy = 1;
#else
        zend_bool more_entropy = 0;
#endif
        char *uniqid;
        int sec, usec, prefix_len = 0;
        struct timeval tv;

        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sb", &prefix, &prefix_len,
                                                          &more_entropy)) {
                return;
        }

#if HAVE_USLEEP && !defined(PHP_WIN32)
        if (!more_entropy) {
#if defined(__CYGWIN__)
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "You must use 'more entropy' under CYGWIN");
                RETURN_FALSE;
#else
                usleep(1);
#endif
        }
#endif
        gettimeofday((struct timeval *) &tv, (struct timezone *) NULL);
        sec = (int) tv.tv_sec;
        usec = (int) (tv.tv_usec % 0x100000);

        /* The max value usec can have is 0xF423F, so we use only five hex
         * digits for usecs.
         */
        if (more_entropy) {
                spprintf(&uniqid, 0, "%s%08x%05x%.8F", prefix, sec, usec, php_combined_lcg(TSRMLS_C) * 10);
        } else {
                spprintf(&uniqid, 0, "%s%08x%05x", prefix, sec, usec);
        }

        RETURN_STRING(uniqid, 0);
}
#endif

Pas besoin de chercher longtemps pour s’apercevoir que lorsqu’on n’utilise pas l’entropie un usleep(1) est exécuté. J’ai donc supprimé les lignes 24 et 25 et recompiler pour tester !

Je relance le test précédent:

$begin = microtime(true);
for($i = 0; $i < 100000; $i++) {
        uniqid("", false);
}
echo "entropy false: ".(microtime(true)-$begin)."\n";

$begin = microtime(true);
for($i = 0; $i < 100000; $i++) {
        uniqid("", true);
}
echo "entropy true: ".(microtime(true)-$begin)."\n";

// résultat:
//     entropy false: 0.034775018692017
//     entropy true: 0.064203023910522

Le résultat est sans appel, l’appel à la fonction sans entropie est maintenant 2 fois plus rapide !!

Mais quelque chose me chagrine encore. J’exécute 100000 fois la fonction, avec un usleep(1), au grand maximum je devrais avec 0.1 sec de plus, et pas 6sec !!

Pour en avoir le cœur net, je lance ce simple test:

$begin = microtime(true);
for($i = 0; $i < 100000; $i++)
        usleep(1);
echo (microtime(true)-$begin)."\n";
// résultat
// 5.8738329410553

Hu ??

En faisant un strace sur le script, on peut voir c’est la fonction nanosleep qui est utilisée pour mettre le thread en attente.

En cherchant un peu j’ai trouvé ceci:
Le fait que nanosleep() dort pendant un intervalle relative peut être problématique si l’appel est relancé à plusieurs reprises après avoir été interrompu par des signaux, depuis le temps entre les interruptions et les redémarrages de l’appel aboutira à la dérive dans le temps lorsque le sommeil se termine enfin. Ce problème peut être évité en utilisant clock_nanosleep (2) avec une valeur de temps absolu.

Au final, pas vraiment de solution à vous fournir. Soit vous supprimez ces 2 lignes, dont je ne vois pas l’intérêt (mais ça ne veut pas dire qu’il n’y en a pas) soit vous utilisez entropie pour ceux qui ne compile pas. Pour ma part, j’ai choisi la seconde solution.

Plus qu’à espérer un développeur C passe par là :p

EDIT:
Concernant le usleep, il sert tout simplement à gérer l’unicité de l’id , oubliez donc la modification du source, et abusez de l’entropie (Merci Vincent :))

No Comments

Sorry, the comment form is closed at this time.