我有一个很大的矢量字符串向量: 大约有50,000个字符串向量, 每个包含2-15个长度为1-20个字符的字符串。
MyScoringOperation
是一个函数,它对字符串向量(基准面)进行操作,并返回一个10100得分的数组(如Float64s)。运行MyScoringOperation
大约需要0.01秒(取决于基准面的长度)
function MyScoringOperation(state:State, datum::Vector{String})
...
score::Vector{Float64} #Size of score = 10000
我有什么相当于嵌套循环。 外循环通常会运行500次迭代
data::Vector{Vector{String}} = loaddata()
for ii in 1:500
score_total = zeros(10100)
for datum in data
score_total+=MyScoringOperation(datum)
end
end
在一台计算机上,在3000(而不是50,000)的小测试用例中,每个外环需要100-300秒。
我有3个功能强大的服务器,安装了Julia 3.9(可以轻松获得3个,然后在下一个规模上可以获得数百个)。
我有@parallel的基本经验,但似乎花了很多时间复制常量(它或多或少挂在较小的测试用例上)
看起来像:
data::Vector{Vector{String}} = loaddata()
state = init_state()
for ii in 1:500
score_total = @parallel(+) for datum in data
MyScoringOperation(state, datum)
end
state = update(state, score_total)
end
我对@parallel实现方式的理解是:
每个 ii
:
data
分区为每个工作人员的夹头我想删除第2步,
这样,而不是向每个工人发送一大块数据,
我只是向每个工作人员发送一系列索引,然后从他们自己的data
副本中查找。甚至更好,只给每个人自己的块,并让它们每次都重复使用(节省大量的RAM)。
剖析支持了我对@parellel功能的看法。 对于类似范围的问题(甚至更小的数据), 非并行版本运行0.09秒, 并行运行 分析器显示几乎所有时间都花费了185秒。 Profiler显示,其中几乎100%用于与网络IO进行交互。
答案 0 :(得分:4)
这应该让你开始:
function get_chunks(data::Vector, nchunks::Int)
base_len, remainder = divrem(length(data),nchunks)
chunk_len = fill(base_len,nchunks)
chunk_len[1:remainder]+=1 #remained will always be less than nchunks
function _it()
for ii in 1:nchunks
chunk_start = sum(chunk_len[1:ii-1])+1
chunk_end = chunk_start + chunk_len[ii] -1
chunk = data[chunk_start: chunk_end]
produce(chunk)
end
end
Task(_it)
end
function r_chunk_data(data::Vector)
all_chuncks = get_chunks(data, nworkers()) |> collect;
remote_chunks = [put!(RemoteRef(pid)::RemoteRef, all_chuncks[ii]) for (ii,pid) in enumerate(workers())]
#Have to add the type annotation sas otherwise it thinks that, RemoteRef(pid) might return a RemoteValue
end
function fetch_reduce(red_acc::Function, rem_results::Vector{RemoteRef})
total = nothing
#TODO: consider strongly wrapping total in a lock, when in 0.4, so that it is garenteed safe
@sync for rr in rem_results
function gather(rr)
res=fetch(rr)
if total===nothing
total=res
else
total=red_acc(total,res)
end
end
@async gather(rr)
end
total
end
function prechunked_mapreduce(r_chunks::Vector{RemoteRef}, map_fun::Function, red_acc::Function)
rem_results = map(r_chunks) do rchunk
function do_mapred()
@assert r_chunk.where==myid()
@pipe r_chunk |> fetch |> map(map_fun,_) |> reduce(red_acc, _)
end
remotecall(r_chunk.where,do_mapred)
end
@pipe rem_results|> convert(Vector{RemoteRef},_) |> fetch_reduce(red_acc, _)
end
rchunk_data
将数据分成块(由get_chunks
方法定义)并将这些块分别发送给不同的工作者,并将它们存储在RemoteRefs中。
RemoteRefs是对其他进程(以及可能的计算机)的内存的引用,
prechunked_map_reduce
对某种地图进行缩小以使每个工作人员首先在其每个卡盘元素上运行map_fun
,然后使用{减少其卡盘中的所有元素{1}}(减少累加器函数)。最后,每个工作人员返回结果,然后使用red_acc
使用red_acc
将所有结果合并在一起,以便我们可以添加先完成的第一个结果。
fetch_reduce
是非阻塞获取和减少操作。我相信它没有竞争条件,但这可能是因为fetch_reduce
和@async
中的实现细节。当朱莉娅0.4出来时,很容易锁定它,使其显然没有竞争条件。
这段代码并没有真正的战斗力。我不相信 您还可能希望查看chuck大小可调,以便您可以向更快的工作人员看到更多数据(如果有更好的网络或更快的cpu)
您需要将代码重新表达为map-reduce问题,这看起来并不太难。
用以下方法测试:
@sync
当分布在8名工人(没有他们与发射器在同一台机器上)时,花了大约0.03秒
vs仅在本地运行:
data = [float([eye(100),eye(100)])[:] for _ in 1:3000] #480Mb
chunk_data(:data, data)
@time prechunked_mapreduce(:data, mean, (+))
耗时约0.06秒。