Узкая область действия переменной и производительность?

avatar
Jin Kwon
9 августа 2021 в 06:52
109
2
1

У меня метод main выглядит следующим образом.

    struct list *l = array_list();
    if (l == NULL) {
        return EXIT_FAILURE;
    }
    srand(time(NULL));
    int size = 4;
    for (int i = 0; i < size; i++) {
        int *d = malloc(sizeof(int));
        *d = rand();
        list_insert_last(l, d);
        printf("inserted: %d\n", *d);
    }
    printf("size: %zu\n", list_size(l));
    for (int i = 0; i < size; i++) {
        int *d = list_delete_first(l);
        printf("deleted: %d\n", *d);
        free(d);
    }
    printf("size: %zu\n", list_size(l));
    array_list_free(l);
    return EXIT_SUCCESS;

У меня вопрос, хотя я уже отметил premature-optimization, о переменной int *d.

Не будет ли лучше объявить переменную один раз вне цикла и использовать ее повторно?

    struct list *l = array_list();
    if (l == NULL) {
        return EXIT_FAILURE;
    }
    srand(time(NULL));
    int size = 4;
    int *d; // declare once
    for (int i = 0; i < size; i++) {
        d = malloc(sizeof(int)); // reuse it
        *d = rand();
        list_insert_last(l, d);
        printf("inserted: %d\n", *d);
    }
    printf("size: %zu\n", list_size(l));
    for (int i = 0; i < size; i++) {
        d = list_delete_first(l); // reuse it
        printf("deleted: %d\n", *d);
        free(d);
    }
    printf("size: %zu\n", list_size(l));
    array_list_free(l);
    return EXIT_SUCCESS;
Источник
Costantino Grana
9 августа 2021 в 07:07
1

Сделайте минимальный воспроизводимый пример и загрузите его на godbolt.org с включенной оптимизацией. Я почти уверен, что код сборки будет точно таким же.

Ответы (2)

avatar
Lundin
9 августа 2021 в 08:53
1

Это не имеет никакого значения. Место объявления переменной в коде C часто не совпадает с местом размещения в машинном коде. Так что это действительно "преждевременная оптимизация".

Когда я дизассемблирую ваш код, я получаю идентичный машинный код для d = malloc и int* d = malloc. В зависимости от size компилятор может даже развернуть весь цикл во время оптимизации. В любом случае d, скорее всего, будет выделено в индексном регистре ЦП, что является настолько быстрым, насколько это возможно. (Примерно 2-3 цикла или сколько угодно, в зависимости от ISA.)

Однако обычно рекомендуется максимально сокращать область действия переменных, чтобы сделать код более читабельным и уменьшить беспорядок в пространстве имен. Поэтому локальный int *d = malloc является правильной формой. (И в качестве бонуса, совместимость с C90.)


Теперь, если вы действительно заботитесь о производительности, основным узким местом в этом коде является ввод-вывод с printf, который астрономически медленнее, чем выделение одной переменной int.

Аналогично повторные вызовы malloc в цикле астрономически медленнее. Это также вызывает ненужную фрагментацию кучи и блокирует эффективное использование кэша данных, поскольку выделенная память находится повсюду.

Вызовы rand() также могут быть несколько медленными, в зависимости от реализации C lib.

avatar
wiml
9 августа 2021 в 07:02
1

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

Я предпочитаю ваш первый пример, но это на 100% в интересах человеческих читателей, которым не придется задаваться вопросом, используется ли d позже. Компилятор проведет достаточный анализ, чтобы понять, что они эквивалентны.