Как лучше всего подойти к хранилищу значений ключей для коллекции PyObjects, которые не помещаются в память?

#dictionary #julia

#словарь #джулия

Вопрос:

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

В принципе, если бы результаты были простыми числами с плавающей запятой (или другими типами битов), SharedArrays.jl справился бы с задачей. Но мои значения — это PyObjects, а не типы bits.

Я просмотрел JuliaDB.jl (только в памяти) и MMap.jl (только типы битов).

Есть ли что-то неясное, похожее на отображенный в памяти Dict, где я мог бы хранить в NVMe?

Спасибо за любые советы!

Ответ №1:

На самом деле вы можете использовать SharedArrays.jl для хранения объектов Python и делиться ими между рабочими. Это немного сложнее и связано с некоторыми накладными расходами.

Для простоты давайте рассмотрим этот график networkx:

 using PyCall
nx = pyimport("networkx") # requires using Conda;Conda.add("networkx")
g =  nx.random_tree(10) 
# PyObject <networkx.classes.graph.Graph object at 0x0000000002267790>
  

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

 using Distributed, SharedArrays, PyCall, Serialization
Distributed.addprocs(2)
@everywhere using Distributed, SharedArrays, PyCall, Serialization

const data = SharedVector(Vector{UInt8}(undef, 1000))
@everywhere nx = pyimport("networkx") # all proceses need to have the Python library loaded otherwise they would fail!
  

Теперь давайте сохраним объект Python в SharedArray . Конечно, это будет не так эффективно, как использование битовых типов, и, эй, это объект Python, а не объект Julia.

 b = IOBuffer()
serialize(b, g)
seekstart(b)
bbytes = readavailable(b)
(@view data[1:8]) .= reinterpret(UInt8, [length(bbytes)])
(@view data[9:8 length(bbytes)]) .= bbytes
  

Как вы можете видеть, первые 8 байтов data now содержат информацию о размере объекта, а остальные байты содержат сам объект.
Поскольку это SharedArray объект Python, он может быть прочитан каждым работником в кластере:

 julia> @sync @distributed for i in 1:2
           mysize = reinterpret(Int, view(data,1:8))[1]
           g2 = deserialize(IOBuffer(collect(view(data,9:8 mysize))))
           println("I am worker $(myid()) and I got graph $g2 that has nodes $(g2.nodes)")
       end

      From worker 2:    I am worker 2 and I got graph PyObject <networkx.classes.graph.Graph object at 0x0000000032240AF0> that has nodes PyObject NodeView((0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
      From worker 3:    I am worker 3 and I got graph PyObject <networkx.classes.graph.Graph object at 0x0000000032640AF0> that has nodes PyObject NodeView((0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
Task (done) @0x0000000017596290
  

Надеюсь, это поможет вам разделить объект Python среди многих рабочих.

Комментарии:

1. Очень интересно. Я обязательно попробую этот подход и проголосую / приму в качестве ответа, если смогу заставить его работать! Ваш подход также не учитывает необходимость того, чтобы мне нужно было распространять ответы из головного узла. Мне это нравится!

2. не забудьте загрузить все модули Python для каждого рабочего, иначе это может привести к некрасивому сбою! Также существуют структуры, которые нельзя отправлять через процессы, например, те, которые содержат открытый файловый дескриптор.