Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Это важно
Описанные в этом разделе методы повышают производительность при применении к горячим путям в коде. Горячие пути — это те разделы базы кода, которые выполняются часто и многократно в обычных операциях. Применение этих методов к коду, который не часто выполняется, будет иметь минимальное влияние. Перед внесением изменений для улучшения производительности важно измерить исходный уровень. Затем проанализируйте базовые показатели, чтобы определить, где происходят узкие места памяти. Вы можете узнать о многих кроссплатформенных средствах для измерения производительности приложения в разделе "Диагностика и инструментирование". Вы можете практиковать сеанс профилирования в руководстве по измерению использования памяти в документации По Visual Studio.
После измерения использования памяти и определения того, что можно уменьшить объем выделений, используйте методы, описанные в этом разделе, чтобы сократить объем выделений. После каждого последовательного изменения снова измеряйте использование памяти. Убедитесь, что каждое изменение оказывает положительное влияние на использование памяти в приложении.
Производительность работы в .NET часто означает удаление выделений из кода. Каждый блок памяти, который вы выделяете, в конечном итоге должен быть освобожден. Уменьшение количества выделений сокращает время, потраченное на сбор мусора. Это позволяет более предсказуемо контролировать время выполнения, удалив сборщики мусора из определенных участков кода.
Распространенная тактика сокращения выделения заключается в изменении критически важных структур данных с class
типов на struct
типы. Это изменение влияет на семантику использования этих типов. Параметры и возвращаемые значения теперь передаются по значению, вместо ссылки. Стоимость копирования значения незначительна, если объем типов мал — три слова или меньше (учитывая, что размер одного слова соответствует размеру одного целого числа). Это измеряемое значение, которое может реально повлиять на производительность крупных типов данных. Чтобы бороться с эффектом копирования, разработчики могут передать эти типы через ref
, чтобы сохранить их предполагаемую семантику.
Функции C# ref
позволяют выразить нужную семантику для struct
типов, не влияя на общую удобство использования. До этих улучшений разработчикам необходимо использовать unsafe
конструкции с указателями и необработанной памятью, чтобы добиться того же влияния на производительность. Компилятор создает проверенный безопасный код для новых ref
связанных функций.
Проверенный безопасный код означает, что компилятор обнаруживает возможные переполнения буфера или доступ к нераспределенной или освобожденной памяти. Компилятор обнаруживает и предотвращает некоторые ошибки.
Передача и возврат по ссылке
Переменные в C# хранят значения. В struct
типах значение — это содержимое экземпляра типа. В class
типах значение является ссылкой на блок памяти, в который хранится экземпляр типа.
ref
Добавление модификатора означает, что переменная сохраняет ссылку на значение. В struct
типах ссылка указывает на хранилище, содержащее значение. В class
типах ссылка указывает на хранилище, содержащее ссылку на блок памяти.
В C#параметры методов передаются по значению, а возвращаемые значения возвращаются по значению. Значение аргумента передается методу. Значение возвращаемого аргумента — возвращаемое значение.
Модификатор ref
, in
, ref readonly
, или out
указывает, что аргумент передается по ссылке.
Ссылка на расположение хранилища передается методу. Добавление ref
к сигнатуре метода означает, что возвращаемое значение возвращается по ссылке.
Ссылка на расположение хранилища — это возвращаемое значение.
Вы также можете использовать назначение ссылок, чтобы переменная ссылалась на другую переменную. Обычное присваивание копирует значение правой стороны в переменную на левой стороне присваивания.
Присваивание ссылки копирует адрес памяти переменной справа в переменную слева. Теперь ref
ссылается на исходную переменную:
int anInteger = 42; // assignment.
ref int location = ref anInteger; // ref assignment.
ref int sameLocation = ref location; // ref assignment
Console.WriteLine(location); // output: 42
sameLocation = 19; // assignment
Console.WriteLine(anInteger); // output: 19
При назначении переменной измените его значение. При назначении ссылки переменной, вы изменяете то, на что она ссылается.
Вы можете работать непосредственно с хранилищем для значений, используя ref
переменные, передачу параметров по ссылке и присвоение с помощью ref. Правила области, применяемые компилятором, обеспечивают безопасность при работе непосредственно с хранилищем.
ref readonly
И in
модификаторы указывают, что аргумент должен передаваться по ссылке и не может быть переназначирован в методе. Разница заключается в том, что ref readonly
метод использует параметр в качестве переменной. Метод может использовать параметр или вернуть его через ссылку только для чтения. В этих случаях следует использовать ref readonly
модификатор.
in
В противном случае модификатор обеспечивает большую гибкость. Не нужно добавлять модификатор in
к аргументу для параметра in
, так что вы можете безопасно обновлять существующие сигнатуры API, используя модификатор in
. Компилятор выдает предупреждение, если вы не добавляете ref
или in
модификатор в аргумент параметра ref readonly
.
Безопасный контекст ссылки
C# содержит правила для ref
выражений, чтобы ref
выражение не могло быть доступным, когда хранилище, на которое оно ссылается, больше не является действительным. Рассмотрим следующий пример:
public ref int CantEscape()
{
int index = 42;
return ref index; // Error: index's ref safe context is the body of CantEscape
}
Компилятор сообщает об ошибке, так как невозможно вернуть ссылку на локальную переменную из метода. Вызывающий объект не может получить доступ к указанному хранилищу. Безопасный контекст ссылки определяет область, в которой ref
выражение безопасно для доступа или изменения. В следующей таблице перечислены безопасные контексты ссылок для типов переменных.
ref
поля не могут быть объявлены в class
или в struct
без ссылки, поэтому эти строки не указаны в таблице.
Декларация | Безопасный контекст ссылок |
---|---|
локальный без ссылки | блок, где объявлена локальная переменная |
Параметр, отличный от ссылок | текущий метод |
ref , ref readonly , in параметр |
метод вызова |
out параметр |
текущий метод |
Полеclass |
метод вызова |
Поле, не являющееся ссылкой struct |
текущий метод |
ref поле ref struct |
метод вызова |
Переменная может быть ref
возвращена, если её безопасный контекст ссылки является вызывающим методом. Если его безопасный контекст ссылки является текущим методом или блоком, возвращение запрещено. В следующем фрагменте кода показаны два примера. Поле-член можно получить из области вызова метода, поэтому безопасный контекст поля ссылок класса или структуры является вызывающим методом.
Контекст безопасной ссылки для параметра с ref
или in
модификаторами — это весь метод. Оба могут быть ref
возвращены из метода-члена:
private int anIndex;
public ref int RetrieveIndexRef()
{
return ref anIndex;
}
public ref int RefMin(ref int left, ref int right)
{
if (left < right)
return ref left;
else
return ref right;
}
Замечание
Если модификатор ref readonly
или in
применяется к параметру, этот параметр может быть возвращен через ref readonly
, а не ref
.
Компилятор гарантирует, что ссылка не может избежать безопасного контекста ссылки. Вы можете безопасно использовать ref
параметры, ref return
, и ref
локальные переменные, так как компилятор обнаруживает, если вы случайно написали код, в котором можно получить доступ к выражению ref
, когда его хранилище недействительно.
Безопасные структуры контекста и ссылок
ref struct
типы требуют больше правил, чтобы обеспечить их безопасное использование. Тип ref struct
может включать ref
поля. Для этого требуется введение безопасного контекста. Для большинства типов безопасный контекст является вызывающим методом. Другими словами, значение, которое не ref struct
всегда может быть возвращено из метода.
Неформальный безопасный контекст для ref struct
определяется областью, в которой ко всем его ref
полям можно получить доступ. Другими словами, это пересечение безопасного контекста ссылок всех его ref
полей. Следующий метод возвращает ReadOnlySpan<char>
к члену поля, так что его безопасный контекст — это сам метод.
private string longMessage = "This is a long message";
public ReadOnlySpan<char> Safe()
{
var span = longMessage.AsSpan();
return span;
}
В отличие от этого, следующий код выдает ошибку, так как ref field
член Span<int>
ссылается на массив целых чисел, выделенный в стеке. Он не может избежать метода
public Span<int> M()
{
int length = 3;
Span<int> numbers = stackalloc int[length];
for (var i = 0; i < length; i++)
{
numbers[i] = i;
}
return numbers; // Error! numbers can't escape this method.
}
Объединение типов памяти
Введение System.Span<T> и System.Memory<T> предоставляют единую модель для работы с памятью.
System.ReadOnlySpan<T> и System.ReadOnlyMemory<T> предоставьте доступные для чтения версии для доступа к памяти. Все они предоставляют абстракцию по блоку памяти, в котором хранится массив похожих элементов. Разница заключается в том, что Span<T>
и ReadOnlySpan<T>
являются типами ref struct
, тогда как Memory<T>
и ReadOnlyMemory<T>
являются типами struct
. Диапазоны содержат ref field
. Поэтому экземпляры цепочки не могут покинуть свой безопасный контекст. Безопасный контекст объекта ref struct
— это безопасный контекст ссылки его ref field
. Реализация Memory<T>
и ReadOnlyMemory<T>
устраняет это ограничение. Эти типы используются для прямого доступа к буферам памяти.
Повышение производительности с помощью безопасности указателей
Использование этих функций для повышения производительности включает следующие задачи:
-
Избегайте распределения ресурсов: При изменении типа данных с
class
наstruct
, вы изменяете способ его хранения. Локальные переменные хранятся в стеке. Члены хранятся встроенно, когда выделяется объект контейнера. Это изменение означает меньше выделений и уменьшает работу сборщика мусора. Это также может снизить давление памяти, поэтому сборщик мусора работает реже. -
Сохранение ссылочной семантики: изменение типа из
class
вstruct
изменяет семантику передачи переменной в метод. Код, изменяющий состояние параметров, нуждается в изменении. Теперь, когда параметр является параметромstruct
, метод изменяет копию исходного объекта. Вы можете восстановить исходную семантику, передав этот параметр вref
качестве параметра. После этого изменения метод изменяет исходный файлstruct
еще раз. -
Избегайте копирования данных: копирование больших
struct
типов может повлиять на производительность в некоторых путях кода. Вы также можете добавить модификаторref
, чтобы передавать более крупные структуры данных методам по ссылке, а не по значению. -
Ограничение изменений: когда
struct
тип передается по ссылке, вызываемый метод может изменить состояние структуры. Вы можете заменить модификаторref
на модификаторыref readonly
илиin
, чтобы указать, что аргумент нельзя изменить. Предпочитайтеref readonly
, когда метод захватывает параметр или возвращает его по ссылке только для чтения. Вы также можете создаватьreadonly struct
типы илиstruct
типы сreadonly
элементами, чтобы обеспечить более широкий контроль над тем, какие элементыstruct
можно изменить. -
Непосредственное управление памятью: некоторые алгоритмы наиболее эффективны при обработке структур данных в виде блока памяти, содержащего последовательность элементов. Типы
Span
иMemory
обеспечивают безопасный доступ к блокам памяти.
Ни один из этих методов не требует кода unsafe
. При грамотном использовании вы можете получить характеристики производительности из безопасного кода, которые ранее были возможны только с помощью небезопасных методов. Вы можете попробовать методы, описанные в руководстве по сокращению выделения памяти.