Разрешено ли компиляторам оптимизировать realloc? - PullRequest
0 голосов
/ 19 ноября 2018

Я столкнулся с ситуацией, когда было бы полезно иметь ненужные вызовы для оптимизации realloc.Тем не менее, похоже, что ни clang, ни gcc этого не делают ( godbolt ).- Хотя я вижу оптимизацию, выполняемую с несколькими вызовами malloc.

Пример:

void *myfunc() {
    void *data;
    data = malloc(100);
    data = realloc(data, 200);
    return data;
}

Я ожидал, что он будет оптимизирован до следующего типа:

void *myfunc() {
    return malloc(200);
}

Почему ни clang, ни gcc не оптимизируют его?- Им не разрешено это делать?

Ответы [ 5 ]

0 голосов
/ 14 декабря 2018

Насколько я понимаю, такая оптимизация может быть запрещена (особенно для -indeed невероятного случая, когда malloc успешен, но следующий realloc завершается неудачей).

Вы можете предположить, что malloc и realloc всегда успешны (то есть против стандарта C11, n1570 ; посмотрите также на мою реализацию прикола malloc) , В этой гипотезе (не так строго, но некоторые системы Linux имеют чрезмерную память , чтобы создать эту иллюзию), если вы используете GCC , вы можете написать свой собственный GCC сделать такую ​​оптимизацию.

Я не уверен, что стоит потратить несколько недель или месяцев, чтобы закодировать такой плагин GCC (на практике вы, вероятно, захотите, чтобы он иногда обрабатывал некоторый код между malloc и realloc, и тогда это не так просто, так как вы должны охарактеризовать и определить, какой такой промежуточный код является приемлемым), но этот выбор остается за вами.

0 голосов
/ 20 ноября 2018

Но вы не проверяете возвращаемое значение первого malloc (), которое затем используете во втором realloc ().С тем же успехом это может быть NULL.

Как компилятор может оптимизировать два вызова в один, не делая необоснованных предположений о возвращаемом значении первого?

Тогда существует еще один возможный сценарий,FreeBSD имел обыкновение a realloc(), что было в основном malloc + memcpy + освобождает старый указатель.

Предположим, что от свободной памяти осталось всего 230 байтов.В этой реализации ptr = malloc(100) с последующим realloc(ptr, 200) завершится ошибкой, но один malloc(200) будет успешным.

0 голосов
/ 19 ноября 2018

Им не разрешено это делать?

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


Если осталось 150 байтов выделяемой памяти,
data = malloc(100); data = realloc(data, 200); возвращает NULL с использованием 100 байтов (и утечкой) и 50 оставшихся.

data = malloc(200); возвращает NULL с 0 использованными байтами (без утечки) и 150 остаются.

Различная функциональность в этом узком случае может помешать оптимизации.


Разрешено ли компиляторам оптимизировать realloc?

Возможно - я бы ожидал, что это разрешено. Однако, возможно, не стоит улучшать компилятор, чтобы определить, когда он может.

Успешно malloc(n); ... realloc(p, 2*n) отличается от malloc(2*n);, когда ..., возможно, установило часть памяти.

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

0 голосов
/ 19 ноября 2018

Компилятор, который связывает свои собственные автономные версии malloc / calloc / free / realloc, может законно выполнить указанную оптимизацию, если авторы считают, что это стоило усилий.Компилятор, который связывает цепочки с внешними функциями, все еще может выполнять такие оптимизации, если он задокументировал, что он не рассматривал точную последовательность вызовов таких функций как наблюдаемый побочный эффект, но такая обработка могла бы быть немного более ненадежной.

Если между malloc () и realloc () не выделено или не выделено хранилище, размер realloc () известен при выполнении malloc (), а размер realloc () больше, чем malloc ()размер, тогда может иметь смысл объединить операции malloc () и realloc () в одно большее распределение.Однако, если состояние памяти может временно измениться, то такая оптимизация может привести к сбою операций, которые должны были быть выполнены успешно.Например, учитывая последовательность:

void *p1 = malloc(2000000000);
void *p2 = malloc(2);
free(p1);
p2 = realloc(p2, 2000000000);

система может не иметь 2000000000 байтов, доступных для p2, до тех пор, пока не будет освобожден p1.Если изменить код на:

void *p1 = malloc(2000000000);
void *p2 = malloc(2000000000);
free(p1);

, это приведет к сбою распределения p2.Поскольку стандарт никогда не гарантирует, что запросы на выделение будут успешными, такое поведение не будет несоответствующим.С другой стороны, следующее также может быть "соответствующей" реализацией:

void *malloc(size_t size) { return 0; }
void *calloc(size_t size, size_t count) { return 0; }
void free(void *p) {  }
void *realloc(void *p, size_t size) { return 0; }

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

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

0 голосов
/ 19 ноября 2018

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

Таквопрос в том, является ли realloc() чистой функцией или нет.

В проекте стандартного комитета C11 N1570 говорится об этой функции realloc:

7.22.3.5Функция realloc
...
2. Функция realloc освобождает старый объект, на который указывает ptr, и возвращает указатель на новый объект, размер которого определяется размером.Содержимое нового объекта должно быть таким же, как и у старого объекта до освобождения, до меньшего из нового и старого размеров.Любые байты в новом объекте, превышающие размер старого объекта, имеют неопределенные значения.

Возвращает
4. Функция realloc возвращает указатель на новый объект (который может иметь то же значение, что и указатель на старый объект), или нулевой указатель, если новый объект не может быть выделен.

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

Это означает, что realloc() не может считаться чистой функцией, и компилятор не может оптимизировать множественные вызовы к ней.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...