DEV Community

Cover image for Performance des UUIDs

Performance des UUIDs

Frédéric Bouchery on March 20, 2020

(English version) Dernièrement, en analysant les performances d'une application au moyen de l'excellent outil blackfire, nous avons constaté un te...
Collapse
 
utix profile image
Aurélien Lajoie

ext-uuip c'est le résultat via l'extension PECL ?
Est-il possible d'avoir le code des tests ?

Sur le custom on peut gagner encore un peu. fromBytes appelle le constructeur qui vérifie la chaîne avec preg_match, la chaîne étant valide par construction, il faudrait essayer avec un constructeur sans vérification de format. Un million de preg_match ça doit même être assez important.

Collapse
 
fredbouchery profile image
Frédéric Bouchery

Je viens d'ajouter le code en fin d'article ;)

Collapse
 
utix profile image
Aurélien Lajoie

J'ai refait un module en C pour voir pour aller encore plus loin.
J'ai modifié le module pecl pour garder les tests et les signatures de fonction
Premier test, sans validation du uuid sur le constructeur, deuxième en gardant la validation, troisième la version custom de git:

ext-uuid => 2753 ms
ext-uuid_c => 3735 ms
custom => 4816 ms

Là où il y a le plus à gagner c'est sur le unparse.
Les deux fonctions modifiées

#define UUID_STRING_LEN 36
static inline int hex_to_bin(int x)
{
    switch (x) {
      case '0' ... '9': return x - '0';
      case 'a' ... 'f': return 10 + x - 'a';
      case 'A' ... 'F': return 10 + x - 'A';
      default:          return -1;
    }
}

static int __uuid_parse(const char *str, unsigned char uuid[16])
{
    int j = 0;
    for (int i = 0; i < UUID_STRING_LEN; i++) {
        if (i == 8 || i == 13 || i == 18 || i == 23) {
            if (str[i] != '-')
                return -1;
        } else if (!isxdigit(str[i])) {
            return -1;
        } else {
            int hi = hex_to_bin(str[i]);
            int lo = hex_to_bin(str[++i]);
            uuid[j++] = (hi << 4) | lo;
        }
    }
    return 0;
}

PHP_FUNCTION(uuid_parse)
{

    const char * uuid = NULL;
    size_t uuid_len = 0;
    uuid_t uuid_bin;



    if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &uuid, &uuid_len) == FAILURE) {                                    
        return;                                                                                                        
    }

    if (__uuid_parse(uuid, uuid_bin)) {                                                                                
        RETURN_FALSE;
    }                                                                                                                  

    RETURN_STRINGL((char *)uuid_bin, sizeof(uuid_t));                                                                  
}   

char const __str_digits_lower[36] = "0123456789abcdefghijklmnopqrstuvwxyz";                                            

static void __uuid_fmt(char buf[UUID_STRING_LEN + 1], const uuid_t uuid)                                               
{                                                                                                                      
    char *p = buf;                                                                                                     

    for (int i = 0; i < 16; i++) {                                                                                     
        if (i == 4 || i == 6 || i == 8 || i == 10) {                                                                   
            *p++ = '-'; 
        }
        *p++ = __str_digits_lower[uuid[i] >> 4];                                                                       
        *p++ = __str_digits_lower[uuid[i] & 15];                                                                       
    }   
    *p = '\0';                                                                                                         
}   

PHP_FUNCTION(uuid_unparse)
{

    const char * uuid = NULL;
    size_t uuid_len = 0;
    char uuid_txt[UUID_STRING_LEN + 1];



    if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &uuid, &uuid_len) == FAILURE) {
        return;
    }

    if (uuid_len != sizeof(uuid_t)) {
        RETURN_FALSE;
    }

    __uuid_fmt(uuid_txt, uuid);

    RETURN_STRINGL(uuid_txt, UUID_STRING_LEN);
}
Thread Thread
 
utix profile image
Aurélien Lajoie

Allez, améliorons directement util-linux spinics.net/lists/util-linux-ng/ms... sur le unparse ça divise par 10 le temps

Thread Thread
 
utix profile image
Aurélien Lajoie

Du coup en descendant les étages on finit par arriver à l'assembleur et aux instructions SIMD

stackoverflow.com/questions/538237...

Thread Thread
 
fredbouchery profile image
Frédéric Bouchery

Beau travail !

Thread Thread
 
utix profile image
Aurélien Lajoie
Collapse
 
lyrixx profile image
Grégoire Pineau

Coucou

Mais dans ce cas, le benchmark de Jolicode est-il toujours pertinent, dans la mesure où celui-ci fait une comparaison sur la génération d'UUID ?

Dans le cas des applications que je conçoit, il n'y a jamais besoin de convertir des UUID du format string au format binaire (ou vice versa). Je n'utilise que uuid_create(). Soit ces lignes la

Je ne vois pas bien l’intérêt de passer de string a binaire :( Ah si peut être : Si on utilise un SGBD qui ne support pas les UUID nativement. Heureusement que j'utilise PostgreSQL :)


Sinon si tu trouves des optims à backporté dans le polyfill, je suis preneur :)

Collapse
 
fredbouchery profile image
Frédéric Bouchery

Sinon si tu trouves des optims à backporté dans le polyfill, je suis preneur :)

Nicolas, suite à mon article, a déjà soumis une PR pour optimiser le polyfill :)
github.com/symfony/polyfill/pull/244
Il est réactif le bougre :)

Collapse
 
albanio profile image
Alban Baixas • Edited

Merci pour ton post,

Il est encore possible d'améliorer les performances de fromBytes (au prix d'un peu de lisibilité).

// 1_000_000 itérations
custom-without-check    => 541 ms
custom-without-check-v2 => 521 ms
    public static function fromBytes(string $bytes): self
    {
        if (\strlen($bytes) !== 16) {
            throw new RuntimeException("Invalid binary UUID. Length is not 16 bytes");
        }

        return new self(
            \substr_replace(
                \substr_replace(
                    \substr_replace(
                        \substr_replace(\bin2hex($bytes), '-', 8, 0)
                        , '-', 13, 0
                    ), '-', 18, 0
                ), '-', 23, 0
            ), false
        );
    }

ps: dans le gist le nombre d'itération est de 10_000_000

Collapse
 
fredbouchery profile image
Frédéric Bouchery

En effet, il y a un très léger gain. C'est intéressant de voir que cette écriture puisse faire gagner quelques "ms".
(Oui, sur le gist, j'était resté à 10 millions, car j'avais fait pas mal de tests avec cette valeur)