2016-07-30 4 views
12

C# /. NET имеет переменные параметры функции, передавая Array тип ссылки (в отличие от C/C++, который просто помещает все значения непосредственно в стек, для лучшего и худшего).Будет ли `params` в C# всегда вызывать новый массив для каждого вызова?

В C# мире это имеет опрятное преимущество, что позволяет вам вызывать ту же функцию, либо с «сырыми» аргументами или многоразовым экземпляром массива:

CultureInfo c = CultureInfo.InvariantCulture; 

String formatted0 = String.Format(c, "{0} {1} {2}", 1, 2, 3); 

Int32 third = 3; 
String formatted0 = String.Format(c, "{0} {1} {2}", 1, 2, third); 

Object[] values = new Object[] { 1, 2, 3 }; 
String formatted1 = String.Format(c, "{0} {1} {2}", values); 

Это означает, что сгенерированный CIL эквивалентен:

String formatted0 = String.Format(c, "{0} {1} {2}", new Object[] { 1, 2, 3 }); 

Int32 third = 3; 
String formatted0 = String.Format(c, "{0} {1} {2}", new Object[] { 1, 2, third }); 

Object[] values = new Object[] { 1, 2, 3 }; 
String formatted1 = String.Format(c, "{0} {1} {2}", values); 

Это означает, что (в не-оптимизирующий JIT компилятор) каждый вызов будет выделить новый Object[] экземпляр - хотя в третьем примере вы сможете хранить массив как поле или другое многоразовое значение, чтобы устранить новое распределение по каждые по телефону String.Format.

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

Возможно, потому, что компилятор C# или JIT знает количество аргументов (при использовании «raw») может ли он сделать то же самое, что и ключевое слово stackalloc, и поместить массив в стек и, следовательно, не нужно освобождать Это?

ответ

7

Да, каждый раз выделяется новый массив.

Нет, оптимизация не выполняется. Не существует «интернирования» такого рода, который вы предлагаете. В конце концов, как это могло быть? Метод получения может делать что-либо с массивом, включая мутирование его членов или переназначение записей в массиве, или передачу ссылки на массив на другие (без params).

Никакой специальной «маркировки» такого рода, которую вы предлагаете, не существует. Эти массивы собирают мусор таким же образом, как и все остальное.


Дополнение: Существует, конечно, один частный случай, когда «интернирование» подобного мы обсудим здесь, может быть легко сделать, и что для длины нуля массивов. Компилятор C# мог вызывать Array.Empty<T>() (который каждый раз возвращает один и тот же массив длины-нуля) вместо создания new T[] { } всякий раз, когда он сталкивался с вызовом params, где нужен массив с нулем.

Причиной этой возможности является то, что массивы с нулевым нулем действительно неизменяемы.

Конечно «интернирование» длины нуля массивов будет обнаруживаемым, например, поведение этого класса изменится, если функция должны были быть введены:

class ParamsArrayKeeper 
{ 
    readonly HashSet<object[]> knownArrays = new HashSet<object[]>(); // reference-equals semantics 

    public void NewCall(params object[] arr) 
    { 
    var isNew = knownArrays.Add(arr); 
    Console.WriteLine("Was this params array seen before: " + !isNew); 
    Console.WriteLine("Number of instances now kept: " + knownArrays.Count); 
    } 
} 

Дополнение: Учитывая, что «странная» ковариация массива.NET не относится к типам значений, вы уверены, что ваш код:

Int32[] values = new Int32[ 1, 2, 3 ]; 
String formatted1 = String.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", values); 

работает, как предполагались (если синтаксис исправляется new[] { 1, 2, 3, } или подобными, это будет идти к неправильным перегрузкам String.Format, конечно).

+0

re не интернирование: большое дело! Если метод изменяет массив, не зная, откуда он произошел, и что с ним будет потом, и ожидает, что это будет нормально, он заслуживает того, что получает. – jmoreno

5

Но в официальной среде CLR и JIT есть какие-либо оптимизации для устранения этого распределения?

Вы должны спросить авторов. Но, учитывая, как много усилий придется вникать в это, я сомневаюсь. Метод объявления должен иметь доступ к массиву и извлекать элементы, используя синтаксис массива. Поэтому любая оптимизация обязательно заставит переписать логику метода для преобразования доступа к массиву для прямого доступа к параметрам.

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

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

Возможно, это массив, помеченный специально для того, чтобы он был освобожден, как только выполнение покидает область вызова сайта?

Не нужно отмечать массив «специально», так как сборщик мусора уже автоматически обработает сценарий. Фактически, массив может быть собран в мусор, как только он больше не используется в методе декларирования. Не нужно ждать, пока метод вернется.

6

Да, при каждом вызове будет выделен новый массив.

Помимо сложности методов Встраивания с params, который был объяснен @PeterDuniho, подумайте: все характеристики критических методов .NET, которые имеют params перегрузок имеют перегрузки, принимая только один или несколько аргументов тоже. Они не сделали бы этого, если бы была возможна автоматическая оптимизация.

  • Console (также String, TextWriter, StringBuilder т.д.):

    • public static void Write(String format, params Object[] arg)
    • public static void Write(String format, Object arg0)
    • public static void Write(String format, Object arg0, Object arg1)
    • public static void Write(bool value)
  • Array:

    • public unsafe static Array CreateInstance(Type elementType, params int[] lengths)
    • public unsafe static Array CreateInstance(Type elementType, int length)
    • public unsafe static Array CreateInstance(Type elementType, int length1, int length2)
    • public unsafe static Array CreateInstance(Type elementType, int length1, int length2, int length3)
  • Path:

    • public static String Combine(params String[] paths)
    • public static String Combine(String path1, String path2)
    • public static String Combine(String path1, String path2, String path3)
  • CancellationTokenSource:

    • public static CancellationTokenSource CreateLinkedTokenSource(params CancellationToken[] tokens)
    • public static CancellationTokenSource CreateLinkedTokenSource(CancellationToken token1, CancellationToken token2)
  • т.д.

P. S. Я признаю, что это ничего не доказывает, а Opti возможно, в более поздних версиях, но это все еще нужно рассмотреть. CancellationTokenSource был представлен в сравнительно недавнем 4.0.

+0

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

2

В настоящее время компилятор создает новый объект перед вызовом метода. Это не требуется, и, возможно, JITTER оптимизирует его.

См. https://github.com/dotnet/roslyn/issues/36 для обсуждения возможных изменений производительности с параметрами.

+0

Компилятор C# необходим для создания нового массива под версиями C# 1.0 до 6.0 (что означает все существующие версии C#). Джиттер мог в принципе делать магические вещи, но в этом случае этого не произойдет. Ваша ссылка дает интересное обсуждение некоторых новых функций 'params', которые могут или не могут быть добавлены в C# в будущей версии. Можно представить себе способ сохранить «список» переменных длины в стеке вызовов, не выделяя новый объект в куче (который будет кратковременным во многих случаях использования _typical_). Но сегодня это не то, что существует в C# и CLR. –

+0

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

+0

Если что-то другое, кроме создания нового массива при каждом вызове метода с теми же параметрами, выполняется, метод, получающий массив, может заметить разницу. Спецификация языка C# говорит о создании нового массива. Поэтому, поскольку компилятор C# не может смотреть в будущее и посмотреть, действительно ли метод получения проверяет, является ли это новым экземпляром, компилятор C# будет испытывать трудности с любой «оптимизацией». Время выполнения и джиттер могут обманывать в принципе, но, насколько мне известно, это не будет (в данном случае). См. Мой ответ для примера метода, который показывает «обман». –