Встроенные функции в C #?

276

Как вы делаете "встроенные функции" в C #? Я не думаю, что понимаю концепцию. Они как анонимные методы? Как лямбда-функции?

Примечание . Ответы почти полностью касаются возможности встроенных функций , т. Е. «Оптимизации вручную или компилятора, которая заменяет сайт вызова функции телом вызываемого». Если вас интересуют анонимные (или лямбда) функции , см . Ответ @ jalf или Что такое «лямбда», о которой все говорят? ,

Дина
источник
11
Это наконец возможно - см. Мой ответ.
konrad.kruczynski
1
Подробнее о .NET 4.5 см. Вопрос « Как определить переменную как лямбда-функцию» . На самом деле он НЕ компилируется как встроенный, но это краткое объявление функции, подобное встроенному объявлению. Зависит от того, что вы пытаетесь достичь с помощью встроенного.
AppFzx
Для людей , любопытно, проверить это расширение VS .
TripleAccretion

Ответы:

384

Наконец, в .NET 4.5 CLR позволяет намекать / предлагать 1 метод встраивания, используя MethodImplOptions.AggressiveInliningзначение. Это также доступно в стволе Моно (совершенный сегодня).

// The full attribute usage is in mscorlib.dll,
// so should not need to include extra references
using System.Runtime.CompilerServices; 

...

[MethodImpl(MethodImplOptions.AggressiveInlining)]
void MyMethod(...)

1 . Ранее здесь использовалась «сила». Поскольку было несколько отрицательных голосов, я попытаюсь уточнить этот термин. Как и в комментариях и документации, The method should be inlined if possible.особенно с учетом Mono (который открыт), существуют некоторые специфические для моно технические ограничения, касающиеся встраивания или более общие (например, виртуальные функции). В целом, да, это подсказка компилятору, но я думаю, это то, о чем просили.

konrad.kruczynski
источник
17
+1 - Обновлен ваш ответ, чтобы он был более точным в отношении требований к версии фреймворка.
М.Бабкок
4
Это все еще, вероятно, не встроенная сила , но в большинстве ситуаций, конечно, достаточно переопределить эвристику JITters.
CodesInChaos
7
Другой подход, который может работать со всеми версиями .NET, состоит в том, чтобы разделить немного слишком большой метод на два метода, один из которых вызывает другой, ни один из которых не превышает 32 байта IL. Чистый эффект, как если бы оригинал был встроен.
Рик Слэдки
4
Это не форсирование «Inlining», это просто попытка поговорить с JIT и сказать ему, что программист действительно хочет использовать Inlining здесь, но у JIT есть последнее слово. Следовательно, MSDN: метод должен быть встроен, если это возможно.
Орловская Эраки
11
Для сравнения, встроенные предложения C ++, даже специфичные для компилятора, на самом деле также не вызывают принудительную вставку: не все функции могут быть встроенными (в сущности, такие вещи, как рекурсивные функции, сложны, но есть и другие случаи). Так что да, эта "не совсем" сила является типичной.
Имон Нербонн
87

Встроенные методы - это просто оптимизация компилятора, когда код функции свернут в вызывающую функцию.

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

Изменить: Чтобы уточнить, есть две основные причины, по которым их нужно использовать экономно:

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

Лучше оставить все как есть и позволить компилятору выполнить свою работу, а затем профилировать и выяснить, является ли inline лучшим решением для вас. Конечно, некоторые вещи имеют смысл встраивать (в частности, в математические операторы), но, как правило, использование компилятора - это лучшая практика.

Коди Броусиус
источник
35
Обычно я думаю, что все в порядке, что компилятор обрабатывает встраивание. Но есть ситуации, когда мне нравится переопределять решение компилятора и встроенный или не встроенный метод.
мммммммм
7
@Joel Coehoorn: Это было бы плохой практикой, потому что это нарушило бы модульность и защиту доступа. (Подумайте о встроенном методе в классе, который обращается к закрытым членам и вызывается из другой точки кода!)
мммммммм
9
@Poma Это странная причина использовать встраивание. Я очень сомневаюсь, что это окажется эффективным.
Крис Кричит
56
Аргументы о том, что компилятор знает лучше, неверны. Это не встроенный метод больше 32 байтов IL, точка. Это ужасно для проектов (таких как мой, а также вышеупомянутый Егор), в которых есть горячие точки, определенные профилировщиком, с которыми мы ничего не можем поделать. То есть ничего, кроме вырезания и вставки кода и т. Д., Встроенного вручную. Одним словом, это ужасное положение дел, когда вы действительно гоняетесь за работой.
яркий
6
Подкладка более тонкая, чем кажется. В памяти есть кеши, к которым можно быстро получить доступ, и текущая часть кода хранится в тех же кешах, что и переменные. Загрузка следующей строки кэша инструкций является ошибкой кэша и может стоить в 10 или более раз больше, чем делает одна инструкция. В конце концов он должен загрузиться в кэш L2, что еще дороже. Таким образом, раздутый код может привести к большему количеству пропущенных кеш-памяти, в то время как встроенная функция, которая постоянно находится в одних и тех же строках кеш-памяти L1, может привести к меньшему количеству пропусков кэш-памяти и потенциально более быстрому коду. С .Net это еще сложнее.
56

Обновление: Per ответ konrad.kruczynski в следующем верно для версий .NET до 4.0 включительно.

Вы можете использовать класс MethodImplAttribute, чтобы предотвратить вставку метода ...

[MethodImpl(MethodImplOptions.NoInlining)]
void SomeMethod()
{
    // ...
}

... но нет способа сделать обратное и заставить его быть встроенным.

БЕКОН
источник
2
Интересно знать это, но почему, черт возьми, помешать методу быть встроенным? Я провел некоторое время, уставившись на монитор, но не могу придумать причину, по которой встраивание может причинить какой-либо вред.
Камило Мартин
3
Если вы получаете стек вызовов (т. Е. NullReferenceException) и, очевидно, нет способа, которым метод поверх стека бросил его. Конечно, один из его вызовов может иметь. Но какой?
Дзендрас
19
GetExecutingAssemblyи GetCallingAssemblyможет давать разные результаты в зависимости от того, является ли метод встроенным. Принудительное использование метода без вложений устраняет любую неопределенность.
Стусмит
1
@ Downvoter: если вы не согласны с тем, что я написал, вы должны оставить комментарий, объясняющий почему. Мало того, что это обычная вежливость, вы также не добьетесь многого, набрав всего 20+ ответов.
Бекон
2
Хотел бы я +2, потому что одно только твое имя заслуживает +1: D.
ретродрон
33

Вы смешиваете две разные концепции. Встраивание функций - это оптимизация компилятора, которая не влияет на семантику. Функция ведет себя одинаково независимо от того, встроена она или нет.

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

В C # нет встроенного ключевого слова, потому что это оптимизация, которую обычно можно оставить компилятору, особенно в языках JIT. JIT-компилятор имеет доступ к статистике времени выполнения, что позволяет ему решать, что встроить гораздо эффективнее, чем вы можете при написании кода. Функция будет встроена, если компилятор решит, и вы ничего не можете с этим поделать. :)

jalf
источник
20
«Функция ведет себя одинаково независимо от того, встроена она или нет». В некоторых редких случаях это не так, а именно: журналирование функций, которые хотят знать о трассировке стека. Но я не хочу слишком сильно подрывать ваше базовое утверждение: оно в целом верно.
Джоэл Коухорн
«И вы ничего не можете с этим поделать в любом случае.» - неправда. Даже если мы не считаем «сильное предложение» компилятору делать что-либо, вы можете предотвратить встроенные функции.
BartoszKP
21

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

Пример:

inline int Add(int left, int right) { return left + right; }

Если это так, то нет, C # не эквивалентно этому.

Или вы имеете в виду функции, которые объявлены внутри другой функции? Если это так, то да, C # поддерживает это с помощью анонимных методов или лямбда-выражений.

Пример:

static void Example() {
  Func<int,int,int> add = (x,y) => x + y;
  var result = add(4,6);  // 10
}
JaredPar
источник
21

Коди это правильно, но я хочу привести пример того, что такое встроенная функция.

Допустим, у вас есть этот код:

private void OutputItem(string x)
{
    Console.WriteLine(x);

    //maybe encapsulate additional logic to decide 
    // whether to also write the message to Trace or a log file
}

public IList<string> BuildListAndOutput(IEnumerable<string> x)
{  // let's pretend IEnumerable<T>.ToList() doesn't exist for the moment
    IList<string> result = new List<string>();

    foreach(string y in x)
    {
        result.Add(y);
        OutputItem(y);
    }
    return result;
}

Компилятор Just-In-Time оптимизатор может выбрать , чтобы изменить код , чтобы избежать повторно поместить вызов OutputItem () в стеке, так что было бы , как если бы вы написали код , как это вместо того, чтобы :

public IList<string> BuildListAndOutput(IEnumerable<string> x)
{
    IList<string> result = new List<string>();

    foreach(string y in x)
    {
        result.Add(y);

        // full OutputItem() implementation is placed here
        Console.WriteLine(y);   
    }

    return result;
}

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

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

Джоэл Коухорн
источник
9
Просто чтобы уточнить, хотя; это JIT, который делает встраивание; не компилятор C #.
Марк Гравелл
Также обратите внимание, что, по крайней мере, изначально JIT «предпочел бы» встроенные статические методы даже через границы сборки. Таким образом, старой техникой оптимизации было помечать ваши методы как статические. С тех пор сообщество не одобряет это, но по сей день я все же предпочитаю использовать встроенные методы, которые являются внутренними, неполиморфными по своей природе и, как правило, стоят дешевле, чем связанные с ними заполнение стека + разлив.
Шон Уилсон
7

Да Точно, единственное различие - факт, что это возвращает значение.

Упрощение (без использования выражений):

List<T>.ForEach Принимает меры, не ожидает возврата.

Так что Action<T>делегата было бы достаточно .. сказать:

List<T>.ForEach(param => Console.WriteLine(param));

это то же самое, что сказать:

List<T>.ForEach(delegate(T param) { Console.WriteLine(param); });

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

В то время как

List<T>.Where Принимает функцию, ожидая результата.

Так Function<T, bool>что можно ожидать:

List<T>.Where(param => param.Value == SomeExpectedComparison);

что так же, как:

List<T>.Where(delegate(T param) { return param.Value == SomeExpectedComparison; });

Вы также можете объявить эти методы встроенными и присвоить их переменным IE:

Action myAction = () => Console.WriteLine("I'm doing something Nifty!");

myAction();

или

Function<object, string> myFunction = theObject => theObject.ToString();

string myString = myFunction(someObject);

Надеюсь, это поможет.

Квинтин Робинсон
источник
2

Есть случаи, когда я хочу заставить код быть встроенным.

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

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

разработчик
источник
2

Утверждение «лучше всего оставить все это в покое и позволить компилятору выполнить работу…» (Коди Брошиус) - полная чушь. Я программирую высокопроизводительный игровой код в течение 20 лет, и мне еще не приходилось сталкиваться с «достаточно умным» компилятором, который бы знал, какой код должен быть встроен (функции) или нет. Было бы полезно иметь «встроенный» оператор в c #, правда в том, что компилятор просто не имеет всей информации, необходимой для определения, какая функция должна быть всегда встроенной или нет без подсказки «inline». Конечно, если функция маленькая (accessor), то она может быть автоматически вставлена, но что, если это несколько строк кода? Безусловно, компилятор не знает, вы не можете просто оставить это на усмотрение компилятора (кроме алгоритмов).

gvick2002
источник
3
«Нет смысла, компилятор не может знать» JITer выполняет профилирование вызовов функций в реальном времени.
Брайан Гордон
3
Известно, что .NET JIT оптимизируется во время выполнения лучше, чем это возможно при 2-проходном статическом анализе во время компиляции. Сложно понять программистам «старой школы», что JIT, а не компилятор, отвечает за генерацию нативного кода. JIT, а не компилятор, отвечает за встроенные методы.
Шон Уилсон
1
Я могу скомпилировать код, в который вы звоните, без моего исходного кода, и JIT может решить встроить вызов. Обычно я бы согласился, но я бы сказал, как человек, вы больше не можете превзойти инструменты.
Шон Уилсон
0

Нет, в C # такой конструкции нет, но JIT-компилятор .NET может решить выполнять вызовы встроенных функций во время JIT. Но я на самом деле не знаю, действительно ли он делает такие оптимизации.
(Думаю, так и должно быть :-))

мммммммм
источник
0

Если ваши сборки будут созданы, вы можете взглянуть на TargetedPatchingOptOut. Это поможет ngen решить, следует ли использовать встроенные методы. Ссылка на MSDN

Тем не менее, это всего лишь декларативная подсказка для оптимизации, а не обязательная команда.

Тим Ловелл-Смит
источник
И JIT все равно будет знать лучше, будет ли он встроен или нет. Очевидно, что это не поможет, если JIT не оптимизирует сайт вызова, но тогда зачем мне беспокоиться о минимальных затратах на функцию, которая вызывается всего несколько раз?
Во
-7

Лямбда-выражения являются встроенными функциями! Я думаю, что у C # нет лишних атрибутов, таких как inline или что-то в этом роде!

cordellcp3
источник
9
Я не отрицал вас, но, пожалуйста, прочитайте вопросы и убедитесь, что вы понимаете их, прежде чем отправлять случайный ответ. ru.wikipedia.org/wiki/Inline_function
Камило Мартин
1
Этот ответ не так плох, как он был озвучен - у ОП неясно, что такое «вставка функций» против «лямбда-функций», и, к сожалению, MSDN ссылается на лямбды как «встроенные операторы» или «встроенный код» . Однако, согласно ответу Конрада, есть атрибут, который подсказывает компилятору о включении метода.
StuartLC
-7

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

static void Main(string[] args)
{
    int a = 1;

    Action inline = () => a++;
    inline();
    //here a = 2
}
Йордан Павлов
источник
10
Что это нужно делать с встроенными функциями? это анонимный метод.
Томер W