web-dev-qa-db-fra.com

Concaténer des chaînes en C, quelle méthode est la plus efficace?

Je suis tombé sur ces deux méthodes pour concaténer des chaînes:

Partie commune:

char* first= "First";
char* second = "Second";
char* both = malloc(strlen(first) + strlen(second) + 2);

Méthode 1:

strcpy(both, first);
strcat(both, " ");       // or space could have been part of one of the strings
strcat(both, second);

Méthode 2:

sprintf(both, "%s %s", first, second);

Dans les deux cas, le contenu de both serait "First Second".

Je voudrais savoir lequel est le plus efficace (je dois effectuer plusieurs opérations de concaténation), ou si vous connaissez une meilleure façon de le faire.

56
Xandy

Pour la lisibilité, j'irais avec

char * s = malloc(snprintf(NULL, 0, "%s %s", first, second) + 1);
sprintf(s, "%s %s", first, second);

Si votre plate-forme supporte les extensions GNU, vous pouvez également utiliser asprintf():

char * s = NULL;
asprintf(&s, "%s %s", first, second);

Si vous êtes bloqué avec le runtime MS C, vous devez utiliser _scprintf() pour déterminer la longueur de la chaîne résultante:

char * s = malloc(_scprintf("%s %s", first, second) + 1);
sprintf(s, "%s %s", first, second);

Ce qui suit sera probablement la solution la plus rapide:

size_t len1 = strlen(first);
size_t len2 = strlen(second);

char * s = malloc(len1 + len2 + 2);
memcpy(s, first, len1);
s[len1] = ' ';
memcpy(s + len1 + 1, second, len2 + 1); // includes terminating null
72
Christoph

Ne vous souciez pas de l'efficacité: rendez votre code lisible et maintenable. Je doute que la différence entre ces méthodes ait de l'importance dans votre programme.

24
Ned Batchelder

Voici de la folie pour vous, je suis allé le mesurer. Putain, imaginez ça. Je pense que j'ai eu des résultats significatifs.

J'ai utilisé un P4 double cœur, tournant sous Windows, utilisant mingw gcc 4.4, construisant avec "gcc foo.c -o foo.exe -std = c99 -Wall -O2".

J'ai testé la méthode 1 et la méthode 2 à partir du message original. Initialement gardé le malloc en dehors de la boucle de référence. La méthode 1 était 48 fois plus rapide que la méthode 2. Bizarrement, la suppression de -O2 de la commande de construction rendait le fichier exe résultant 30% plus rapide (je n'ai pas encore cherché pourquoi).

Ensuite, j'ai ajouté un malloc et libre dans la boucle. Cela a ralenti la méthode 1 d'un facteur de 4,4. Méthode 2 ralentie par un facteur de 1,1.

Ainsi, malloc + strlen + free NE domine PAS assez le profil pour éviter le sprintf.

Voici le code que j'ai utilisé (à part que les boucles ont été implémentées avec <au lieu de! = Mais cela a cassé le rendu HTML de ce post):

void a(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000 * 48; i++)
    {
        strcpy(both, first);
        strcat(both, " ");
        strcat(both, second);
    }
}

void b(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000 * 1; i++)
        sprintf(both, "%s %s", first, second);
}

int main(void)
{
    char* first= "First";
    char* second = "Second";
    char* both = (char*) malloc((strlen(first) + strlen(second) + 2) * sizeof(char));

    // Takes 3.7 sec with optimisations, 2.7 sec WITHOUT optimisations!
    a(first, second, both);

    // Takes 3.7 sec with or without optimisations
    //b(first, second, both);

    return 0;
}
18
Andrew Bainbridge
size_t lf = strlen(first);
size_t ls = strlen(second);

char *both = (char*) malloc((lf + ls + 2) * sizeof(char));

strcpy(both, first);

both[lf] = ' ';
strcpy(&both[lf+1], second);
6

La différence est peu probable:

  • Si vos chaînes sont petites, le malloc couvrira les concaténations de chaînes.
  • Si vos chaînes sont volumineuses, le temps passé à copier les données éliminera les différences entre strcat/sprintf.

Comme d'autres affiches l'ont mentionné, il s'agit d'une optimisation prématurée. Concentrez-vous sur la conception de l'algorithme et n'y revenez que si le profilage montre qu'il s'agit d'un problème de performances.

Cela dit ... I suspect la méthode 1 sera plus rapide. Il y a un peu de surcharge - certes petite - pour analyser la chaîne de formatage sprintf. Et strcat est plus probable "inline -able".

2
ijprest

Ils devraient être à peu près les mêmes. La différence ne va pas avoir d'importance. J'irais avec sprintf car il nécessite moins de code.

2
Jay Conrod

sprintf () est conçu pour gérer beaucoup plus que des chaînes, strcat () est un spécialiste. Mais je soupçonne que vous transpirez les petites choses. Les chaînes de caractères C sont fondamentalement inefficaces en ce sens que les différences entre ces deux méthodes proposées sont insignifiantes. Lire "Back to Basics" par Joel Spolsky pour les détails sanglants.

C++ présente généralement de meilleures performances que C. La gestion de chaînes lourdes utilisant std :: string sera probablement plus efficace et certainement plus sûre.

[modifier]

[2ème édition] Le code corrigé (trop d'itérations dans l'implémentation de la chaîne C), les temps et la conclusion changent en conséquence

J'ai été surpris par le commentaire d'Andrew Bainbridge selon lequel std :: string était plus lent, mais il n'a pas publié de code complet pour ce scénario de test. J'ai modifié le sien (automatisation du timing) et ajouté un test std :: string. Le test portait sur VC++ 2008 (code natif) avec les options par défaut "Release" (optimisées), Athlon dual core, 2,6 GHz. Résultats:

C string handling = 0.023000 seconds
sprintf           = 0.313000 seconds
std::string       = 0.500000 seconds

Donc, ici, strcat () est de loin plus rapide (votre kilométrage peut varier en fonction du compilateur et des options), malgré l'inefficacité inhérente à la convention de la chaîne C, et confirme ma suggestion initiale selon laquelle sprintf () transporte beaucoup de bagages non nécessaires à cette fin. . Il reste de loin le moins lisible et le moins sûr, donc, lorsque les performances ne sont pas critiques, a peu de mérite à l’OMI.

J'ai également testé une implémentation std :: stringstream, ce qui était encore beaucoup plus lent, mais le formatage de chaîne complexe a toujours son mérite.

Le code corrigé suit:

#include <ctime>
#include <cstdio>
#include <cstring>
#include <string>

void a(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000; i++)
    {
        strcpy(both, first);
        strcat(both, " ");
        strcat(both, second);
    }
}

void b(char *first, char *second, char *both)
{
    for (int i = 0; i != 1000000; i++)
        sprintf(both, "%s %s", first, second);
}

void c(char *first, char *second, char *both)
{
    std::string first_s(first) ;
    std::string second_s(second) ;
    std::string both_s(second) ;

    for (int i = 0; i != 1000000; i++)
        both_s = first_s + " " + second_s ;
}

int main(void)
{
    char* first= "First";
    char* second = "Second";
    char* both = (char*) malloc((strlen(first) + strlen(second) + 2) * sizeof(char));
    clock_t start ;

    start = clock() ;
    a(first, second, both);
    printf( "C string handling = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    start = clock() ;
    b(first, second, both);
    printf( "sprintf           = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    start = clock() ;
    c(first, second, both);
    printf( "std::string       = %f seconds\n", (float)(clock() - start)/CLOCKS_PER_SEC) ;

    return 0;
}
1
Clifford

Je ne sais pas si dans le cas deux il y a vraiment une concaténation faite. Les imprimer dos à dos ne constitue pas une concaténation.

Dis-moi cependant, ce serait plus rapide:

1) a) copier la chaîne A dans le nouveau tampon b) copier la chaîne B dans le tampon c) copier le tampon dans le tampon de sortie

ou

1) copier la chaîne A dans le tampon de sortie b) copier la chaîne b dans le tampon de sortie

0
San Jacinto
  • strcpy et strcat sont des opérations beaucoup plus simples comparées à sprintf, qui doit analyser la chaîne de format
  • strcpy et strcat sont petits, ils seront donc généralement insérés par les compilateurs, ce qui évite même une surcharge d'appels de fonctions. Par exemple, dans llvm, strcat sera intégré à l'aide d'un strlen pour rechercher la position de départ de la copie, suivi d'une instruction de stockage simple.
0
Tong Zhou