Julia é uma linguagem de programação de código aberto feita para atender os requisitos da computação de alto desempenho numérico e científico, escrita em C, C++ e Scheme. Considerada uma linguagem de alto nivel e dinâmica, ou seja, suas variáveis podem receber quaquer tipo de dado e sua sintaxe se assemelha mais com a linguagem humana do que com a de máquina. Mesmo sendo desenvolvida para atender as aplicações que dependem de um desempenho mais elevado, ela pode ser utilizada para uso geral em vários tipos de aplicações.
O projeto foi inciado em 2009 por Jeff Bezanson, Stefan Karpinski, Viral B. Shah e Alan Edelman, que colocaram em pratica a ideia de criar uma linguagem de programação que fosse rápida como as linguagens de tipagem estática e fácil de usar como Python.
Julia fornence quatro tipos de programação paralela e concorrente
O multi-threading de Julia oferece a capacidade de agendar tasks simultaneamente em mais de um thread ou núcleo de CPU, compartilhando memória. Geralmente, essa é a maneira mais fácil de obter paralelismo em um computador ou em um único servidor grande de vários núcleos. O multi-threading de Julia é combinável. Quando uma função multi-threads chama outra função multi-threads, Julia agendará todos as threads globalmente nos recursos disponíveis, sem excesso de assinatura.
As tasks permite suspender e retomar cálculos para E/S, manipulação de eventos, processos do tipo produtor-consumidor e semelhantes. As tasks podem ser sincronizadas por meio de operações como wait, @sync e fetch e se comunicar por meio de Channels. Embora estritamente não seja uma computação paralela por si só, Julia permite que você agende Tasks em um ou mais threads.
Agendamento de tasks em uma thread
# ...
# Vetor para o armazenamento das tasks
tasks = []
#Criação das tasks
t1 = @task begin
# ...
end
push!(tasks, t1)
# ...
# Agendamento das tasks para serem executadas, todas no mesmo thread (o thread que está executando elas).
for i ∈ 1:length(tasks)
schedule(tasks[i])
end
# ...
Agendamento de tasks em multiplas threads
# ...
# Vetor para o armazenamento das tasks
tasks = []
#Criação das tasks
t1 = @task begin
# ...
end
push!(tasks, t1)
# ...
# Agendamento das tasks para serem executadas, permitindo que as tasks sejam executadas em outras threads.
Threads.@threads for i ∈ 1:length(tasks)
schedule(tasks[i])
end
# ...
A computação distribuída executa vários processos Julia com espaços de memória separados. Eles podem estar no mesmo computador ou em vários computadores. A biblioteca padrão Distributed fornece a capacidade de execução remota de uma função Julia. Com este bloco de construção básico, é possível construir muitos tipos diferentes de abstrações de computação distribuída. Pacotes como DistributedArrays.jl são um exemplo de tal abstração. Por outro lado, os pacotes como MPI.jl e Elemental.jl fornecem acesso ao ecossistema de bibliotecas MPI existente.
O compilador Julia GPU oferece a capacidade de executar o código Julia nativamente em GPUs. Há um rico ecossistema de pacotes Julia direcionados a GPUs. O site JuliaGPU.org fornece uma lista de recursos, GPUs compatíveis, pacotes relacionados e documentação.
Mesmo que a API de entrada e saída seja síncrona, a implementação subjacente permite a saida de segmentos assíncrona.
O exemplo abaixo é uma implementação assíncrona
julia> @sync for i in 1:3
@async write(stdout, string(i), " Foo ", " Bar ")
end
Resultado:
123 Foo Foo Foo Bar Bar Bar
Já o exemplo a seguir é uma implementação síncrona, pois a função println bloqueia o fluxo de execução do programa até terminar
julia> @sync for i in 1:3
@async println(stdout, string(i), " Foo ", " Bar ")
end
Por isso o resultado é:
1 Foo Bar
2 Foo Bar
3 Foo Bar
Outro tópico importante é o multiprocessamento. Como atualmente computadores possuem mais de uma CPU, e combinações deles em clusters, e quanto melhor a distribuição dos recursos, permite finalizar a tarefa mais rapidamente. E Julia possui o modulo Distributed para o manuseio.
A biblioteca tem como finalidade dar ferramentas ao usuário para gerenciar processos, realizar a movimentação de dados, memoria compartilhada e gerenciamento de Clusters, permitindo que o usuário venha a otimizar o seu código, desde espaço utilizado e velocidade.