Cheguei a uma solução um tanto satisfatória, então, caso alguém esteja interessado, pensei em compartilhá -la. Eu ainda apreciaria comentários sobre como melhorar/ajustar a abordagem.
Basicamente, decidi que a única maneira sensata é construir um modelo (muito) rudimentar do agendador para o loop paralelo:
function c=est_cost_para(cost_blocks,cost_it,num_cores)
% Estimate cost of parallel computation
% Inputs:
% cost_blocks: Estimate of cost per block in arbitrary units. For
% consistency with the other code this must be in the reverse order
% that the scheduler is fed, i.e. cost should be ascending!
% cost_it: Base cost of iteration (regardless of number of entries)
% in the same units as cost_blocks.
% num_cores: Number of cores
%
% Output:
% c: Estimated cost of parallel computation
num_blocks=numel(cost_blocks);
c=zeros(num_cores,1);
i=min(num_blocks,num_cores);
c(1:i)=cost_blocks(end-i+1:end)+cost_it;
while i<num_blocks
i=i+1;
[~,i_min]=min(c); % which core finished first; is fed with next block
c(i_min)=c(i_min)+cost_blocks(end-i+1)+cost_it;
end
c=max(c);
end
O parâmetro cost_it
pois uma iteração vazia é uma mistura bruta de muitos efeitos colaterais diferentes, que podem ser considerados separados: o custo de uma iteração vazia em um for
/parfor
-loop (também pode ser diferente por bloco), bem como o tempo de inicialização resp. transmissão de dados do parfor
-loop (e provavelmente mais). Meu principal motivo para juntar tudo é que não quero estimar/determinar os custos mais granulares.
Eu uso a rotina acima para determinar o corte da seguinte maneira:
% function i=cutoff_ser_para(cost_blocks,cost_it,num_cores)
% Determine cut-off between serial an parallel regime
% Inputs:
% cost_blocks: Estimate of cost per block in arbitrary units. For
% consistency with the other code this must be in the reverse order
% that the scheduler is fed, i.e. cost should be ascending!
% cost_it: Base cost of iteration (regardless of number of entries)
% in the same units as cost_blocks.
% num_cores: Number of cores
%
% Output:
% i: Number of blocks to be calculated serially
num_blocks=numel(cost_blocks);
cost=zeros(num_blocks+1,2);
for i=0:num_blocks
cost(i+1,1)=sum(cost_blocks(end-i+1:end))/num_cores + i*cost_it;
cost(i+1,2)=est_cost_para(cost_blocks(1:end-i),cost_it,num_cores);
end
[~,i]=min(sum(cost,2));
i=i-1;
end
Em particular, não inflar/altero o valor de est_cost_para
que assume (além de cost_it
) a programação mais otimista possível. Deixo como é principalmente porque não sei o que funcionaria melhor. Para ser conservador (ou seja, evite alimentar blocos muito grandes para o loop paralelo), é claro que pode -se adicionar alguma porcentagem como buffer ou até usar uma potência> 1 para inflar o custo paralelo.
Observe também isso est_cost_para
é chamado com blocos sucessivamente menos (embora eu use o nome da variável cost_blocks
Para ambas as rotinas, uma é um subconjunto do outro).
Comparado à abordagem em minha pergunta prolongada, vejo duas vantagens principais:
- A dependência relativamente complexa entre os dados (tanto o número de blocos quanto o custo) e o número de núcleos é capturado muito melhor com o agendador simulado do que seria possível com uma única fórmula.
- Ao calcular o custo de todas as combinações possíveis de distribuição serial/paralela e depois tomar o mínimo, não se pode ficar "preso" muito cedo enquanto lê os dados de um lado (por exemplo, um salto que é grande em relação aos dados até agora, mas pequeno em comparação com o total).
Obviamente, a complexidade assintótica é maior chamando est_cost_para
com seu loop enquanto o tempo todo, mas no meu caso (num_blocks<500
) Isso é absolutamente insignificante.
Finalmente, se um valor decente de cost_it
Não se apresenta prontamente, pode -se tentar calculá -lo medindo o tempo de execução real de cada bloco, bem como a parte puramente paralela dele, e depois tentando ajustar os dados resultantes à previsão de custos e obter um valor atualizado de cost_it
Para a próxima chamada da rotina (usando a diferença entre custo total e custo paralelo ou inserindo um custo de zero na fórmula ajustada). Esperamos que isso "converja" para o valor mais útil de cost_it
Para o problema em questão.