Quer compartilhar seu conteúdo em R-bloggers? clique aqui se você tiver um blog, ou aqui se não tiver.
Cerca de uma década depois, mas decidi dar uma chance ao Grand Central Dispatch (GCD) em R. GCD é bastante semelhante ao OpenMP, pois fornece uma interface simplificada para pthreads. Desde seu lançamento para Mac OS X 10.6 em 2009, o GCD foi portado para vários sistemas operacionais por meio de libdispatch. No momento em que este livro foi escrito, o libdispatch estava disponível principalmente nos sistemas operacionais Linux e Apple usando o compilador Clang. O GCC pode funcionar, mas não tive sorte.
O aspecto mais notável do GCD é que ele fornece um ambiente multithreading nativo nos sistemas operacionais da Apple. Isso inclui MacOS e iOS. Não é verdade para o OpenMP desde a remoção do GCC do Mac OS X Snow Leopard. Portanto, se você está tentando obter o máximo desempenho para todos os seus clientes, o GCD pode fornecer alguns benefícios para muitos #ifdefs.
Se você já usou OpenCL, GCD deve estar familiarizado. Isso ocorre devido ao OpenCL e GCD usarem filas e blocos de código. No entanto, você não é forçado a usar blocos de código, pois o GCD fornece variantes _f dos comandos de envio para permitir o uso de funções C padrão.
As filas são fornecidas em dois tipos principais, serial e concorrente. As filas seriais são exatamente o que parecem, elas fornecem uma fila FIFO (First In First Out). Portanto, as tarefas são executadas e concluídas em sequência. As filas simultâneas são filas sem bloqueio, pois são executadas em ordem, mas não aguardam a conclusão.
Uma fila simultânea simples pode ser criada por meio de dispatch_get_global_queue
, esta função possui duas entradas. O primeiro é a Qualidade de Serviço (QoS) para a fila, o segundo é reservado para uso futuro e pode ser definido como 0 ou NULL.
dispatch_queue_global_t dispatch_get_global_queue(intptr_t qos, uintptr_t flags)
A qualidade do serviço permitirá flexibilidade em seu pacote R. Você pode ter uma fila de alta prioridade para desenhar um gráfico em tempo real e uma fila de prioridade mais baixa para extrair dados de sites em segundo plano.
O nível da fila é definido por um dos três valores enumerados:
DISPATCH_QUEUE_PRIORITY_HIGH = 2,
DISPATCH_QUEUE_PRIORITY_DEFAULT = 0,
DISPATCH_QUEUE_PRIORITY_LOW = -2
QoS mais alto forçará sua fila a ser executada antes de outras tarefas em seu sistema.
dispatch_queue_global_t dispatch_get_global_queue
só criará uma fila simultânea, mas você pode usar dispatch_queue_create
para criar qualquer tipo de fila:
dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t qt)
A primeira entrada fornece um rótulo para depuração etc., pode ser qualquer coisa, incluindo NULL, a segunda identifica o tipo de fila de um dos valores enumerados abaixo:
DISPATCH_QUEUE_SERIAL or NULL
DISPATCH_QUEUE_CONCURRENT
Depois de criar uma fila, você provavelmente vai querer colocar algo nela. Os comandos de envio levarão você até lá.
dipatch_apply e da mesma forma dispatch_apply_f resolverá esse problema para você, eles enfileirarão sua solicitação e a executarão um número fixo de vezes. A primeira função será usada para blocos de código, enquanto a última será usada para funções padrão.
Para lhe dar uma ideia do GCD, um loop for paralelo pode ser criado por meio da função a seguir.
// inner kernel using GCD
void R_parallel_inner_kernel_gcd( double * x, double * y, int * n ) {
// concurrent queue
dispatch_queue_t my_queue = dispatch_get_global_queue(0, 0);
// for like loop
dispatch_apply (*n, my_queue, ^(size_t idx){
size_t j;
double tmp = 0;
for( j = 0; j
Aqui você pode ver que o bloco de código está sendo aplicado ao despacho e executando n vezes usando o iterador idx. O bloco de código é apenas uma função acumulada que permite a criação de variáveis privadas, como tmp e j e compartilha os dados de e e x.
Isso deve ser o suficiente para você começar a brincar com o GCD, há mais documentação no
Página do GitHub da Apple.
Para comparação com o OpenMP, reescrevi o código abaixo para alguns testes de desempenho. Como você pode ver, o código é bastante semelhante, mas ao contrário do anterior, a falha em ter o OpenMP disponível permitirá que o código seja executado conforme projetado, ao contrário do caso GCD.
// inner kernel using OpenMP
void R_parallel_inner_kernel_omp( double * x, double * y, int * n ) {
size_t idx,j;
double tmp;
#pragma omp parallel
{
#pragma omp for
for( idx = 0; idx
Executando o código em um MacPro 2012 de 12 núcleos e 24 threads (3,3 GHz) mais antigo executando Mac OS Catalina, recebi os seguintes tempos para o GCC 9 (média de 100 execuções).
With OpenMP: 0.46 sec elapsed
Without GCD or OpenMP: 9.05 sec elapsed
O GCD não foi testado porque não consegui fazê-lo funcionar com o GCC neste caso.
Usando o LLVM do homebrew (Versão 10), recebi as seguintes temporizações no mesmo sistema (média de 100 execuções).
With GCD: 0.53 sec elapsed
With OpenMP: 0.54 sec elapsed
Without GCD or OpenMP: 9.08 sec elapsed
Mudando para o meu laptop executando o mesmo sistema operacional, um MacBook Pro 2017 de 13 “com uma CPU i5 (2,3 GHz, dual-core 4-thread), tive resultados que realmente foram na outra direção usando o homebrew LLVM (versão 10) ( média de 100 execuções).
With GCD: 2.92 sec elapsed
With OpenMP: 3.00 sec elapsed
Without GCD or OpenMP: 11.51 sec elapsed
Usando o GCC 9, também repeti a mesma compilação novamente usando os mesmos sinalizadores (média de 100 execuções).
With OpenMP: 3.21 sec elapsed
Without GCD or OpenMP: 11.61 sec elapsed
Podemos ver que as medidas de desempenho diferem um pouco entre OpenMP e GCD, mesmo ao executar um programa bastante simples em hardware diferente.
Mais testes seriam necessários para entender melhor as condições que fazem com que o OpenMP tenha mais desempenho do que o GCD.
Uma ideia poderia ser o número de núcleos ou otimização para processadores específicos.
Talvez outra postagem no blog para isso!
Para aqueles interessados em molhar um pouco mais os pés, criei um template de referência para GCD com make files para escolher a melhor implementação paralela para seu código (rgcd-template). Estão incluídos os testes de tempo e os exemplos de código mostrados.
Relacionados