Удаленные параллельные ядра и LibraryLink - как заставить их работать вместе?
У кого-нибудь есть опыт использования расширений C для Mathematica (LibraryLink или MathLink - в настоящее время я использую LibraryLink) с удаленными параллельными ядрами?
Вкратце: как я могу прозрачно использовать функцию, определенную в LibraryLink (см. CreateLibrary и LibraryFunctionLoad), как в параллельных, так и в непараллельных оценках, когда суб-ядра работают на удаленном компьютере?
Я ищу некоторые шаги настройки, которые позволят мне иметь libraryFun
функция (написана на C), которая может быть вызвана как обычно libraryFun[args]
или параллельно (оба Parallelize@Table[libraryFun[arg], {arg, 0, 100}]
и то же самое с ParallelTable[]
), когда суб-ядра работают на удаленной машине.
Удаленное выполнение основного ядра также может быть лучше, если бы у меня не было проблем с этим.
Обновить
В то же время я добился определенного прогресса. Я опишу это здесь.
Первый, ParallelEvaluate
оценит выражение во всех параллельных ядрах. Если исходные файлы для расширения C скопированы на удаленный компьютер, мы можем скомпилировать их там следующим образом:
ParallelNeeds["CCompilerDriver`"]
k1 = First@Kernels[]
ParallelEvaluate[SetDirectory["/path/to/source/files"]]
ParallelEvaluate[CreateLibrary["sourefile", "myLibrary"]]
Это должно быть сделано только один раз. Я предполагаю, что библиотека уже скомпилирована на главном компьютере ядра.
После этого во всех последующих сессиях мы можем использовать FindLibrary
на основной и удаленной машинах для загрузки библиотеки.
LibraryFunctionLoad[myFun = FindLibrary["myLibrary"], "fun", ...]
ParallelEvaluate[myFun = LibraryFunctionLoad[FindLibrary["myLibrary"], "fun", ...]]
И тут приходит беда. Из-за разных путей, myFun
будут иметь разные значения в основном и в параллельных ядрах.
Таким образом, вопрос заключается в следующем: как обеспечить myFun
случайно не синхронизируется между основным и параллельным ядрами?
Я покажу в отдельных примерах, как это может произойти случайно:
In[1]:= LaunchKernels[2]
Out[1]= {KernelObject[1, "local"], KernelObject[2, "local"]}
Установить значение x
в основном ядре:
In[2]:= x = 1
Out[2]= 1
Обратите внимание, что он получает то же значение и в удаленных ядрах:
In[3]:= ParallelEvaluate[x]
Out[3]= {1, 1}
Установите другое значение для x
в параллельных ядрах и убедитесь, что они хранят его:
In[4]:= ParallelEvaluate[x = 2]
Out[4]= {2, 2}
In[5]:= {x, ParallelEvaluate[x]}
Out[5]= {1, {2, 2}}
Сейчас "невинно" используют Parallelize
на чем-то, содержащем x
:
In[6]:= Parallelize[Table[x, {10}]]
Out[6]= {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
И посмотрите, как значение x
был повторно синхронизирован между основным и подъядерными ядрами.
In[7]:= {x, ParallelEvaluate[x]}
Out[7]= {1, {1, 1}}
Новый вопрос: как я могу предотвратить автоматическую синхронизацию определенного символа между основным и субядрами?
2 ответа
Кажется, я нашел решение моего вопроса в обновлении выше. Кажется, это работает, но я пока не могу подтвердить, что он не хрупкий.
Решение состоит в том, чтобы поместить символы, которые мы не хотим синхронизировать, в отдельный контекст. С помощью c`x
в моем примере вместо x
предотвращает синхронизацию значения x
когда он используется внутри Parallelize
, Затем мы можем добавить этот контекст к $ContextPath
сделать символ легко доступным.
Наиболее удобный способ сделать это, вероятно, поместить все определения в пакет, который загружает библиотечные функции, используя LibraryFunctionLoad[FindLibrary[...], ...]
, Для этого библиотека должна быть сначала скомпилирована вручную как на локальном, так и на удаленном компьютере, однако код пакета может быть одинаковым как для основного, так и для подъядерных ядер.
Мне все еще интересно, если кто-то может подтвердить, что переменные не в $Context
гарантированно не будут автоматически синхронизированы.
Обновление было подтверждено здесь.
Надеюсь, это ответит на ваш вопрос:
На данный момент я предполагаю, что основное ядро и параллельные ядра имеют одинаковую архитектуру, для меня это Windows 7. Сначала вы компилируете функцию; Вы можете сделать это вне Mathematica, используя компилятор C, или непосредственно в Mathematica:
f = Compile[{x}, x^2, CompilationTarget -> "C"]
Вы можете проверить, посмотрев на InputForm
из f
где находится сгенерированная dll:
f // InputForm
Дает что-то вроде:
CompiledFunction[{8, 8., 5468}, {_Real}, {{3, 0, 0}, {3, 0, 1}}, {}, {0, 0, 2, 0, 0}, {{40,
56, 3, 0, 0, 3, 0, 1}, {1}}, Function[{x}, x^2], Evaluate,
LibraryFunction["C:\\Users\\arnoudb\\AppData\\Roaming\\Mathematica\\ApplicationData\\CCompilerDriver\\BuildFolder\\arnoudb2win-5184\\compiledFunction0.dll",
"compiledFunction0", {{Real, 0, "Constant"}}, Real]]
Вы можете скопировать этот файл в место, где его может найти параллельное (возможно, удаленное) ядро, например:
CopyFile["C:\\Users\\arnoudb\\AppData\\Roaming\\Mathematica\\ApplicationData\\CCompilerDriver\\BuildFolder\\arnoudb2win-3316\\compiledFunction1.dll",
"c:\\users\\arnoudb\\compiledFunction1.dll"]
Затем вы можете загрузить библиотеку во все параллельные ядра следующим образом:
ParallelEvaluate[
ff = LibraryFunctionLoad["C:\\users\\arnoudb\\compiledFunction1.dll",
"compiledFunction1", {Real}, Real]
]
И проверьте, работает ли это:
ParallelEvaluate[ff[3.4]]
Который возвращается {11.56,11.56}
для меня.
Если параллельное ядро имеет другую архитектуру, вам нужно будет скомпилировать код C для этой архитектуры (или оценить Compile[..., CompilationTarget->"C"]
на параллельном ядре).