Как получить оптимизацию из "чистой функции" в С#?

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

public static int AddOne(int x) { return x + 1; }

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

Есть ли способ достичь такой оптимизации времени выполнения в С#? И я предполагаю, что есть имя для такого рода оптимизации. Как это называется?

Изменить: Очевидно, что моя примерная функция не принесет большой пользы от такого рода оптимизации. Приведен пример, чтобы выразить тип чистоты, который я имел в виду, а не реальный пример.

Ответы

Ответ 1

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

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

Эта область становится все более важной, поскольку массовые многоядерные машины становятся менее дорогими и более распространенными. У нас есть долгосрочная цель исследования для языка С#, чтобы выяснить какой-то способ использовать преимущества чистых функций (и нечистых, но "изолированных" функций) в языке, компиляторе и времени выполнения. Но это связано с множеством трудных проблем, проблем, о которых мало мнения в отрасли или академических кругах относительно наилучшего подхода. Высшие мысли думают об этом, но не ожидайте каких-либо серьезных результатов в ближайшее время.

Ответ 2

Если вычисление было дорогостоящим, вы могли бы кэшировать результат в словаре?

    static Dictionary<int, int> cache = new Dictionary<int, int>();
    public static int AddOne(int x)
    {
        int result;
        if(!cache.TryGetValue(x, out result))
        {
            result = x + 1;
            cache[x] = result;
        }
        return result;
    }

конечно, поиск словаря в этом случае более дорогостоящий, чем add:)

Там еще один более крутой способ сделать функциональную memoization, объясненную Wes Dyer здесь: http://blogs.msdn.com/wesdyer/archive/2007/01/26/function-memoization.aspx - если вы много работаете с этим кешированием, то его функция Memoize может сэкономить вам много кода...

Ответ 4

Техника, которую вы используете, - это memoization: кешируйте результаты выполнения, отбрасывая аргументы, переданные функции, в массив или словарь. Runtimes не склонны применять его автоматически, хотя, безусловно, есть случаи, когда они будут. Ни С#, ни .NET не применяют автоматическое замещение. Вы можете реализовать memoization самостоятельно - это довольно легко, но делать это обычно полезно только для более медленных чистых функций, где вы склонны повторять вычисления и там, где у вас достаточно памяти.

Ответ 5

Это, вероятно, будет встроено (aka встроенное расширение)...

Просто убедитесь, что вы скомпилируете свой код с установленным флагом "Оптимизировать код" (в VS: свойства проекта/вкладка сборки/Оптимизировать код)


Другое, что вы можете сделать, это кешировать результаты (aka memoization). Тем не менее, из-за вашей логики поиска возникает огромный первоначальный результат, поэтому это интересно только для медленных функций (т.е. Не для добавления int).

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


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

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

Ответ 6

Компилятор может оптимизировать эту функцию с помощью комбинации встраивания (замены вызова функции с телом этой функции на сайте вызова) и постоянного распространения (заменяя выражение без свободных переменных с результатом этого выражения). Например, в этом бите кода:

AddOne(5);

AddOne может быть встроен:

5 + 1;

Постоянное распространение может затем упростить выражение:

6;

(Исключение Dead code может затем упростить это выражение, но это только пример).

Зная, что AddOne() не имеет побочных эффектов, также может позволить компилятору выполнить общее устранение подвыражения, чтобы:

AddOne(3) + AddOne(3)

можно преобразовать в:

int x = AddOne(3);
x + x;

или уменьшением силы, даже:

2*AddOne(3);

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

Ответ 7

Как мог компилятор сделать это? Как он знает, какие значения x будут переданы во время выполнения?

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

Ответ 8

Другой вариант - использовать плагин fody https://github.com/Dresel/MethodCache вы можете украсить методы, которые нужно кэшировать. При использовании этого вы должны, конечно, учитывать все комментарии, упомянутые в других ответах.