В настоящее время я работаю над трассировщиком лучей на C# как хобби. Я пытаюсь добиться приличной скорости рендеринга, реализуя некоторые трюки из реализации на C++, и столкнулся с проблемой.
Объекты в сценах, которые визуализирует трассировщик лучей, хранятся в структуре KdTree, а узлы дерева, в свою очередь, хранятся в массиве. Оптимизация, с которой у меня возникают проблемы, заключается в попытке разместить как можно больше узлов дерева в строке кеша. Один из способов сделать это - чтобы узлы содержали указатель только на левый дочерний узел. Тогда подразумевается, что правый дочерний элемент следует сразу за левым в массиве.
Узлы являются структурами, и во время построения дерева они успешно помещаются в массив классом статического диспетчера памяти. Когда я начинаю обходить дерево, сначала кажется, что все работает нормально. Затем в какой-то момент в начале рендеринга (каждый раз примерно в одном и том же месте) левый дочерний указатель корневого узла внезапно указывает на нулевой указатель. Я пришел к выводу, что сборщик мусора переместил структуры, поскольку массив лежит в куче.
Я пробовал несколько способов закрепить адреса в памяти, но ни один из них, похоже, не действует в течение всего времени жизни приложения, как мне нужно. Ключевое слово fixed, похоже, помогает только при вызовах отдельных методов, а объявление «фиксированных» массивов может быть сделано только для простых типов, которыми не является узел. Есть ли хороший способ сделать это, или я просто слишком далеко пошел по пути того, для чего C# не предназначался.
Кстати, переход на С ++, возможно, лучший выбор для высокопроизводительной программы, не вариант.





Действительно ли запрещено хранить пару ссылки на массив и индекса?
Во-первых, если вы обычно используете C#, вы не можете внезапно получить пустую ссылку из-за того, что сборщик мусора перемещает материал, потому что сборщик мусора также обновляет все ссылки, поэтому вам не нужно беспокоиться о его перемещении.
Вы можете закрепить вещи в памяти, но это может вызвать больше проблем, чем решить. Во-первых, это мешает сборщику мусора правильно сжимать память и таким образом может повлиять на производительность.
Одна вещь, которую я хотел бы сказать из вашего сообщения, заключается в том, что использование структур может не улучшить производительность, как вы надеетесь. C# не может встроить какие-либо вызовы методов, связанных со структурами, и хотя они исправили это в своей последней бета-версии среды выполнения, структуры часто не работают так хорошо.
Лично я бы сказал, что подобные уловки C++ обычно не слишком хорошо переносятся на C#. Возможно, вам придется немного научиться отпускать; могут быть и другие, более тонкие способы повышения производительности;)
Что на самом деле делает ваш диспетчер статической памяти? Если он не делает что-то небезопасное (P / Invoke, небезопасный код), поведение, которое вы видите, является ошибкой в вашей программе, а не из-за поведения среды CLR.
Во-вторых, что вы подразумеваете под «указателем» в отношении связей между структурами? Вы буквально имеете в виду небезопасный указатель KdTree *? Не делай этого. Вместо этого используйте индекс в массиве. Поскольку я ожидаю, что все узлы одного дерева хранятся в одном массиве, вам не понадобится отдельная ссылка на массив. Подойдет всего один индекс.
Наконец, если вам действительно действительно необходимо использовать указатели KdTree *, тогда ваш диспетчер статической памяти должен выделить большой блок, например, Marshal.AllocHGlobal или другой неуправляемый источник памяти; он должен обрабатывать этот большой блок как массив KdTree (т.е. индексировать KdTree * в стиле C) и он должен перераспределять узлы из этого массива, нажимая «свободный» указатель.
Если вам когда-нибудь придется изменить размер этого массива, вам, конечно же, придется обновить все указатели.
Основной урок здесь заключается в том, что небезопасные указатели и управляемая память смешивают нет вне «фиксированных» блоков, которые, конечно, имеют сходство с кадрами стека (т.е. когда функция возвращается, закрепленное поведение исчезает). Есть способ закрепить произвольные объекты, такие как ваш массив, с помощью GCHandle.Alloc (yourArray, GCHandleType.Pinned), но вы почти наверняка не захотите идти по этому пути.
Вы получите более разумные ответы, если более подробно опишете, чем занимаетесь.
What is your static memory manager actually doing? Unless it is doing something unsafe (P/Invoke, unsafe code), the behaviour you are seeing is a bug in your program, and not due to the behaviour of the CLR.
Фактически я говорил о небезопасных указателях. Я хотел что-то вроде Marshal.AllocHGlobal, но с временем жизни, превышающим один вызов метода. Поразмыслив, кажется, что просто использование индекса - правильное решение, поскольку я, возможно, слишком увлекся имитацией кода C++.
One thing I would say from your post is that using structs may not help performance as you hope. C# fails to inline any method calls involving structs, and even though they've fixed this in their latest run-time beta, structs frequently don't perform that well.
Я немного изучил это и вижу, что это было исправлено в .NET 3.5SP1; Я предполагаю, что это то, что вы называли бета-версией времени выполнения. Фактически, теперь я понимаю, что это изменение привело к удвоению моей скорости рендеринга. Теперь структуры агрессивно встроены, что значительно улучшает их производительность в системах X86 (у X64 заранее была лучшая производительность структур).
Если вы хотите это сделать, В самом деле, вы можете использовать метод GCHandle.Alloc, чтобы указать, что указатель должен быть закреплен без автоматического освобождения в конце области, как фиксированный оператор.
Но, как говорили другие, это оказывает чрезмерное давление на сборщик мусора. Как насчет того, чтобы просто создать структуру, которая удерживает пару ваших узлов, а затем управлять массивом NodePairs, а не массивом узлов?
Если вы действительно хотите иметь полностью неуправляемый доступ к фрагменту памяти, вам, вероятно, будет лучше выделить память непосредственно из неуправляемой кучи, чем постоянно закреплять часть управляемой кучи (это мешает куче правильно сама компактная). Один из быстрых и простых способов сделать это - использовать метод Marshal.AllocHGlobal.