Закрепление массивов указателей в памяти - PullRequest
1 голос
/ 01 сентября 2008

В настоящее время я работаю над трассировкой лучей в C # как хобби-проект. Я пытаюсь добиться приличной скорости рендеринга, реализовав некоторые трюки из реализации c ++, и столкнулся с проблемой.

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

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

Я пробовал несколько вещей, чтобы закрепить адреса в памяти, но ни одна из них не работает в течение всего времени жизни приложения, сколько мне нужно. Ключевое слово «fixed», по-видимому, помогает только при вызовах одного метода, а объявление «фиксированных» массивов может быть сделано только для простых типов, которые не являются узлами. Есть хороший способ сделать это, или я просто слишком далеко иду по пути вещей, для которых C # не предназначался.

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

Ответы [ 5 ]

4 голосов
/ 01 сентября 2008

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

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

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

Лично я бы сказал, что подобные трюки C ++ обычно не слишком хорошо переносятся в C #. Возможно, вам придется научиться отпускать немного; Могут быть и другие, более тонкие способы улучшить производительность;)

2 голосов
/ 02 сентября 2008

Что на самом деле делает ваш менеджер статической памяти? Если это не делает что-то небезопасное (P / Invoke, небезопасный код), то поведение, которое вы видите, является ошибкой в ​​вашей программе, а не из-за поведения CLR.

Во-вторых, что вы подразумеваете под «указателем» в отношении связей между структурами? Вы буквально имеете в виду небезопасный указатель KdTree *? Не делай этого. Вместо этого используйте индекс в массиве. Поскольку я ожидаю, что все узлы для одного дерева хранятся в одном и том же массиве, вам не понадобится отдельная ссылка на массив. Подойдет только один индекс.

Наконец, если вы действительно должны использовать указатели KdTree *, тогда ваш менеджер статической памяти должен выделить большой блок, используя, например, Marshal.AllocHGlobal или другой неуправляемый источник памяти; он должен оба обрабатывать этот большой блок как массив KdTree (то есть индексировать KdTree * C-стиль) и , он должен перераспределять узлы из этого массива путем увеличения «свободного» указателя.

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

Основной урок здесь заключается в том, что небезопасные указатели и управляемая память не смешивают вне «фиксированных» блоков, которые, конечно, имеют сходство кадров стека (т.е. когда функция возвращается, закрепленное поведение исчезает) , Существует способ закрепления произвольных объектов, таких как ваш массив, с помощью GCHandle.Alloc (yourArray, GCHandleType.Pinned), но вы почти наверняка не хотите идти по этому пути.

Вы получите более разумные ответы, если опишите более подробно, что делаете.

1 голос
/ 02 сентября 2008

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

Но, как говорили другие люди, это оказывает чрезмерное давление на сборщик мусора. Как насчет простого создания структуры, которая содержит пару ваших узлов, и затем управления массивом NodePairs, а не массивом узлов?

Если вы действительно хотите иметь полностью неуправляемый доступ к фрагменту памяти, вам, вероятно, лучше распределить память непосредственно из неуправляемой кучи, а не фиксировать часть управляемой кучи навсегда (это предотвращает кучи в состоянии правильно компактировать себя). Одним из быстрых и простых способов сделать это было бы использование метода Marshal.AllocHGlobal.

0 голосов
/ 02 сентября 2008

Что на самом деле делает ваш менеджер статической памяти? Если это не делает что-то небезопасное (P / Invoke, небезопасный код), поведение, которое вы видите, является ошибкой в ​​вашей программе, а не из-за поведения CLR.

Я на самом деле говорил о небезопасных указателях. Я хотел что-то вроде Marshal.AllocHGlobal, хотя время жизни превышало один вызов метода. Размышляя, кажется, что просто использование индекса - правильное решение, так как я, возможно, слишком увлекся имитацией кода C ++.

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

Я немного разбирался в этом и вижу, что это исправлено в .NET 3.5SP1; Я предполагаю, что это то, что вы называли бета-тестом во время выполнения. Фактически, теперь я понимаю, что это изменение привело к удвоению моей скорости рендеринга. Теперь структуры агрессивно встроены, что значительно повышает их производительность в системах X86 (X64 заранее улучшил производительность структур).

0 голосов
/ 01 сентября 2008

Действительно ли запрещено хранить пару ссылок на массив и индекс?

...