Подтвердить что ты не робот

Повышение производительности при повторном написании кода С# в C/С++

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

Была часть программы, которая замедляла весь код, и я решил переписать его на C с помощью указателей для доступа к каждому символу в строке, а теперь часть кода, которая заняла около 119 секунд для анализа 10 000 000 строк в С# берет код C всего 5 секунд! Производительность является приоритетом, поэтому я рассматриваю возможность перезаписи всей программы на C, компиляции ее в dll (что-то, что я не знал, как это сделать, когда я начал писать программу) и использовать DllImport из С#, чтобы использовать его методы для работы с строками базы данных.

Учитывая, что для перезаписи всей программы потребуется некоторое время, и поскольку использование DllImport для работы с строками С# требует сортировки и таких вещей, мой вопрос заключается в том, что увеличение производительности от более быстрой обработки строк C dll перевешивает производительность, связанную с необходимостью неоднократно маршал строки для доступа к C dll из С#?

4b9b3361

Ответ 1

Сначала профайлируйте свой код. Вы можете найти некоторую настоящую головную машину, которая значительно увеличивает код С#.

Во-вторых, написание кода в C с помощью указателей на самом деле не является хорошим сравнением. Если вы собираетесь использовать указатели, почему бы не написать его на ассемблере и получить реальную производительность? (Не совсем, просто reductio ad absurdam.) Лучшее сравнение для собственного кода было бы использовать std::string. Таким образом, вы по-прежнему получаете большую помощь от класса string и исключительной безопасности С++.

Учитывая, что вы должны прочитать 2-3 миллиона записей из БД для выполнения этой работы, я очень сомневаюсь, что время, затрачиваемое на взлома строк, перевешивает время прошедшее время загрузки данные из БД. Итак, рассмотрите вместо этого, как структурировать свой код, чтобы вы могли начать обработку строк во время загрузки базы данных.

Если вы используете SqlDataReader (скажем), чтобы последовательно загружать строки, должно быть возможно как можно быстрее дозировать N строк и передать в отдельный поток для последующей обработки, которая является вашей текущей головной болью и причина для этого вопроса. Если вы находитесь на .Net 4.0, это проще всего сделать с помощью Task Parallel Library и System.Collections.Concurrent также может быть полезно для сопоставления результатов между потоками.

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

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

Ответ 2

Один из вариантов заключается в том, чтобы переписать код C как небезопасный С#, который должен иметь примерно такую ​​же производительность и не будет подвергаться никаким мерам вмешательства.

Ответ 3

Нет причин писать в C над С++, а C/С++ не существует.

Последствия маршаллинга влияют на производительность. Если вы должны маршевать каждую строку в отдельности, то ваша производительность будет сосать. Если вы можете объединить все десять миллионов строк за один звонок, то маршаллинг вообще не будет иметь никакого значения. P/Invoke - это не самая быстрая операция в мире, но если вы только вызываете ее несколько раз, это не имеет большого значения.

Возможно, было бы проще перезаписать основное приложение на С++, а затем использовать С++/CLI, чтобы объединить его с концом базы данных С#.

Ответ 4

Здесь есть довольно хорошие ответы, особенно @Steve Townsend's.

Тем не менее, я чувствовал, что стоит подчеркнуть ключевой момент: Существует неотъемлемо никаких причин, по которым C-код "будет быстрее", чем код С#. Эта идея - миф. Под капотом они оба производят машинный код, который работает на одном процессоре. Пока вы не просите С# делать больше работы, чем C, тогда он может работать так же хорошо.

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

Например: на прошлой неделе я переписал (в С#) класс, который написал младший (также в С#). Я добился 25-кратного повышения скорости по сравнению с исходным кодом, применяя тот же подход, который я использовал бы, если бы я писал его на C (т.е. Думал о производительности). Я достиг такого же ускорения, которого вы требуете, не переходя на другой язык вообще.

Наконец, только потому, что изолированный случай может быть сделан на 24 раза быстрее, это не значит, что вы можете сделать всю вашу программу 24x быстрее по всем направлениям, поместив все это на C. Как сказал Стив, профилируйте его, чтобы определить, где он медленный, и расходуйте свои усилия только там, где это принесет значительные преимущества. Если вы вслепую конвертируете в C, вы, вероятно, обнаружите, что потратили много времени на то, чтобы сделать уже действующий код намного менее удобным.

(PS Моя точка зрения связана с 29-летним опытом написания ассемблера, C, С++ и кода С# и понимания того, что язык является всего лишь инструментом для генерации машинного кода - в случае С# vs С++ vs C это прежде всего умение программиста, а не используемый язык, который определяет, будет ли код работать быстро или медленно. Программисты C/С++, как правило, лучше, чем программисты на С#, потому что они должны быть - С# позволяет вам быть ленивым и быстро писать код, в то время как C/С++ заставляет вас делать больше работы, а код занимает больше времени, но хороший программист может получить отличную производительность из С#, а плохой программист может вырвать ужасную производительность из C/С++)

Ответ 5

При неизменяемости строк в .NET я не сомневаюсь, что оптимизированная реализация C превзойдет оптимизированный С# - без сомнения!

P/Invoke несет накладные расходы, но если вы реализуете основную часть логики на C и только выставляете очень гранулированный API для С#, я считаю, что вы находитесь в гораздо лучшей форме.

В конце концов, запись реализации в C означает получение более длительного времени, но это даст вам лучшую производительность, если вы подготовлены к дополнительной стоимости разработки.

Ответ 6

Соберитесь со смешанными сборками - это лучше, чем Interop. Interop - это быстрый способ справиться с родными libs, но смешанные сборки работают лучше.
Смешанные сборки в MSDN
Как обычно, главное - это тестирование и измерение...

Ответ 7

Для конкатенации длинных строк или нескольких строк всегда используйте StringBuilder. Не все знают, что StringBuilder можно использовать не только для ускорения конкатенации строк, но и для вставки, удаления и замены символов.

Если вам это не достаточно быстро, вы можете использовать char - или байт-массивы вместо строк и работать с ними. Если вы закончили с манипуляцией, вы можете преобразовать массив обратно в строку.

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

Как говорили другие, вы можете использовать управляемый С++ (С++/CLI), чтобы хорошо взаимодействовать между .NET и управляемым кодом.

Не могли бы вы показать нам код, возможно, есть другие варианты оптимизации?

Ответ 8

Когда вы начинаете оптимизировать программу на поздней стадии (приложение было написано без оптимизации), вы должны определить узкие места.

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

Просто имейте в виду, что профилировщики С# будут профилировать ваше приложение .Net - не сервер IIS, реализованный в ядре, ни сетевой стек.

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

Там вы думаете, что у вас нет влияния на IIS, реализованный как драйвер ядра, и вы правы.

Но вы можете обойтись без него - и сэкономить много времени и денег.

Положите свой талант там, где он может изменить ситуацию, а не там, где вы вынуждены бежать вместе со своими ногами.

Ответ 9

Врожденные отличия обычно даются как 2x меньше CPU, 5x памяти. На практике немногие люди достаточно хороши или С++, чтобы получить преимущества.

Дополнительный выигрыш для скимпинга в поддержке Unicode, но вы можете достаточно хорошо знать свое приложение, чтобы знать, безопасно ли оно.

Сначала используйте профилировщик, убедитесь, что вы не привязаны к вводу/выводу.