Вбудовані функції в C #?


276

Як ви робите "вбудовані функції" в C #? Я не думаю, що я розумію цю концепцію. Вони схожі на анонімні методи? Як функції лямбда?

Примітка . Відповіді майже повністю стосуються можливості вбудованих функцій , тобто "оптимізація вручну чи компілятора, яка замінює сайт виклику функції тілом виклику". Якщо вас цікавлять анонімні (інакше лямбда) функції , див. Відповідь @ jalf або Що це за "лямбда", про який всі говорять? .


11
Це нарешті можливо - дивіться мою відповідь.
konrad.kruczynski

1
Найближче до .NET 4.5 див. Питання Як визначити змінну як функцію лямбда . Він НЕ насправді компілюється як вбудований, але це коротке оголошення функції, як вбудоване оголошення. Залежить від того, що ви намагаєтеся досягти, використовуючи inline.
AppFzx

Відповіді:


384

Нарешті, у .NET 4.5 CLR дозволяє натякнути / запропонувати 1 метод, вкладений з використанням MethodImplOptions.AggressiveInliningзначення. Він також доступний у багажнику Mono (вчинено сьогодні).

// 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 (яке відкрито), є деякі моноспецифічні технічні обмеження, що стосуються вбудованого чи більш загального (наприклад, віртуальних функцій). Загалом, так, це підказка для компілятора, але, мабуть, саме про це вимагали.


17
+1 - оновіть свою відповідь, щоб бути більш конкретним щодо вимог версії рамкової.
М.Бабкок

4
Напевно, це все ще не є силою , але в більшості ситуацій, безумовно, достатньо евристики JITters.
CodesInChaos

7
Інший підхід, який може працювати з усією версією .NET, полягає в тому, щоб розділити трохи занадто великий метод на два способи, той, який викликає інший, жоден з яких не перевищує 32 байти IL. Чистий ефект такий, ніби оригінал був накреслений.
Рік Сладкі

4
Це не змушувати "Inlining", це просто спробувати поговорити з JIT і сказати йому, що програміст дійсно хоче використовувати Inlining тут, але JIT має останнє слово. Звідси MSDN: Метод слід вводити, якщо це можливо.
Орел Еракі

11
Для порівняння, вбудовані пропозиції C ++, навіть специфічні для компілятора, також насправді не змушують вбудовувати рядки: не всі функції можуть бути вписані (принципово, такі як рекурсивні функції важкі, але є й інші випадки). Так так, ця "не зовсім" сила є типовою.
Еймон Нербонна

87

Вбудовані методи - це просто оптимізація компілятора, де код функції перекочується в абонент.

Немає механізму, як це робити в C #, і вони будуть використовуватись нескінченно на мовах, де вони підтримуються - якщо ви не знаєте, чому їх слід десь використовувати, вони не повинні бути.

Редагувати: Щоб уточнити, існують дві основні причини, з якими їх потрібно використовувати вкрай:

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

Найкраще залишити речі в спокої і дозволити компілятору виконувати свою роботу, а потім профайлювати і з’ясувати, чи найкраще рішення для вас. Звичайно, деякі речі просто мають сенс бути накресленими (особливо математичні оператори), але дозволяти компілятору обробляти це, як правило, найкраща практика.


35
Як правило, я думаю, що це нормально, що компілятор обробляє вкладені місця. Але бувають ситуації, коли мені подобається змінювати рішення компілятора і вбудовувати чи не вбудовувати метод.
ммммммммм,

7
@Joel Coehoorn: Це була б погана практика, оскільки вона порушила б модуляризацію та захист доступу. (Подумайте про вбудований метод у класі, який отримує доступ до приватних членів і викликається з різних точок коду!)
mmmmmmmm

9
@Poma Це незвичайна причина використовувати вбудовування. Я дуже сумніваюся, що це виявиться ефективним.
Кріс Криків

56
Аргументи про те, що компілятор знає найкраще, просто помилкові. Він не містить вбудований метод, більший за 32 байт IL, період. Це жахливо для проектів (як, наприклад, моїх, а також вищезгаданих Єгора), які мають ідентифіковані профілери гарячі точки, з якими ми не можемо робити абсолютно нічого. Нічого, тобто, крім того, щоб вирізати та вставити код і так вручну вставити. Це, одним словом, жахливий стан справ, коли ти справді керуєш виставою.
яскравий

6
Вкладиш є більш тонким, як видається. Пам'ять має кеші, до яких швидко отримати доступ, і поточна частина коду зберігається в цих кешах так само, як і змінні. Завантаження наступного рядка кеш-інструкцій - це пропуск кеша, і він коштує, можливо, в 10 чи більше разів, ніж робить одна інструкція. Врешті-решт він повинен завантажитися в кеш-пам'ять L2, що ще дорожче. Таким чином роздутий код може викликати пропущений більше кеш-пам'яті, коли вбудована функція, яка весь час залишається в одному і тому ж рядку кешу L1, може призвести до меншої кількості пропусків кешу та потенційно швидшого коду. З .Net його ще складніше.

56

Оновлення: Відповідно до відповіді konrad.kruczynski , для версій .NET до версії 4.0 включається наступне.

Ви можете використовувати клас MethodImplAttribute, щоб запобігти введенню методу ...

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

... але немає способу зробити протилежне і змусити його накреслити.


2
Цікаво це знати, але чому чорт перешкоджає накреслити метод? Я провів деякий час, дивлячись на монітор, але я не можу придумати причину, чому вбудова може принести шкоду.
Каміло Мартін

3
Якщо ви отримуєте стек викликів (тобто NullReferenceException) і, очевидно, немає ніякого способу, щоб метод поверх стека кинув його. Звичайно, один із його дзвінків може бути. Але який?
dzendras

19
GetExecutingAssemblyі GetCallingAssemblyможе дати різні результати залежно від того, чи введено метод. Примушування методу до неінлінійного усуває будь-яку невизначеність.
стусміт

1
@Downvoter: якщо ви не згодні з тим, що я написав, вам слід опублікувати коментар із поясненням причини. Це не тільки звичайна ввічливість, ви також не дуже домагаєтесь, отримуючи загальну кількість голосів у відповідь 20+.
БАКОН

2
Бажаю, щоб я міг +2, оскільки одне твоє ім’я заслуговує +1: D.
ретродрон

33

Ви змішуєте дві окремі концепції. Функція вбудовування - це оптимізація компілятора, яка не впливає на семантику. Функція поводиться так само, незалежно від того, вбудована вона чи ні.

З іншого боку, лямбда-функції - це суто семантичне поняття. Немає вимог щодо того, як вони мають бути реалізовані або виконані, якщо вони дотримуються поведінки, викладеної в мовній специфікації. Вони можуть бути накреслені, якщо компілятор JIT відчуває, що це подобається, чи ні, якщо цього немає.

В C # немає ключового слова вбудованого тексту, оскільки це оптимізація, яку зазвичай можна залишити компілятору, особливо на мовах JIT'ed. Компілятор JIT має доступ до статистики виконання часу, що дозволяє йому вирішувати, що вводити в обробку набагато ефективніше, ніж ви можете при написанні коду. Функція буде накреслена, якщо компілятор вирішить, і ви нічого з цим не можете зробити. :)


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
}

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
Просто для уточнення, хоча; саме СТІ виконує вкладення; не компілятор 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 років, і мені ще не довелося натрапити на достатньо розумний компілятор, щоб знати, який код слід накреслити (функції) чи ні. Було б корисно мати "inline" заяву в c #, правда, компілятор просто не має всієї необхідної інформації, щоб визначити, яка функція повинна бути вбудованою чи ні без підказки "inline". Звичайно, якщо функція невелика (аксесуар), вона може бути автоматично введена, але що робити, якщо це кілька рядків коду? Немає думки, компілятор не має можливості знати, ви не можете просто залишити це до компілятора для оптимізованого коду (крім алгоритмів).


3
"Нічого, у компілятора немає способу дізнатися" JITer робить в режимі реального часу профілювання функціональних викликів ..
Брайан Гордон

3
Відомо, що .NET JIT оптимізується під час виконання краще, ніж це можливо за допомогою 2-прохідного статичного аналізу під час компіляції. Найважливіша частина кодерів для "старої школи" - це те, що JIT, а не компілятор, відповідає за генерацію коду. JIT, а не компілятор, відповідає за вбудовані методи.
Шон Вілсон

1
Я можу скласти код, на який ви дзвоните, без мого вихідного коду, і JIT може вирішити вбудовувати виклик. Як правило, я погодився б, але я б сказав, що як людина, ти вже не в змозі перевершити інструменти.
Шон Вілсон


0

Ні, такої конструкції в C # немає, але компілятор .NET JIT міг вирішити робити вбудовані виклики функцій у час JIT. Але я насправді не знаю, чи справді це оптимізація.
(Я думаю, що це повинно :-))


0

У випадку, якщо ваші збори будуть ngen-ed, ви можете поглянути на Tar targetPatchingOptOut. Це допоможе ngen вирішити, чи використовувати вбудовані методи. Посилання MSDN

Це все ще лише декларативний натяк на оптимізацію, але не імперативна команда.


І JIT буде знати набагато краще, будь то вбудований чи ні. Очевидно, це не допоможе вам, якщо JIT не оптимізує дзвінок, але чому тоді, до біса, я повинен турбуватися про мінімальні накладні витрати функції, яка викликається лише кілька разів?
Voo

-7

Лямбда-вирази - це вбудовані функції! Я думаю, що у C # немає такого додаткового атрибуту, як вбудований чи щось подібне!


9
Я не відповідав вам, але будь ласка, прочитайте питання та переконайтесь, що ви їх розумієте, перш ніж розміщувати випадкову відповідь. en.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
Що це робити з вбудованими функціями? це анонімний метод.
Tomer W
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.