Конкатенация строк в C, какой метод более эффективен?


я наткнулся на эти два метода конкатенации строк:

Общая часть:

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

Способ 1:

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

Способ 2:

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

в обоих случаях содержание both будет "First Second".

Я хотел бы знать, какой из них эффективнее (мне надо выполнить несколько операций конкатенации), или, если вы знаете лучший способ сделать это.

10 56

10 ответов:

для удобства чтения, я бы пошел с

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

если ваша платформа поддерживает расширения GNU, вы также можете использовать asprintf():

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

если вы застряли С MS C Runtime, вы должны использовать _scprintf() определить длину результирующей строки:

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

скорее всего, самым быстрым решением будет следующее:

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

Не беспокойтесь об эффективности: сделайте свой код читаемым и поддерживаемым. Я сомневаюсь, что разница между этими методами будет иметь в вашей программе.

вот вам какое-то безумие, я действительно пошел и измерил его. Черт возьми, представь себе это. Я думаю, что получил некоторые значимые результаты.

я использовал двухъядерный P4, работающий под управлением Windows, используя mingw gcc 4.4, строя с "gcc foo.к-о Фу.exe-std=C99-Wall-O2".

я протестировал метод 1 и Метод 2 из исходного сообщения. Первоначально держал malloc вне контрольного цикла. Метод 1 был в 48 раз быстрее, чем метод 2. Как ни странно, удаление -O2 из команды сборки сделало в результате exe на 30% быстрее (еще не исследовал, почему).

затем я добавил malloc и свободный внутри цикла. Это замедлило метод 1 в 4,4 раза. Метод 2 замедляется в 1,1 раза.

таким образом, malloc + strlen + free не доминируют в профиле достаточно, чтобы избежать sprintf стоит.

вот код, который я использовал (кроме циклов были реализованы с

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;
}
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);

они должны быть почти одинаковыми. Разница не будет иметь значения. Я бы пошел с sprintf Так как он требует меньше кода.

разница вряд ли имеет значение:

  • Если ваши строки малы, то Танос заглушит конкатенации строк.
  • Если ваши строки большие, время, потраченное на копирование данных, заглушит различия между strcat/ sprintf.

Как уже упоминалось в других плакатах, это преждевременная оптимизация. Сконцентрируйтесь на разработке алгоритма, и только вернуться к этому, если профилирование показывает, что это проблема производительности.

Что сказал... Я подозреваемый метод 1 будет быстрее. Есть некоторые - - - по общему признанию небольшие - - - накладные расходы для разбора sprintf. И strcat скорее всего, "встроенный".

sprintf() предназначен для обработки гораздо больше, чем просто строки, strcat () является специалистом. Но я подозреваю, что вы потеете по мелочам. Строки C принципиально неэффективны таким образом, что различия между этими двумя предлагаемыми методами незначительны. Читайте "назад к основам" Джоэл Спольски для кровавых деталей.

это экземпляр, где C++ обычно работает лучше, чем C. Для обработки строк с большим весом с помощью std:: string, вероятно, будет более эффективно и, конечно, безопаснее.

[edit]

[2nd edit]исправленный код (слишком много итераций в реализации строки C), тайминги и заключение соответственно меняются

Я был удивлен комментарием Эндрю Бейнбриджа о том, что std:: string был медленнее, но он не опубликовал полный код для этого тестового случая. Я изменил его (автоматизируя синхронизацию) и добавил тест std::string. Тест был на VC++ 2008 (собственный код) с по умолчанию опции "Release" (т. е. оптимизированы), Athlon dual core, 2.6 GHz. Результаты:

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

поэтому здесь strcat () намного быстрее (ваш пробег может варьироваться в зависимости от компилятора и параметров), несмотря на присущую неэффективность соглашения о строках C, и поддерживает мое первоначальное предложение о том, что sprintf () несет много багажа, не необходимого для этой цели. Однако он остается наименее читаемым и безопасным, поэтому, когда производительность не критична, имеет мало достоинств ММО.

Я также протестировал реализацию std::stringstream, которая снова была намного медленнее, но для сложного форматирования строк все еще есть заслуга.

исправленный код ниже:

#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) a) скопировать строку A в новый буфер b) скопировать строку B в буфер c) скопировать буфер в выходной буфер

или

1) скопируйте строку A в выходной буфер B) скопируйте строку b в выходной буфер

  • strcpy и strcat-это гораздо более простые операции по сравнению с sprintf, который должен анализировать строку формата
  • strcpy и strcat малы, поэтому они обычно будут встроены компиляторами, экономя еще один дополнительный вызов функции. Например, в llvm strcat будет встроен с помощью strlen, чтобы найти начальную позицию копирования, а затем простую инструкцию по хранению

ни один из них не очень эффективен, так как оба метода должны вычислять длину строки или сканировать ее каждый раз. Вместо этого, поскольку вы все равно вычисляете strlen()s отдельных строк, поместите их в переменные, а затем просто strncpy() дважды.