Почему строковый литерал быстрее статического массива символов? - PullRequest
2 голосов
/ 22 сентября 2019

Я провел следующий тест.

charspeed.c

#include <stdio.h>
#include <time.h>

#define CHAR_COUNT 26
#define CHAR_LIST "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
static const char *CHAR_ARRAY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

#define RUN_COUNT 1000000000

#define GET_CLOCK (float)clock() / CLOCKS_PER_SEC

int main()
{
    long long int sum = 0;
    float start, end;

    start = GET_CLOCK;
    for (size_t i = 0; i < RUN_COUNT; i++)
    {
        char test = CHAR_LIST[i % CHAR_COUNT];
        sum += test; // Force the loop to run!
    }
    end = GET_CLOCK;
    printf("#define Time: %f\n", end - start);

    start = GET_CLOCK;
    for (size_t i = 0; i < RUN_COUNT; i++)
    {
        char test = CHAR_ARRAY[i % CHAR_COUNT];
        sum += test; // Must be the same as fist loop!
    }
    end = GET_CLOCK;
    printf("static const *CHAR_ARRAY Time: %f\n", end - start);
    printf("sum = %lld\n", sum); // Must access "sum" after loops!
    return 0;
}

Его выходы

#define Time: 1.741000
static const *CHAR_ARRAY Time: 1.868000

Почему строковый литерал использует директиву #define быстрее, чем предварительно инициализированный статический массив символов?Где именно хранится строковый литерал и почему быстрее получить к ним доступ в пределах блока?

Используется опция компилятора gcc -o charspeed charspeed.c

Ответы [ 3 ]

2 голосов
/ 22 сентября 2019

Примечание: отредактировано для «синхронизации» с изменениями OP:

Возможно, проблема в том, что вы не даете достаточно хороший тест.Приличный компилятор запустит оба цикла за ноль времени, потому что внутри них ничего не происходит.Я пробовал на MSVC, и ваш код дал 0 для обоих раз.

Однако, увеличив цикл в десять раз и добавив что-то, что не может быть оптимизировано, я получаю довольно равные времена дляоба:

#include <stdio.h>
#include <time.h>

#define CHAR_COUNT 26
#define CHAR_LIST "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
static const char* CHAR_ARRAY = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

#define RUN_COUNT 1000000000 // Increased by factor of 10!
#define GET_CLOCK (double)clock() / CLOCKS_PER_SEC

int main()
{
    long long int sum = 0;
    double start, end;

    start = GET_CLOCK;
    for (size_t i = 0; i < RUN_COUNT; i++) {
        char test = CHAR_LIST[i % CHAR_COUNT];
        sum += test; // Force the loop to run!
    }
    end = GET_CLOCK;
    printf("#define Time: %lf\n", end - start);

    start = GET_CLOCK;
    for (size_t i = 0; i < RUN_COUNT; i++) {
        char test = CHAR_ARRAY[i % CHAR_COUNT];
        sum += test; // Must be the same as fist loop!
    }
    end = GET_CLOCK;
    printf("static const *CHAR_ARRAY Time: %lf\n", end - start);
    printf("sum = %lld\n", sum); // Must access "sum" after loops!
    return 0;
}

Попробуйте сделать что-то подобное на вашем компиляторе / машине, чтобы увидеть, если это что-то меняет.

2 голосов
/ 22 сентября 2019

Просто дополнительный ответ для завершения остальных.

Строковый литерал не является const char *.Причина в том, что const char * может быть переназначен.В вашем случае это означает, что вы можете сделать CHAR_ARRAY = "foo";.

Практически это означает, что компилятор не может оптимизировать столько же кода, который имеет дело с const char *, сколько код, имеющий дело со строкой.литерал.

Чтобы решить эту проблему, вы можете использовать один из следующих способов:

const char *const CHAR_ARRAY = "...";
const char CHAR_ARRAY[] = "...";

Это должно гарантировать ту же производительность, что и строковый литерал.

Однако в вашем случае, поскольку вы объявили CHAR_ARRAY как static, это означает, что компилятор может видеть, только глядя на текущий исходный файл, если CHAR_ARRAY можно переназначить.

На практике это означает, что, предполагаяоптимизация включена:

  • для строкового литерала, a const char *const или const char[], будет сгенерирован тот же код сборки (A).
  • для нестатического const char * будет сгенерирован другой код сборки (B), возможно, медленнее, поскольку их является дополнительным косвенным указанием, и результат не может быть известен во время компиляции.
  • для static const char *, он будет использовать либо код сборки(A) или (B) в зависимости отКомпилятор может доказать или нет, что указатель может быть переназначен.Например, если вы добавите функцию void f() { CHAR_ARRAY = "foo"; } в любое место вашего кода, вы запретите компилятору использовать сборку (A) и (B).
0 голосов
/ 22 сентября 2019

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

Учитывая эту оптимизациювыключен, и что сгенерированный код соответствует точно каждому шагу вашего исходного кода, в первом цикле это похоже на то, как если бы вы сделали

int test=5;

, а во втором это больше похоже на

int something=5;
...
int *addr=&something;
int test=*addr;

В первом случае вы непосредственно получаете доступ к интересному значению, но во втором вы достигаете интересного значения через косвенное указание.

Но я настаиваю, оно не должно быть релевантным в оптимизированном коде.

...