C # производительность меняется из-за памяти - PullRequest
8 голосов
/ 03 апреля 2012

Надеюсь, что это действительный пост, это комбинация проблем C # и аппаратного обеспечения.

Я тестирую наш сервер, потому что мы обнаружили проблемы с производительностью нашей квантовой библиотеки (написанной на C #). Я смоделировал те же проблемы с производительностью с некоторым простым кодом C #, выполняющим очень интенсивное использование памяти.

Код ниже находится в функции, которая порождается из пула потоков, максимум до 32 потоков (потому что наш сервер имеет 4x ЦП x 8 ядер в каждом).

Это все на .Net 3.5

Проблема в том, что мы получаем совершенно отличную производительность. Я запускаю функцию ниже 1000 раз. Среднее время, необходимое для выполнения кода, может быть, скажем, 3,5 с, но самое быстрое будет только 1,2 с, а самое медленное будет 7 с - для точно такой же функции!

Я составил график использования памяти в зависимости от времени, и, похоже, нет никакой связи с включением GC.

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

Мне было интересно, может ли это быть другой проблемой .net или C #, или это связано с нашим оборудованием? Было бы то же самое, если бы я использовал C ++ или Java ?? Мы используем 4x Intel x7550 с 32 ГБ оперативной памяти. Есть ли способ обойти эту проблему вообще?

Stopwatch watch = new Stopwatch();
watch.Start();
List<byte> list1 = new List<byte>();
List<byte> list2 = new List<byte>();
List<byte> list3 = new List<byte>();


int Size1 = 10000000;
int Size2 = 2 * Size1;
int Size3 = Size1;

for (int i = 0; i < Size1; i++)
{
    list1.Add(57);
}

for (int i = 0; i < Size2; i = i + 2)
{
    list2.Add(56);
}

for (int i = 0; i < Size3; i++)
{
    byte temp = list1.ElementAt(i);
    byte temp2 = list2.ElementAt(i);
    list3.Add(temp);
    list2[i] = temp;
    list1[i] = temp2;
}
watch.Stop();

(код предназначен только для того, чтобы подчеркнуть память)

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

РЕДАКТИРОВАТЬ: я уменьшил "size1" до 100000, который в основном не использует много памяти, и я все еще получаю много дрожания. Это говорит о том, что это не объем передаваемой памяти, а частота захвата памяти?

Ответы [ 6 ]

4 голосов
/ 03 апреля 2012

Недостаточно продолжения, но вот некоторые области, которые нужно начать искать:

  • Изменчивость является результатом внутреннего состояния ГХ. GC динамически управляет размерами различных пулов. Если вы начнете с разных размеров пула, вы получите другое поведение GC во время прогонов.
  • Шаблоны муаров в планировании потоков. В зависимости от случайных изменений в последовательности потоков, вы можете иметь более или менее благоприятные модели конкуренции. Если есть периодичность, это может привести к усиленному эффекту, похожему на конструктивное вмешательство.
  • Ложный обмен. Если у вас есть два потока, оба из которых обращаются к адресам памяти, которые достаточно близки для размещения в кэше процессора, вы увидите заметное снижение производительности, поскольку процессорам придется тратить много времени на повторную синхронизацию своих кэшей. В зависимости от того, как вы организовываете свои данные и выделяете потоки для их обработки, вы можете получить шаблоны ложного обмена, основанные на различиях в начале.
  • Другой процесс в системе занимает процессорное время. Возможно, вы захотите использовать меру времени пользовательского режима процесса вместо настенного времени. (Где-то есть доступ к этому в классе Process).
  • Машина работает близко к пределу полной физической памяти. Перестановка на диск происходит более или менее случайным образом.
1 голос
/ 03 апреля 2012

Вы попали в довольно фундаментальные ограничения машины здесь.У вас много ядер, но все еще есть только одна шина памяти.Таким образом, если ваши потоки выполняют много операций перетасовки, то они, вероятно, будут ограничены пропускной способностью этой единственной шины.Это закон Амдала в действии.

Существует одна возможная оптимизация, это зависит от типа операционной системы, на которой работает эта машина.Это серверное оборудование, но если у вас несерверная версия Windows, сборщик мусора будет работать в режиме рабочей станции.Затем вы можете использовать элемент <gcServer> в файле приложения .config, чтобы запросить серверную версию коллектора.Он использует несколько куч, поэтому потоки не будут бороться за блокировку кучи GC так часто, когда они выделяют память.YMMV.

0 голосов
/ 04 апреля 2012

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

0 голосов
/ 03 апреля 2012

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

Я бы подключил профилировщик или настроил некоторые счетчики производительности Windows:

http://support.microsoft.com/kb/300504

Вы сможете добавить некоторые счетчики производительности, сосредоточенные на процессе. Вы можете посмотреть, сколько потоков запускается, использование памяти и т. Д. Я бы взял некоторые другие предложения здесь и измерил сценарий, который вы ищете. Если вы сбрасываете данные счетчика производительности в CSV-файл, вы можете даже довольно быстро отобразить результаты, чтобы получить хорошие данные для фактического анализа. Если вы можете найти, какая метрика меняется, в сценарии 1.2s против 7s, вы можете начать делать некоторые просвещенные предположения о том, что происходит, и продолжать оттачивать.

0 голосов
/ 03 апреля 2012

Убедитесь, что в конфигурации среды выполнения gcserver = true

0 голосов
/ 03 апреля 2012

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

Когда вы входите в цикл, ему нужно все больше и больше кусков непрерывной памяти, чтобы распределять новые массивы по мере роста списка.С одной нитью это довольно просто.С 2+ потоками, вы конкурируете за большие куски непрерывной памяти.Это будет запускать GC в случайное время, так как массивы становятся больше, а смежную память труднее найти.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...