Моя 32-битная головная боль – это 64-битная мигрень?!? (или 64-битные ошибки CLR Runtime)

Какие необычные, неожиданные последствия произошли с точки зрения производительности, памяти и т. Д. При переключении с приложений .NET под 64-разрядным JIT на 32-разрядный JIT? Я заинтересован в хорошем, но больше интересуюсь удивительно плохими проблемами, с которыми сталкиваются люди.

Я нахожусь в процессе написания нового приложения .NET, которое будет развернуто как на 32-битной, так и на 64-битной. Было много вопросов, касающихся проблем с переносом приложения – я не забочусь о «gotchas» с точки зрения программирования / переноса . (т. е. правильная обработка встроенного / COM-взаимодействия, ссылочные типы, встроенные в структуры, изменяющие размер структуры и т. д.)

Тем не менее, этот вопрос, и это ответ заставил меня задуматься – Какие еще вопросы я пропускаю?

Там было много вопросов и сообщений в блоге, которые охватывают эту проблему или ударяют по одному ее аспекту, но я не видел ничего, что составило бы достойный список проблем.

В частности – мое приложение очень сильно связано с процессором и имеет огромные шаблоны использования памяти (отсюда и необходимость на 64-битной основе), а также графический характер. Я обеспокоен тем, какие другие скрытые проблемы могут существовать в CLR или JIT, работающем на 64-битной Windows (с использованием .NET 3.5sp1).

Вот несколько проблем, о которых я сейчас знаю:

  • ( Теперь я знаю, что ) Свойства, даже автоматические свойства, не входят в x64.
  • Профиль памяти приложения изменяется как из-за размера ссылок , так и из-за того, что распределитель памяти имеет разные характеристики производительности
  • Время запуска может пострадать от x64

Я хотел бы знать, какие другие, специфические проблемы, которые люди обнаружили в JIT на 64-битной Windows, а также, если есть какие-либо обходные пути для производительности.

Спасибо вам всем!

—-РЕДАКТИРОВАТЬ—–

Просто для ясности –

Я знаю, что попытка оптимизировать раннюю часто бывает плохой. Я знаю, что второе предположение, что система часто плохое. Я также знаю, что переносимость на 64-битную имеет свои проблемы – мы ежедневно запускаем и тестируем на 64-битных системах, чтобы помочь в этом. и т.п.

Однако мое приложение не является типичным бизнес-приложением. Это научное программное приложение. У нас есть много процессов, которые сидят, используя 100% -ый процессор на всех ядрах (он очень многопоточно) в течение нескольких часов.

Я трачу много времени на профилирование приложения, и это имеет огромное значение. Тем не менее, большинство профилировщиков отключает многие функции JIT, поэтому небольшие детали в таких вещах, как выделение памяти, вложение в JIT и т. Д., Могут быть очень сложными, когда вы работаете под профилировщиком. Отсюда моя потребность в этом вопросе.

Я помню, как слышал вопрос с канала IRC, который я часто посещаю. Он оптимизирует временную копию в этом случае:

EventHandler temp = SomeEvent; if(temp != null) { temp(this, EventArgs.Empty); } 

Возrotation состояния гонки и устранение возможных исключений для нулевой ссылки.

Особенно сложная проблема производительности в .NET относится к бедным JIT:

https://connect.microsoft.com/VisualStudio/feedback/details/93858/struct-methods-should-be-inlined?wa=wsignin1.0

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

В любом случае, после борьбы с .NET достаточно долго для этого, моим решением является использование C ++ для чего-либо численно-интенсивного. Даже в «хороших» случаях для .NET, где вы не имеете дело с структурами и используете массивы, где оптимизация границ оптимизирована, C ++ бьет .NET.

Если вы делаете что-либо более сложное, чем точечные, изображение становится очень быстрым; код .NET как длиннее, так и менее читабельным (потому что вам нужно вручную вводить строки и / или не использовать дженерики) и намного медленнее.

Я переключился на использование Eigen в C ++: это абсолютно здорово, в результате получается читаемый код и высокая производительность; тонкая shell C ++ / CLI затем предоставляет клей между вычислительным движком и .NET-миром.

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

Так, например, если P – matrix, такой материал Just Works:

 1.0 / (P.transpose() * P).diagonal().sum(); 

… который не выделяет временно транспонированный вариант P и не вычисляет весь матричный продукт, а только поля, в которых он нуждается.

Итак, если вы можете работать в Full Trust – просто используйте C ++ через C ++ / CLI, он работает намного лучше.

В большинстве случаев Visual Studio и компилятор делают довольно хорошую работу, скрывая от вас проблемы. Тем не менее, я знаю о одной серьезной проблеме, которая может возникнуть, если вы настроите приложение на автоматическое обнаружение платформы (x86 против x64), а также имеете какие-либо зависимости от 32-битных сторонних DLL. В этом случае на 64-битных платформах он попытается вызвать DLL, используя 64-битные соглашения и структуры, и это просто не сработает.

Вы упомянули вопросы портирования, это те, о которых нужно беспокоиться. Я (очевидно) не знаю вашего приложения, но, пытаясь догадаться, JIT часто является пустой тратой времени. Люди, которые пишут JIT, имеют глубокое понимание архитектуры микросхем x86 / x64 и, вероятно, знают, что лучше работает, а что хуже, чем кто-либо еще на планете.

Да, возможно, что у вас есть угловой случай, который отличается и уникален, но если вы «в процессе написания нового приложения», то я бы не стал беспокоиться о компиляторе JIT. Там, вероятно, будет глупая петля, которую можно избежать где-нибудь, которая купит вам 100-кратное повышение производительности, которое вы получите от попытки догадаться о JIT. Напоминает мне о проблемах, с которыми мы столкнулись в написании нашего ORM, мы посмотрим на код и подумаем, что мы могли бы вырвать из него пару машинных инструкций … конечно, код затем отключился и подключился к серверу базы данных по сети , таким образом, мы сокращали микросекунды от процесса, который был ограничен миллисекундами где-то в другом месте.

Универсальное правило настройки производительности … Если вы не измеряли свою производительность, вы не знаете, где ваши узкие места, вы просто думаете, что знаете … и вы, вероятно, ошибаетесь.

О ответе Quibblesome:

Я попытался запустить следующий код в своем Windows 7 x64 в режиме Release без отладчика, и NullReferenceException никогда не было брошено .

 using System; using System.Threading; namespace EventsMultithreadingTest { public class Program { private static Action _delegate = new Action(Program_Event); public static event Action Event; public static void Main(string[] args) { Thread thread = new Thread(delegate() { while (true) { Action ev = Event; if (ev != null) { ev.Invoke(null); } } }); thread.Start(); while (true) { Event += _delegate; Event -= _delegate; } } static void Program_Event(object obj) { object.Equals(null, null); } } } 

Я считаю, что 64 JIT не полностью разработаны / портированы, чтобы использовать преимущества таких 64-битных процессоров архитектуры, поэтому у них есть проблемы, вы можете получать «эмулированные» действия своих сборок, которые могут вызывать проблемы и неожиданное поведение. Я бы рассмотрел случаи, когда этого можно избежать и / или, может быть, посмотреть, есть ли хороший быстрый компилятор 64 c ++ для написания критических вычислений и алгоритмов времени. Но даже если у вас возникли трудности с поиском информации или у вас нет времени на чтение скомпрометированного кода, я вполне уверен, что извлечение тяжелых вычислений за пределы управляемого кода уменьшит любые проблемы, которые могут возникнуть у вас, и повысить производительность [несколько уверены, что вы уже делаете это но просто упомянуть :)]

Профилировщик не должен существенно влиять на ваши результаты синхронизации. Если накладные расходы профилировщика действительно «значительны», вы, вероятно, не сможете выжать гораздо больше скорости из своего кода и должны думать о поиске узких мест вашего оборудования (диска, ОЗУ или процессора?) И модернизации. (Похоже, что вы связаны с CPU, так что с чего начать)

В общем случае .net и JIT освобождают вас от большинства проблем с переносом на 64 бит. Как вы знаете, существуют эффекты, относящиеся к размеру регистра (изменения использования памяти, сортировка по собственному коду, необходимость в том, чтобы все части программы были встроенными 64-битными assemblyми) и некоторые различия в производительности (большая карта памяти, больше регистров, более широкие шины и т. д.), поэтому я не могу сказать вам ничего больше, чем вы уже знаете на этом фронте. Другие проблемы, которые я видел, – это ОС, а не C # – теперь есть разные укрытия реестра для 64-разрядных и WOW64-приложений, поэтому некоторые обращения к реестру должны быть тщательно написаны.

Как правило, плохо думать о том, что JIT будет делать с вашим кодом и попытаться настроить его для лучшей работы, поскольку JIT, вероятно, изменится с помощью .net 4 или 5 или 6, и ваши «оптимизации» могут стать неэффективными, или, что еще хуже, ошибки. Также имейте в виду, что JIT компилирует код специально для процессора, на котором он работает, поэтому потенциальное улучшение на вашем ПК разработки может быть не улучшением на другом ПК. То, что вам удастся использовать сегодня JIT на сегодняшнем процессоре, может укусить вас через несколько лет, когда вы что-то обновите.

В частности, вы указываете, что «свойства не встроены в x64». К тому моменту, когда вы запустили всю свою кодовую базу, превратив все свои свойства в поля, вполне может быть новый JIT для 64-битного, который делает встроенные свойства. Действительно, он может работать лучше, чем ваш «обходной» код. Пусть Microsoft оптимизирует это для вас.

Вы справедливо указываете, что ваш профиль памяти может измениться. Таким образом, вам может потребоваться больше оперативной памяти, более быстрых дисков для виртуальной памяти и больших кэшей процессора. Все проблемы с оборудованием. Вы можете уменьшить эффект, используя (например) Int32, а не int, но это может не иметь большого значения и может потенциально повредить производительность (так как ваш процессор может обрабатывать собственные 64-битные значения более эффективно, чем 32-битные значения половинного размера ).

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

Так что вас действительно беспокоит? Возможно, время вашего кода на 32-битном ПК, а затем время, когда он выполняет ту же задачу на 64-битном ПК. Есть ли полчаса разницы за 4 часа? Или разница составляет всего 3 секунды? Или 64-битный ПК работает быстрее? Возможно, вы ищете решения проблем, которые не существуют.

Поэтому вернемся к обычному, более универсальному, совету. Профиль и время для выявления узких мест. Посмотрите на алгоритмы и математические процессы, которые вы применяете, и попытайтесь улучшить или заменить их более эффективными. Убедитесь, что ваш многопоточный подход помогает, а не наносит ущерб вашей производительности (т. Е. Избегает ожидания и блокировки). Попытайтесь уменьшить выделение / освобождение памяти – например, повторно использовать объекты, а не заменять их новыми. Попытайтесь уменьшить использование частых вызовов функций и виртуальных функций. Переключитесь на C ++ и избавитесь от наложенных накладных расходов на garbage collection, проверку границ и т. Д., Которые накладывает .net. Хммм. Ничего из этого не имеет ничего общего с 64-битным, не так ли?

Я не знаком с 64-разрядными проблемами, но у меня есть один комментарий:

Мы должны забыть о небольшой эффективности, скажем, около 97% времени: преждевременная оптимизация – корень всего зла. – Дональд Кнут