Есть ли способ сообщить компилятору C, что указатель не имеет хранилищ псевдонимов?

Если компилятор C знает, что указатель не имеет псевдонима, он может выполнить множество оптимизаций. Например, если я скомпилирую следующую функцию с gcc -O2:

int f_noalias(int *arr, int x)
{
    int res = 0;
    int *p = &arr[17];
    *p = x;
    res += *p;
    res += *p;
    return res;
}

компилятор знает, что чтение *p всегда будет оцениваться как x, поэтому сгенерированный код эквивалентен сгенерированному для следующей функции:

int f_noalias2(int *arr, int x)
{
    int *p = &arr[17];
    *p = x;
    return 2*x;
}

Однако, если компилятор считает, что указатель может иметь псевдоним, он больше не выполняет эту оптимизацию. Например, если мы изменим f так, чтобы неизвестная функция вызывалась между операциями чтения в *p, сгенерированный код дважды разыменует p. Компилятор предполагает, что функция read_arr могла изменить значение, на которое указывает p.

int f_withalias(int *arr, int x)
{
    int res = 0;
    int *p = &arr[17];
    *p = x;
    res += *p;
    read_array(arr);
    res += *p;
    return res;
}

В моей конкретной программе, когда функция f работает, указатель p, который она содержит, является единственным, который записывает в этот элемент массива arr. Другие функции в коде могут в это время читать из arr, но не будут записывать в него. (Однако они могут записать другие значения в arr после завершения работы f.)

Итак, теперь у меня три вопроса:

Во-первых: Могу ли я объявить свои переменные, чтобы дать эту подсказку компилятору C? Я попытался добавить аннотацию ограничения к p, но сгенерированный код под gcc -O2 был идентичен сгенерированному коду для f_withalias

int f_restrict(int *arr, int x)
{
    int res = 0;
    int * restrict p = &arr[17];
    *p = x;
    res += *p;
    read_array(arr);
    res += *p;
    return res;
}

Во-вторых: Достоверна ли моя попытка использовать здесь ограничение? Как я понимаю, ограничение означает, что никакие другие указатели не могут использовать псевдоним p как для чтения, так и для записи. Но в моем случае функция read_arr также может получить доступ к массиву arr, на который указывает p.

Третье: Если ответ на предыдущий вопрос "нет", могу ли я попробовать что-то другое вместо restrict?

По сути, мне нужно убедиться, что если я сделаю *p = x в f, то эта запись будет немедленно замечена другими функциями, читающими из arr[17]. Однако я хочу, чтобы GCC мог свободно оптимизировать такие вещи, как x = *p; y = *p до x = *p; y = x, даже если между двумя операциями чтения есть вызовы функций.


person hugomg    schedule 19.09.2018    source источник
comment
Вы пытались добавить restrict к p и arr в одном и том же коде?   -  person chux - Reinstate Monica    schedule 19.09.2018
comment
Изменение int *arr на int * restrict arr не дало результата.   -  person hugomg    schedule 19.09.2018
comment
Хм, поскольку restrict может привести к оптимизации, запрещенной в противном случае, отсутствие видимой оптимизации не означает бесполезного использования restrict. Просто это было бесполезно для этой компиляции/вариантов. Удачи.   -  person chux - Reinstate Monica    schedule 19.09.2018
comment
Почему бы не загрузить *p в локальную переменную один раз перед read_array?   -  person Shafik Yaghmour    schedule 19.09.2018
comment
Я пишу компилятор и использую C в качестве серверной части. A) Я хотел бы использовать оптимизацию GCC, когда это возможно. Б) создание меньшего количества локальных переменных может уменьшить нагрузку на регистры. Если у меня есть и x, и *p, я бы хотел, чтобы GCC знал, что если x не может быть сохранен в регистре, то его можно пересчитать из p по мере необходимости. Нет необходимости осторожно пересыпать его в стопку и обратно   -  person hugomg    schedule 19.09.2018
comment
Я думаю, что вы лжете компилятору. Вы говорите ему, что никакого другого доступа к содержимому, на которое указывает p, не будет в рамках f_restrict, и тем не менее вы получаете к нему доступ. restrict - это контракт между программистом и компилятором, компилятор не будет проверять такой доступ, а скорее доверяет программисту. Я не верю, что gcc жалуется, если вы выполняете неявное преобразование из type* restrict в type*, что сомнительно.   -  person Lundin    schedule 19.09.2018
comment
@Lundin: так ты говоришь, что ответ на мой второй вопрос НЕТ? (Если это так, вы можете опубликовать это как ответ). Кроме того, вы не знаете, есть ли другой способ получить то, что я хочу?   -  person hugomg    schedule 19.09.2018
comment
Хорошо, я попытаюсь написать ответ, но, по моему опыту, компиляторы еще не заслуживают доверия, когда дело доходит до оптимизации restrict.   -  person Lundin    schedule 19.09.2018


Ответы (1)


Во-первых: есть ли способ объявить свои переменные, чтобы дать эту подсказку компилятору C?

int * restrict p = &arr[17]; утверждает, что только p и выражения указателя, основанные на p, будут использоваться для доступа к любому объекту, на который указывает p, в течение всего блока (за исключением объектов, которые никаким образом не изменяются). Это позволяет оптимизировать res += *p;, которую вы предлагаете. Тот факт, что GCC не так оптимизирует, является проблемой качества в GCC.

Во-вторых: действительна ли моя попытка использовать здесь ограничение? … По сути, мне нужно убедиться, что если я сделаю *p = x в f, то эта запись будет немедленно замечена другими функциями, читающими из arr[17].

Последнее свойство не является допустимым использованием restrict. Тот факт, что p объявляется restrict, а arr[17] изменяется через p, означает, что ни один указатель, не основанный на p, не должен использоваться для доступа к arr[17] во время выполнения блока, содержащего p, даже для чтения. Таким образом, если что-то в read_array прочитало arr[17] (используя arr, которое не основано на p), это было бы нарушением утверждения restrict.

person Eric Postpischil    schedule 19.09.2018
comment
Согласно моему заголовку вопроса, знаете ли вы, есть ли что-то другое, что я мог бы попробовать, если ограничение слишком ограничительно для моих нужд? Является ли сохранение значения в переменной, как предложил Шафик, единственным способом? - person hugomg; 19.09.2018
comment
@hugomg: я не знаю, как сообщить компилятору, что объект изменяется только с помощью одного указателя, но может быть прочитан с помощью другого. Я сомневаюсь, что есть. Правильный способ написания нового компилятора — выдать код во внутреннем представлении GCC. Таким образом, новый интерфейс анализирует язык и пишет внутренний язык GCC, а существующий сервер выполняет оптимизацию и генерацию ассемблерного/объектного кода. - person Eric Postpischil; 19.09.2018