Распределение памяти в Linux неблокирует? - PullRequest
19 голосов
/ 05 января 2011

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

, например

struct Node {
    int a,b;
};

...

Node foo = new Node();

Если несколько потоков попытались создать новый узел, и если один из них был приостановлен ОС в середине выделения, заблокирует ли это выполнение других потоков?

Причина, по которой я спрашиваю, заключается в том, что у меня была параллельная структура данных, которая создавала новые узлы. Затем я изменил алгоритм, чтобы переработать узлы. Производительность двух алгоритмов на 24-ядерном компьютере была практически одинаковой. Однако затем я создал программу помех, которая работала на всех ядрах системы, чтобы создать как можно больше преимуществ ОС. Производительность алгоритма, который создал новые узлы, снизилась в 5 раз по сравнению с алгоритмом, который перерабатывал узлы.

Мне любопытно узнать, почему это произойдет.

Спасибо.

* Редактировать: указание на код для распределителя памяти c ++ для linux также будет полезно. Я пытался посмотреть, прежде чем опубликовать этот вопрос, но мне было трудно его найти.

Ответы [ 5 ]

6 голосов
/ 10 января 2011

мне кажется, что если ваше интерференционное приложение использовало new / delete (malloc / free), тогда интерференционное приложение больше мешало бы тесту без повторного использования.Но я не знаю, как реализован твой тест помех.

В зависимости от того, как вы перерабатываете (т. Е. Если вы используете мьютексы pthread, не дай бог), ваш код рециркуляции может быть медленным (gcc atomic ops будет в 40 раз быстрее при реализации рецикла).

Malloc, в некоторых вариациях в течение длительного времени, по крайней мере, на некоторых платформах, знал о потоках.Используйте ключи компилятора на gcc, чтобы убедиться, что вы его получили.Более новые алгоритмы поддерживают пулы небольших порций памяти для потока каждого потока , поэтому блокировка отсутствует или незначительна, если в вашем потоке имеется маленький элемент.Я упростил это, и это зависит от того, какой malloc использует ваша система.Плюс, если вы идете и выделяете миллионы предметов для проведения теста ... ну, тогда вы не увидите этого эффекта, потому что небольшие пулы предметов ограничены по размеру.Или, может быть, вы будете.Я не знаю.Если вы освободили элемент сразу после размещения, вы с большей вероятностью увидите его.Освобожденные мелкие элементы возвращаются в списки мелких предметов, а не в общую кучу.Хотя «что происходит, когда поток B освобождает элемент, выделенный потоком A», - это проблема, которая может или не может быть решена в вашей версии malloc и не может быть решена неблокирующим образом.Несомненно, если вы не освободили сразу во время большого теста, то поток должен был бы пополнить свой список небольших элементов много раз.Это может заблокировать, если более одного потока пытается.Наконец, в какой-то момент куча вашего процесса запросит у системы кучную память, которая, очевидно, может блокироваться.

Так вы используете небольшие элементы памяти?Что касается вашего malloc, я не знаю, что будет маленьким, но если вы <1k, это точно маленький.Вы выделяете и освобождаете один за другим или выделяете тысячи узлов, а затем освобождаете тысячи узлов?Ваше приложение помех вмешивалось?Все это повлияет на результаты.</p>

Как перерабатывать с атомарными операциями (CAS = сравнить и поменять местами):

Сначала добавьте pNextFreeNode к вашему объекту узла.Я использовал void *, вы можете использовать свой тип.Этот код предназначен для 32-битных указателей, но работает и для 64-битных.Затем создайте глобальную кучу для переработки.

void *_pRecycleHead; // global head of recycle list. 

Добавьте для переработки кучу:

void *Old;
while (1) { // concurrency loop
  Old = _pRecycleHead;  // copy the state of the world. We operate on the copy
  pFreedNode->pNextFreeNode = Old; // chain the new node to the current head of recycled items
  if (CAS(&_pRecycleHead, Old, pFreedNode))  // switch head of recycled items to new node
    break; // success
}

удалить из кучи:

void *Old;
while (Old = _pRecycleHead) { // concurrency loop, only look for recycled items if the head aint null
  if (CAS(&_pRecycleHead, Old, Old->pNextFreeNode))  // switch head to head->next.
    break; // success
}
pNodeYoucanUseNow = Old;

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

При удалении из кучи выше есть условие гонки, если вы быстро добавляете и удаляете тот же элемент.Мы решаем это путем добавления версии # к данным CAS'able.Если вы сделаете версию # в то же время, что и указатель на верхнюю часть стопки, вы выиграете.Используйте союз.Для CAS 64 битов ничего не стоит.

union TRecycle {
  struct {
    int iVersion;
    void *pRecycleHead;
  } ;  // we can set these.  Note, i didn't name this struct.  You may have to if you want ANSI
  unsigned long long n64;  // we cas this
}

Примечание. Вам придется перейти на 128-битную структуру для 64-битной ОС.так что глобальная куча рециркуляции теперь выглядит следующим образом:

TRecycle _RecycleHead;

Добавить в колоду рециркуляции:

while (1) { // concurrency loop
  TRecycle New,Old;
  Old.n64 = _RecycleHead.n64;  // copy state
  New.n64 = Old.n64;  // new state starts as a copy
  pFreedNode->pNextFreeNode = Old.pRecycleHead;  // link item to be recycled into recycle pile
  New.pRecycleHead = pFreedNode;  // make the new state
  New.iVersion++;  // adding item to list increments the version.
  if (CAS(&_RecycleHead.n64, Old.n64, New.n64))  // now if version changed...we fail
    break; // success
}

удалить из колоды:

while (1) { // concurrency loop
  TRecycle New,Old;
  Old.n64 = _RecycleHead.n64;  // copy state
  New.n64 = Old.n64;  // new state starts as a copy
  New.pRecycleHead = New.pRecycledHead.pNextFreeNode;  // new will skip over first item in recycle list so we can have that item.
  New.iVersion++;  // taking an item off the list increments the version.
  if (CAS(&_RecycleHead.n64, Old.n64, New.n64))  // we fail if version is different.
    break; // success
}
pNodeYouCanUseNow = Old.pRecycledHead;

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

3 голосов
/ 05 января 2011

В многопоточных системах malloc() и free()new / delete) обычно используют примитивы синхронизации, чтобы сделать их безопасными для вызова из нескольких потоков.

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

2 голосов
/ 05 января 2011

На самом деле это почти то же самое, что и этот вопрос .

По сути, malloc не определен как поточно-ориентированный, но разработчики могут свободно добавлять реализацию, чтобы сделать егоПоток безопасно.Из вашего описания звучит так, будто ваша конкретная версия такова.

Чтобы быть уверенным, словами Оби-Вана: «Используй источник, Люк».Источник malloc будет вокруг, и его, как правило, читать довольно просто.

@ Марк, вы можете получить стандартный источник GNU libc по

$ git clone git://sourceware.org/git/glibc.git
$ cd glibc
$ git checkout --track -b glibc-2_11-branch origin/release/2.11/master

См. Также здесь .Помните, что malloc находится в разделе 3 руководства - это библиотечная функция, поэтому ее не будет в исходниках вашего ядра.Однако вам может понадобиться прочитать brk, sbrk, getrlimit и setrlimit и т.п., чтобы узнать, что делает ядро.

Еще одна ссылка: Проект GCC .

Хорошо, еще один (я могу остановиться в любое время): вот страница , с которой вы можете скачать исходники.Распакуйте файл, и вы должны найти его в ./malloc/malloc.c.

1 голос
/ 05 января 2011

Этот вопрос имеет несколько хороших ответов: В многопоточном C / C ++ блокирует ли malloc / new кучу при выделении памяти.

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

gcc new является поточно-ориентированным, если вы компилируете с поддержкой pthreads, но это не совсем то, о чем вы просите.

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

0 голосов
/ 05 января 2011

Краткий ответ: Нет.

Одна нить может находиться в середине new node(), а другая нить также может делать new node().Первый поток может быть приостановлен, а второй может закончиться первым.Все в порядке.(при условии, что в вашем конструкторе ничего не использует мьютекс)

Более длинный ответ: Многопоточность - это джунгли.Небезопасный поток может нормально работать в течение десятилетия, а затем каждый день будет выходить из строя в течение недели.Гоночные условия могут не вызвать каких-либо проблем на вашей машине, но могут взорваться на машине клиента.Многопоточные приложения вводят уровень неопределенности, который требует дополнительных усилий для написания и понимания.

Итак, почему эти две программы в один прекрасный день будут работать почти одинаково и сильно различаться в случае конфликта ЦП?Я не знаю.new не блокирует выполнение других потоков new, так что это не так.Я подозреваю, что с дополнительными издержками new / delete ОС имеет больше возможностей для вытеснения вашей программы (и, возможно, даже большей вероятности сделать это).Таким образом, когда нет никаких помех, две программы получают процессор столько, сколько они хотят, и работают нормально - но когда процессор является дефицитным ресурсом, новая / удаляемая программа сталкивается чаще, чем утилизационная.Увидеть?Платит за переработку; -)

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