Истинно небезопасная производительность кода

Я понимаю, что небезопасный код больше подходит для доступа к таким вещам, как Windows API, и для небезопасного приведения типов, чем для написания более производительного кода, но я хотел бы спросить вас, замечали ли вы когда-нибудь значительное улучшение производительности в реальных приложениях с его использованием. по сравнению с безопасным кодом С#.


person Miguel    schedule 21.03.2011    source источник
comment
P/Invoke не совсем то же самое, что unsafe ... Я не уверен, что это следует из рассуждений... Кроме того: вы измерили, не делаете ли вы здесь что-то полезное?   -  person Marc Gravell    schedule 21.03.2011
comment
Ни безопасный, ни небезопасный не должны быть более эффективными де-факто. Общая производительность зависит от алгоритмов, реализованных в вашем коде.   -  person zerkms    schedule 21.03.2011
comment
Я не использую небезопасный код прямо сейчас. Я просто пытаюсь понять, стоит ли менять критические части кода на небезопасный код.   -  person Miguel    schedule 21.03.2011
comment
Почему вы полагаете, что небезопасность означает лучшую производительность? Это верно только в нескольких специализированных сценариях.   -  person jalf    schedule 21.03.2011
comment
Прежде чем рассматривать unsafe, убедитесь, что вы знаете, как эффективно использовать C#. Избегайте чрезмерного создания временных объектов. Когда использовать массив структур и на что следует обратить внимание. Buffer.BlockCopy. Условия, при которых JIT оптимизирует проверку границ массива. (Я не эксперт, просто говорю то, что приходит на ум.) Google Высокая производительность C# и Советы по производительности C#.   -  person ToolmakerSteve    schedule 03.03.2018


Ответы (6)


Некоторые показатели эффективности

Преимущества в производительности не так велики, как вы думаете.

Я провел некоторые измерения производительности обычного доступа к управляемому массиву по сравнению с небезопасными указателями в C#.


Результаты сборки выполняются вне Visual Studio 2010, .NET 4 с использованием любого ЦП | Выпуск сборки на следующей спецификации ПК: ПК на базе x64, 1 четырехъядерный процессор. Intel64 Family 6 Model 23 Stepping 10 GenuineIntel ~2833 МГц.

Linear array access
 00:00:07.1053664 for Normal
 00:00:07.1197401 for Unsafe *(p + i)

Linear array access - with pointer increment
 00:00:07.1174493 for Normal
 00:00:10.0015947 for Unsafe (*p++)

Random array access
 00:00:42.5559436 for Normal
 00:00:40.5632554 for Unsafe

Random array access using Parallel.For(), with 4 processors
 00:00:10.6896303 for Normal
 00:00:10.1858376 for Unsafe

Обратите внимание, что идиома unsafe *(p++) на самом деле работала медленнее. Я предполагаю, что это нарушило оптимизацию компилятора, которая объединяла переменную цикла и (сгенерированный компилятором) доступ к указателю в безопасной версии.

Исходный код доступен на github.

person Thomas Bratt    schedule 10.07.2012
comment
-1. В то время уже был хороший пример того, как НЕ измерять производительность, поскольку в основном пробный код слишком упрощен. - person TomTom; 15.02.2014
comment
Это слишком тривиально. При этом не учитываются оптимизации, которые компилятор делает/может делать. Таким образом, непонятно, имеют ли числа какое-либо значение. - person TomTom; 17.02.2014
comment
Конечно, смысл в том, чтобы измерить, позволяет ли изменение идиомы оптимизировать компилятор, например, путем удаления проверки границ? - person Thomas Bratt; 17.02.2014
comment
Отличный ответ, но, возможно, вы сможете улучшить свой код после того, как посмотрите на это. Я думаю, стоит повторить ваши эксперименты: referencesource.microsoft.com/ #mscorlib/система/текст/ - person martijnn2008; 19.05.2017
comment
Текст из комментария @martijnn2008: // читать следующий символ. JIT-оптимизация, похоже, сбивается // при компиляции ch = *pSrc++;, поэтому лучше использовать ch = *pSrc; pSrc++; вместо этого ch = *pSrc; pSrc++; - person Thomas Bratt; 04.06.2017
comment
@TomTom - что конкретно вы бы сделали по-другому? - person ToolmakerSteve; 03.03.2018
comment
Re Преимущества производительности не так велики, как вы думаете. Это утверждение слишком широкое, основанное на ваших тестах. Вы убедительно продемонстрировали, что простое добавление ключевого слова unsafe и использование ptr++ не приводит к существенному выигрышу. Это ценная информация; Я ценю это. Чего не хватает, так это какого-либо понимания того, когда unsafe может быть намного быстрее, или, наоборот, показать, что unsafe не быстрее, в более существенных вычислениях. Однако это полезная отправная точка; благодаря. - person ToolmakerSteve; 03.03.2018

Как было сказано в других сообщениях, вы можете использовать небезопасный код в очень специализированных контекстах, чтобы получить значительное повышение производительности. Один из таких сценариев — перебор массивов типов значений. Использование небезопасной арифметики указателя намного быстрее, чем использование обычного шаблона for-loop/indexer.

struct Foo
{
    int a = 1;
    int b = 2;
    int c = 0;
}

Foo[] fooArray = new Foo[100000];

fixed (Foo* foo = fooArray)  // foo now points to the first element in the array...
{
    var remaining = fooArray.length;
    while (remaining-- > 0)
    {
        foo->c = foo->a + foo->b;
        foo++;  // foo now points to the next element in the array...
    }
}

Основное преимущество здесь в том, что мы полностью убрали проверку индекса массива.

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

person MattDavey    schedule 21.03.2011
comment
Еще одним большим недостатком такого кода является то, что немногие программисты на C# понимают его или его значение. Если вы работаете в команде, используйте принцип KISS... - person MattDavey; 21.03.2011
comment
Вы уверены, что этот конкретный пример намного быстрее? JVM от Sun позволяет сделать этот тип кода (на Java или Scala) почти таким же быстрым, как C++ с арифметикой указателей; Я удивлен, что реализации C# не сделали бы то же самое. - person Rex Kerr; 21.03.2011
comment
В этом конкретном примере нет, потому что компилятор может определить, что i никогда не выйдет за границы массива, и, таким образом, пропустить проверки границ массива. Но принцип остается. В моем конкретном случае у меня есть кольцевой буфер, реализованный с использованием массива, и отдельный объект Iterator для его перебора. В этом случае компилятор не может сделать эту оптимизацию. - person MattDavey; 21.03.2011
comment
Согласованы кольцевые буферы, по крайней мере, для размеров, кратных двум. Если это не два, компилятор (либо реальный, либо своевременный) мог бы заметить, что ваш тест всегда фиксирует диапазон, и поэтому ему не нужно повторять тот же самый тест снова. Однако я не знаю, делается ли это; в последнее время не нуждался в высокопроизводительном кольцевом буфере. - person Rex Kerr; 21.03.2011
comment
Да, я не уверен, при каких обстоятельствах дрожание С# может определить, что проверка границ массива не нужна. Я работаю с компактной структурой .NET, где дрожание немного более ограничено тем, какие оптимизации он может сделать. Хотя тема интересная.. - person MattDavey; 22.03.2011
comment
Компилятор javac не имеет абсолютно никакого отношения к удалению проверок массива во время выполнения, его работа уже давно завершена. Javac, также известный как компилятор, записывает только файлы классов. Однако JVM — это другой зверь, у него другие правила и так далее. - person mP.; 13.09.2012
comment
В моих тестах использование фиксированных и указателей на самом деле медленнее (Win 7, x64). Ничего не предполагайте, нужно мерить. - person Wout; 05.09.2013
comment
@Wout да, это сильно зависит от обстоятельств и сценария, как я сказал в своем ответе: в очень специализированных контекстах. Бывают случаи, когда арифметика указателей выполняется быстрее. Если бы я утверждал, что небезопасный код всегда быстрее, я бы заслужил ваш отрицательный голос. Как бы то ни было, я этого не утверждал, и ваш отрицательный голос совершенно несправедлив. - person MattDavey; 06.09.2013
comment
@MattDavey, не принимайте это слишком близко к сердцу, я просто не хочу, чтобы люди думали, что этот ответ им поможет, поэтому я проголосовал против него. Не то чтобы это плохой пост, но я не думаю, что это хорошее решение. Кроме того, утверждение Хотя очень производительно не подкреплено никакими цифрами, так что такое заявление вводит в заблуждение. - person Wout; 19.09.2013
comment
Хм. Это было бы более полезно, если бы подкреплялось примером кода, производительность которого можно было бы измерить. Тем не менее, я ценю информацию об увеличении массивов структур — я часто делаю это при подготовке буферов вершин Direct3D. Если один из них станет узким местом, я знаю, что делать дальше. [Хотя судя по комментариям здесь, JIT, возможно, уже делает достойную оптимизацию для меня.] - person ToolmakerSteve; 03.03.2018

Хорошим примером являются манипуляции с изображениями. Изменение пикселей с помощью указателя на их байты (для чего требуется небезопасный код) происходит немного быстрее.

Пример: http://www.gutgames.com/post/Using-Unsafe-Code-for-Faster-Image-Manipulation.aspx

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

person Botz3000    schedule 21.03.2011
comment
Безопасное манипулирование изображениями также может быть довольно быстрым. Особенно, если ваш алгоритм может быть написан так, чтобы исключить проверки границ. Я использую небезопасный код только для копирования данных из растрового изображения в массив и обратно. И если вы используете byte[], вы даже можете избежать этого и просто использовать функции Marshal. - person CodesInChaos; 21.03.2011
comment
@Botz3000: Это отличная ссылка (с нетерпением жду прочтения остальной части сайта), но вывод необоснованный. Использование GetPixel и SetPixel работает очень медленно, но использование int[] более или менее быстро, чем использование указателей без недостатков. - person Thomas Bratt; 10.07.2012
comment
@CodeInChaos: я должен согласиться. вывод, к которому я пришел, заключается в том, что единственная область, в которой можно извлечь выгоду, — это копирование растровых данных. Однако еще не пробовал использовать Marshal. - person Thomas Bratt; 10.07.2012
comment
Я вижу так много проблем с кодом в этой статье. Перебор y во внутреннем цикле означает, что доступ к памяти не осуществляется последовательно. Кроме того, доступ к примитивам, таким как высота, из свойств C# вместо того, чтобы сначала копировать этот примитив в локальную переменную, означает, что выполняется множество дорогостоящих операций выборки из кучи. В-третьих, использование вызова функции GetPixel вместо встроенной арифметики указателя в цикл требует больших накладных расходов. Вы видите примерно 100-кратное снижение производительности с кодом в этой статье. Это ужасный пример использования небезопасного кода. - person nuzzolilo; 09.06.2016
comment
@Nuzzolilo Пример не должен быть идеальным. В самой статье написано, что можно оптимизировать, но и без оптимизаций работает быстрее. Какова была производительность с вашими оптимизациями? - person Botz3000; 09.06.2016
comment
@Botz3000 Botz3000 Как я уже сказал выше, вы видите примерно 100-кратное снижение производительности (на основе тестирования, которое я провел сам). Конечно, это будет быстрее, чем использовать Graphics.GetPixel, но мой I7 также быстрее, чем коммодор, который у меня есть в гараже. Статья об использовании небезопасного кода для более быстрых вычислений не должна содержать столько ошибок, IMO. - person nuzzolilo; 09.06.2016
comment
То, что я упомянул, я бы даже не назвал оптимизацией, это исправление ошибок. Оптимизация будет заключаться в том, чтобы получить максимальную отдачу от вашего целевого оборудования, используя преимущества оптимального кэширования ЦП, выравнивания структур и так далее... - person nuzzolilo; 09.06.2016
comment
Эти цифры зависят от аппаратного обеспечения и, конечно, от всего, но я смог прочитать и зафиксировать 10M пикселей в/из кучи порядка 1-3 миллисекунд в одном потоке на современном процессоре, используя C#, скомпилированный в x86. режим. Доступ к Bitmap.Width во внутреннем цикле замедлил это до 70 мс для меня. переключение X и Y в циклах for добавило несколько миллисекунд - person nuzzolilo; 09.06.2016
comment
@Nuzzolilo Конечно, это может быть быстрее, как и сказал автор статьи, это довольно простое обновление. И оптимизация не ограничивается оптимизацией конкретного оборудования, устранение избыточных вызовов методов и результатов кэширования также является оптимизацией. - person Botz3000; 09.06.2016

Что ж, я бы посоветовал прочитать этот пост в блоге: Блоги MSDN: устранение проверки границ массива в среде CLR

Это поясняет, как выполняются проверки границ в C#. Более того, тесты Томаса Брэттса кажутся мне бесполезными (смотря на код), поскольку JIT все равно удаляет в своих циклах «сохранения» связанные проверки.

person DerPrzem    schedule 29.09.2012
comment
Можете ли вы кратко изложить сообщение здесь. Если ссылка когда-нибудь погаснет, ваш ответ не будет очень полезным. - person ChrisF; 01.10.2012
comment
Вы упустили смысл тестов - они не должны показывать влияние проверки границ на небезопасность. Они должны показать, что проверка границ часто оптимизируется и что стоимость небезопасности, вероятно, не стоит того в этих случаях. - person Thomas Bratt; 25.03.2013

Я использую небезопасный код для обработки видео. В таком коде вы хотите, чтобы он работал как можно быстрее без внутренних проверок значений и т. Д. Без небезопасных атрибутов мой мог бы не справиться с видеопотоком со скоростью 30 кадров в секунду или 60 кадров в секунду. (в зависимости от используемой камеры).

Но из-за скорости он широко используется людьми, которые кодируют графику.

person Peter    schedule 05.02.2016

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

Обратите внимание, что .net сильно изменился, и теперь у него также есть возможность доступа к новым типам данных, таким как векторы, Span, ReadOnlySpan, а также к специфичным для оборудования библиотекам и классам, таким как найденные в System.Runtime.Intrinsics в ядре 3.0

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

person Walter Vehoeven    schedule 11.10.2019