Помогает ли мне создание новых процессов для перемещения большого дерева?

Подумайте об этом как о генеалогическом древе, у отца есть дети, у этих детей есть дети, у этих детей есть дети и т.д... Таким образом, у меня есть рекурсивная функция, в которой отец использует Recursion для получения дочерних элементов, а теперь просто распечатывает их для вывода окна вывода... Но в какой-то момент (через один час после запуска и печати, например, 26000 строк) это дает мне исключение StackOverFlowException.

Итак, у меня действительно нехватает памяти? хммм? то не следует ли мне получать исключение "Out of memory"? на других сообщениях, которые я обнаружил, когда люди говорили, что количество рекурсивных вызовов слишком велико, вы все равно можете получить исключение SOF...

Во всяком случае, моя первая мысль заключалась в том, чтобы разбить дерево на более мелкие подграфы. Я знаю, что мой корневой отец всегда имеет этих пяти детей, поэтому вместо того, чтобы вызывать мой метод один раз с переданным ему корнем, Я сказал, что хорошо назовите его пять раз с Kids of root. Проходит к нему. Это помогло мне подумать... но все-таки один из них настолько велик - 26000 строк, когда он падает, и все еще есть эта проблема.

Как насчет областей приложений и создания новых процессов во время выполнения на определенном уровне глубины? Помогает ли это?

Как создать собственный стек и использовать его вместо рекурсивных методов? это помогает?

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

private void MyLoadMethod(string conceptCKI)
{

// make some script calls to DB, so that moTargetConceptList2 will have Concept-Relations for the current node. 

            // when this is zero, it means its a leaf. 
            int numberofKids = moTargetConceptList2.ConceptReltns.Count();
            if (numberofKids == 0)
                return;
            for (int i = 1; i <= numberofKids; i++)
            {
                oUCMRConceptReltn = moTargetConceptList2.ConceptReltns.get_ItemByIndex(i, false);

                //Get the concept linked to the relation concept
                if (oUCMRConceptReltn.SourceCKI == sConceptCKI)
                {
                    oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.TargetCKI, false);
                }
                else
                {
                    oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.SourceCKI, false);
                }

                //builder.AppendLine("\t" + oConcept.PrimaryCTerm.SourceString);
                Debug.WriteLine(oConcept.PrimaryCTerm.SourceString);

                MyLoadMethod(oConcept.ConceptCKI);
            }
        }

Ответ 1

StackOverflowException сильно отличается от OutOfMemoryException.

OOME означает, что для процесса вообще нет памяти. Это может быть при попытке создать новый поток с новым стеком или при попытке создать новый объект в куче (и в нескольких других случаях).

SOE означает, что стек потоков - по умолчанию 1M, хотя он может быть установлен по-разному при создании потока или если исполняемый файл имеет другой умолчаний; поэтому потоки ASP.NET имеют 256k по умолчанию, а не 1M - были исчерпаны. Это может быть вызвано методом или назначением локального.

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

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

Если вы используете это 1M пространства, вы получаете StackOverflowException. Поскольку 1M (или даже 256k) - это большой объем памяти для такого использования (мы не помещаем действительно большие объекты в стек), три вещи, которые могут вызвать SOE:

  • Кто-то подумал, что было бы неплохо оптимизировать, используя stackalloc, когда это было не так, и они быстро использовали этот 1M.
  • Кто-то подумал, что было бы хорошей идеей оптимизировать, создав поток с меньшим, чем обычно, стекю, когда это было не так, и они используют этот маленький стек.
  • Рекурсивный (прямо или через несколько шагов) вызов попадает в бесконечный цикл.
  • Это было не совсем бесконечно, но оно было достаточно большим.

У вас есть случай 4. 1 и 2 довольно редки (и вам нужно быть достаточно преднамеренным, чтобы рисковать ими). Случай 3 является наиболее распространенным явлением и указывает на ошибку в том, что рекурсия не должна быть бесконечной, но ошибка означает, что это так.

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

Теперь для случая 4 у нас есть два варианта. В очень редких случаях, когда мы получили слишком много звонков, мы можем запустить его в потоке с большим стеком. Это не относится к вам.

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

private static int Fac(int n)
{
  return n <= 1 ? 1 : n * Fac(n - 1);
}

Вместо использования рекурсии мы выполняем один и тот же метод:

private static int Fac(int n)
{
  int ret = 1;
  for(int i = 1; i <= n, ++i)
    ret *= i;
  return ret;
}

Вы можете понять, почему здесь меньше места для стека. Итеративная версия также будет быстрее 99% времени. Теперь представьте, что мы случайно вызываем Fac(n) в первом и оставляем ++i во втором - эквивалентную ошибку в каждом, и она вызывает SOE в первой и программу, которая никогда не останавливается во второй.

В отношении того кода, о котором вы говорите, где вы продолжаете производить все больше и больше результатов по ходу работы на основе предыдущих результатов, вы можете разместить полученные результаты в структуре данных (Queue<T> и Stack<T> оба хорошо служат для многих случаев), поэтому код становится чем-то вроде:

private void MyLoadMethod(string firstConceptCKI)
{
  Queue<string> pendingItems = new Queue<string>();
  pendingItems.Enqueue(firstConceptCKI);
  while(pendingItems.Count != 0)
  {
    string conceptCKI = pendingItems.Dequeue();
    // make some script calls to DB, so that moTargetConceptList2 will have Concept-Relations for the current node. 
    // when this is zero, it means its a leaf. 
    int numberofKids = moTargetConceptList2.ConceptReltns.Count();
    for (int i = 1; i <= numberofKids; i++)
    {
        oUCMRConceptReltn = moTargetConceptList2.ConceptReltns.get_ItemByIndex(i, false);

        //Get the concept linked to the relation concept
        if (oUCMRConceptReltn.SourceCKI == sConceptCKI)
        {
            oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.TargetCKI, false);
        }
        else
        {
            oConcept = moTargetConceptList2.ItemByKeyConceptCKI(oUCMRConceptReltn.SourceCKI, false);
        }

        //builder.AppendLine("\t" + oConcept.PrimaryCTerm.SourceString);
        Debug.WriteLine(oConcept.PrimaryCTerm.SourceString);

        pendingItems.Enque(oConcept.ConceptCKI);
    }
  }
}

(Я еще не полностью проверил это, просто добавил очередь, а не рекурсивный код в ваш вопрос).

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

Изменить: лучше добавить, как использовать HashSet для поиска дубликатов. Сначала настройте HashSet, это может быть просто:

HashSet<string> seen = new HashSet<string>();

Или, если строки используются без учета регистра, вам будет лучше:

HashSet<string> seen = new HashSet<string>(StringComparison.InvariantCultureIgnoreCase) // or StringComparison.CurrentCultureIgnoreCase if that closer to how the string is used in the rest of the code.

Затем, прежде чем вы начнете использовать строку (или, возможно, перед тем, как перейти к ее добавлению в очередь, у вас есть одно из следующих значений:

Если повторяющихся строк не должно быть:

if(!seen.Add(conceptCKI))
  throw new InvalidOperationException("Attempt to use \" + conceptCKI + "\" which was already seen.");

Или если повторяющиеся строки действительны, и мы просто хотим пропустить выполнение второго вызова:

if(!seen.Add(conceptCKI))
  continue;//skip rest of loop, and move on to the next one.

Ответ 2

Как создать собственный стек и использовать его вместо рекурсивных методов? это помогает?

Да!

Когда вы создаете экземпляр Stack<T>, это будет жить в куче и может увеличиваться сколь угодно большим (пока не закончится адресная память).

Если вы используете рекурсию, вы используете стек вызовов. Стек вызовов намного меньше кучи. По умолчанию используется 1 МБ пространства стека вызовов на поток. Обратите внимание, что это можно изменить, но это не рекомендуется.

Ответ 3

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

Для тестирования:

Объявить глобальную переменную для хранения работоспособных объектов:

private Dictionary<int,object> _operableIds = new Dictionary<int,object>();

...
private void Start()
{
    _operableIds.Clear();
    Recurtion(start_id);
}
...
private void Recurtion(int object_id)
{
   if(_operableIds.ContainsKey(object_id))
      throw new Exception("Have a ring!");
   else
     _operableIds.Add(object_id, null/*or object*/);

   ...
   Recurtion(other_id)
   ...
   _operableIds.Remove(object_id);
}