Может ли компилятор автоматически определять чистые функции без информации о типе?

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

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

Итак, вопрос: все ли это необходимо или нет? Может ли компилятор обнаруживать чистоту без какой-либо информации о мета или типе, просто предполагая, что все, что делает IO или получает доступ к глобальным переменным автоматически, не является чистым?

Ответ 1

Конечно, вы можете обнаружить чистые функции в некоторых случаях. Например,

int f(int x)
{
    return x*2;
}

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

В GCC есть предупреждения -Wsuggest-attribute=pure и -Wsuggest-attribute=const, которые предлагают функции, которые могут быть кандидатами для pure и const attributes. Я не уверен, хочет ли он быть консервативным (т.е. Не хватает многих чистых функций, но никогда не предлагает его для нечистой функции) или позволяет пользователю решить.

Обратите внимание, что определение GCC pure "зависит только от аргументов и глобальных переменных":

Многие функции не имеют эффектов, кроме возвращаемого значения, а их возвращаемое значение зависит только от параметров и/или глобальных переменных. Такая функция может быть подвержена общему исключению подвыражения и оптимизации цикла, как и в случае с арифметическим оператором. Эти функции должны быть объявлены с помощью атрибута pure.

- Руководство GCC

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

Кстати, автоматическое распараллеливание чистых функций не так просто, как вы думаете; твердая часть решает, что следует параллелизировать. Параллельные вычисления, которые являются слишком дешевыми, и накладные расходы делают его бессмысленным. Не распараллеливайте достаточно, и вы не пользуетесь преимуществами. По этой причине я не знаю никакой практической реализации функционального языка, которая делает автоматическую параллелизацию, хотя библиотеки, такие как repa, параллельны многим операциям за кулисами без явного parallelism в пользовательском коде.

Ответ 2

Есть еще одна проблема. Рассмотрим

int isthispure(int i) {
   if (false) return getchar();
   return i + 42;
}

Функция эффективно чистая, хотя она содержит нечистый код, но этот код не может быть достигнут. Предположим теперь, что false заменяется на g(i), но мы знаем, что g (i) является ложным (например, g может проверить, является ли его аргумент числом Lychrel). Чтобы доказать, что isthispure действительно является чистым, компилятор должен был бы доказать, что не существует чисел Лычреля.

(Я признаю, что это довольно теоретическое соображение. Можно также решить, что если функция содержит любой нечистый код, она сама нечиста, но это не оправдано системой типа С, ИМХО.)

Ответ 3

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

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

Ответ 4

Я обнаружил, что при написании статьи сравнивая производительность С# и С++, Visual С++ действительно может обнаружить чистую функцию умеренной сложности, называя < функция href= "http://dan.corlan.net/bench.html#C" rel= "nofollow" > , которая вычисляла многочлен.

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

Я должен сказать, правда, приятно иметь возможность гарантировать, что компилятор может сделать оптимизацию, отметив функцию как чистую, и она также служит формой документации.