Самостоятельный перезапуск MathKernel — возможен ли он в Mathematica?

Этот вопрос исходит из недавнего вопроса "Правильный способ ограничения Mathematica использование памяти?"

Интересно, можно ли программно перезапустить MathKernel, сохраняя текущий процесс FrontEnd подключенным к новому процессу MathKernel и оценивая некоторый код в новом сеансе MathKernel? Я имею в виду «прозрачный» перезапуск, который позволяет пользователю продолжать работу с FrontEnd, имея новый свежий процесс MathKernel с некоторым оцениваемым/оценивающим в нем кодом из предыдущего ядра?

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


person Alexey Popkov    schedule 23.10.2011    source источник
comment
Вероятно, один из возможных способов решения проблемы включает в себя программный запуск нового ядра из FrontEnd, затем оценку кода в этом новом ядре, а затем закрытие старого ядра, оставляя новое ядро ​​работающим.   -  person Alexey Popkov    schedule 23.10.2011
comment
Как насчет управления ядром (назовем его B) другим (назовем его A) и использованием A в качестве супервизора? Конечно, это требует реорганизации кода. Но наверняка вы подумали об этом и отказались от подхода?   -  person acl    schedule 23.10.2011
comment
@acl Эта идея была первой (и единственной), которую я пробовал. И я уже реализовал такую ​​функциональность через чистый MathLink - но мой способ действительно очень сложный, крайне неэлегантный, полагается на большое количество недокументированных функций, которые потенциально зависят от версии. И код действительно огромен! Я надеюсь, что новая функциональность ScheduledTasks может дать гораздо более элегантный способ решить эту проблему.   -  person Alexey Popkov    schedule 23.10.2011


Ответы (6)


Из вчерашнего комментария Арноуда Бузинга в чате Stack Exchange Mathematica, полностью цитирующего:

В записной книжке, если у вас есть несколько ячеек, вы можете поместить «Выход» в отдельную ячейку и установить этот параметр:

SetOptions[$FrontEnd, "ClearEvaluationQueueOnKernelQuit" -> False]

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

-- Арно Бьюзинг

person Chris Degnen    schedule 06.12.2012

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

Этот файл является входным файлом MathKernel, C:\Temp\test4.m

Needs["JLink`"];
$FrontEndLaunchCommand="Mathematica.exe";
UseFrontEnd[
nb = NotebookOpen["C:\\Temp\\run.nb"];
SelectionMove[nb, Next, Cell];
SelectionEvaluate[nb];
];
Pause[8];
CloseFrontEnd[];
Pause[1];
UseFrontEnd[
nb = NotebookOpen["C:\\Temp\\run.nb"];
Do[SelectionMove[nb, Next, Cell],{12}];
SelectionEvaluate[nb];
];
Pause[8];
CloseFrontEnd[];
Print["Completed"]

Демонстрационная записная книжка C:\Temp\run.nb содержит две ячейки:

x1 = 0;
Module[{}, 
 While[x1 < 1000000, 
  If[Mod[x1, 100000] == 0, Print["x1=" <> ToString[x1]]]; x1++];
 NotebookSave[EvaluationNotebook[]];
 NotebookClose[EvaluationNotebook[]]]

Print[x1]
x1 = 0;
Module[{}, 
 While[x1 < 1000000, 
  If[Mod[x1, 100000] == 0, Print["x1=" <> ToString[x1]]]; x1++];
 NotebookSave[EvaluationNotebook[]];
 NotebookClose[EvaluationNotebook[]]]

Исходное ядро ​​открывает интерфейс и запускает первую ячейку, затем закрывает интерфейс, снова открывает его и запускает вторую ячейку.

Все это можно запустить либо путем вставки (за один раз) ввода MathKernel в сеанс ядра, либо его можно запустить из пакетного файла, например. C:\Temp\RunTest2.bat

@echo off
setlocal
PATH = C:\Program Files\Wolfram Research\Mathematica\8.0\;%PATH%
echo Launching MathKernel %TIME%
start MathKernel -noprompt -initfile "C:\Temp\test4.m"
ping localhost -n 30 > nul
echo Terminating MathKernel %TIME%
taskkill /F /FI "IMAGENAME eq MathKernel.exe" > nul
endlocal

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

person Chris Degnen    schedule 23.10.2011
comment
+1 Потрясающе! Но я чувствую, что должен быть более простой способ через плохо документированные токены FrontEnd, которые доступны через меню Evaluation в FrontEnd: Запустить ядро ​​(позволяет запустить новое ядро, которое уже настроено в диалоговом окне параметров конфигурации ядра), Ядро по умолчанию (позволяет чтобы установить другое ядро ​​по умолчанию для текущего ноутбука). Настоящая проблема заключается в том, как сконфигурировать новое ядро ​​с помощью диалогового окна параметров конфигурации ядра программно и как оценить в новом ядре некоторый код исходного ядра. - person Alexey Popkov; 23.10.2011

Возможно, для этого можно использовать параллельные вычислительные машины? Вот грубая установка, иллюстрирующая идею:

Needs["SubKernels`LocalKernels`"]

doSomeWork[input_] := {$KernelID, Length[input], RandomReal[]}

getTheJobDone[] :=
  Module[{subkernel, initsub, resultSoFar = {}}
  , initsub[] :=
      ( subkernel = LaunchKernels[LocalMachine[1]]
      ; DistributeDefinitions["Global`"]
      )
  ; initsub[]
  ; While[Length[resultSoFar] < 1000
    , DistributeDefinitions[resultSoFar]
    ; Quiet[ParallelEvaluate[doSomeWork[resultSoFar], subkernel]] /.
        { $Failed :> (Print@"Ouch!"; initsub[])
        , r_ :> AppendTo[resultSoFar, r]
        }
    ]
  ; CloseKernels[subkernel]
  ; resultSoFar
  ]

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

Чтобы попробовать это, оцените:

getTheJobDone[]

Чтобы продемонстрировать механизм восстановления, откройте окно Parallel Kernel Status и время от времени отключайте подядро. getTheJobDone почувствует боль и напечатает Ой! всякий раз, когда подядро умирает. Однако работа в целом продолжается, и возвращается окончательный результат.

Обработка ошибок здесь очень грубая и, вероятно, ее нужно будет усилить в реальном приложении. Кроме того, я не исследовал, будут ли действительно серьезные ошибки в подядрах (например, нехватка памяти) иметь неблагоприятное влияние на основное ядро. Если это так, то, возможно, подъядра могут убить себя, если MemoryInUse[] превысит заданный порог.

Обновление: изоляция основного ядра от сбоев подядра

Играя с этой структурой, я обнаружил, что любое использование общих переменных между основным ядром и подядром делает Mathematica нестабильной в случае сбоя подядра. Это включает в себя использование DistributeDefinitions[resultSoFar], как показано выше, а также явные общие переменные с использованием SetSharedVariable.

Чтобы обойти эту проблему, я передал resultSoFar через файл. Это устранило синхронизацию между двумя ядрами, в результате чего основное ядро ​​оставалось в блаженном неведении о сбое подядра. У этого также был приятный побочный эффект сохранения промежуточных результатов в случае сбоя основного ядра. Конечно, это также делает вызовы подядра немного медленнее. Но это может не быть проблемой, если каждый вызов подядра выполняет значительный объем работы.

Вот пересмотренные определения:

Needs["SubKernels`LocalKernels`"]

doSomeWork[] := {$KernelID, Length[Get[$resultFile]], RandomReal[]}

$resultFile = "/some/place/results.dat";

getTheJobDone[] :=
  Module[{subkernel, initsub, resultSoFar = {}}
  , initsub[] :=
      ( subkernel = LaunchKernels[LocalMachine[1]]
      ; DistributeDefinitions["Global`"]
      )
  ; initsub[]
  ; While[Length[resultSoFar] < 1000
    , Put[resultSoFar, $resultFile]
    ; Quiet[ParallelEvaluate[doSomeWork[], subkernel]] /.
        { $Failed :> (Print@"Ouch!"; CloseKernels[subkernel]; initsub[])
        , r_ :> AppendTo[resultSoFar, r]
        }
    ]
  ; CloseKernels[subkernel]
  ; resultSoFar
  ]
person WReach    schedule 29.10.2011

У меня есть аналогичное требование, когда я запускаю CUDAFunction для длинного цикла, а у CUDALink закончилась память (аналогично здесь: https://mathematica.stackexchange.com/questions/31412/cudalink-ran-out-of-доступная-память). Даже в последней версии Mathematica 10.4 проблема с утечкой памяти не улучшилась. Я нахожу обходной путь здесь и надеюсь, что вы найдете его полезным. Идея состоит в том, что вы используете bash-скрипт для многократного вызова программы Mathematica (запускаемой в пакетном режиме) с передачей параметров из bash-скрипта. Вот подробная инструкция и демонстрация (это для ОС Windows):

Вот демонстрация файла test.m

str=$CommandLine;
len=Length[str];
Do[
If[str[[i]]=="-start",
start=ToExpression[str[[i+1]]];
Pause[start];
Print["Done in ",start," second"];
];
,{i,2,len-1}];

Этот математический код считывает параметр из командной строки и использует его для расчета. Вот скрипт bash (script.sh) для многократного запуска test.m с разными параметрами.

#c:\cygwin64\bin\bash
for ((i=2;i<10;i+=2))
do
math -script test.m -start $i
done

В терминале cygwin введите «chmod a+x script.sh», чтобы включить скрипт, затем вы можете запустить его, набрав «./script.sh».

person entropi99    schedule 10.03.2016
comment
Преимущество этого обходного пути по сравнению с способом Криса Дегнена заключается в том, что вам не нужно иметь дело со временем ожидания. - person entropi99; 10.03.2016

Вы можете программно завершить работу ядра, используя Exit[]. Внешний интерфейс (ноутбук) автоматически запустит новое ядро, когда вы в следующий раз попытаетесь вычислить выражение.

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

С другой стороны, если вы знаете, какие определения занимают слишком много памяти, вы можете использовать Unset, Clear, ClearAll или Remove для удаления этих определений. Вы также можете установить $HistoryLength на что-то меньшее, чем Infinity (по умолчанию), если на это уходит ваша память.

person rob mayoff    schedule 23.10.2011
comment
Суть в том, чтобы продолжить вычисления, начиная с некоторой точки, без взаимодействия с пользователем. - person Alexey Popkov; 23.10.2011
comment
@AlexeyPopkov, я думаю, что решение Роба для DumpSave и Exit можно было бы реализовать программно, но сложной частью будет автоматический выбор определений для сохранения. Вы можете расширить Set, чтобы добавить указанные определения в список, который будет сохранен. Возможно, что-то похожее на метод, используемый в MakeCheckedReader. - person rcollyer; 23.10.2011

Звучит как работа для CleanSlate.

<< Utilities`CleanSlate`;
CleanSlate[]

Источник: http://library.wolfram.com/infocenter/TechNotes/4718/

"CleanSlate, пытается сделать все возможное, чтобы вернуть ядро ​​в то состояние, в котором оно было при первоначальной загрузке пакета CleanSlate.m."

person Chris Degnen    schedule 23.10.2011