
Muitas vezes, ao resolver problemas de análise e preparação de dados, scripts únicos são gravados, cujo suporte e desenvolvimento não é fornecido. Essa abordagem tem o direito de existir, especialmente na comunidade estudantil. No entanto, quando houver mais de uma pessoa trabalhando com o código ou se o código precisar ser mantido por mais de um dia útil, a opção de organizar o trabalho na forma de um monte de arquivos não será aceitável.
Portanto, hoje falaremos sobre um tópico tão importante como criar um projeto do zero na linguagem Julia, como preenchê-lo e quais ferramentas tecnológicas existem para apoiar o desenvolvimento.
Projeto
Como já mencionado, scripts únicos ou notebooks Jupyter têm o direito de existir na área de trabalho de uma pessoa, especialmente quando a linguagem de programação é usada como uma calculadora avançada. Mas essa abordagem é completamente inadequada para o desenvolvimento de projetos que devem ser desenvolvidos e operados por anos. E, é claro, Julia, como plataforma de tecnologia, possui ferramentas que fornecem aos desenvolvedores essa oportunidade.
Para começar, alguns pontos gerais. Julia possui um módulo Pkg para gerenciamento de pacotes. Qualquer biblioteca Julia é um módulo. Se o módulo não estiver incluído no kit básico Julia, ele será emitido como um pacote separado. Para cada pacote, há um arquivo de projeto Project.toml
, que contém uma descrição do projeto e sua dependência de outros pacotes. Existe um segundo arquivo - Manifest.toml
, que, diferentemente do Project.toml
, é gerado automaticamente e contém uma lista de todas as dependências necessárias com os números de versão do pacote. O formato do arquivo Toml é o idioma óbvio e mínimo de Tom .
Regras de Nomenclatura de Pacotes
De acordo com a documentação , o nome do pacote pode consistir em letras e números em latim. E esse nome deve ser escolhido de forma que fique claro para a maioria dos usuários de Julia, e não apenas para especialistas em áreas restritas.
- O jargão e contrações controversas usadas de maneira diferente em diferentes áreas devem ser evitadas.
- Não use a palavra Julia no nome do pacote.
- Pacotes que fornecem algumas funções junto com os novos tipos declarados neles devem ser nomeados no plural.
F DataFrames fornece um tipo de DataFrame.
◦ BloomFilters fornece o tipo BloomFilter.
◦ Ao mesmo tempo, o JuliaParser não fornece um novo tipo e a nova funcionalidade é a função JuliaParser.parse (). - Transparência e compreensão usando o nome completo devem ser preferidas à abreviação.
RandomMatrices
menos ambíguo que RndMat
ou RMT
. - O nome pode corresponder às especificidades da área de assunto. Exemplos:
◦ Julia não possui seus próprios pacotes de desenho gráfico. Em vez disso, existem pacotes Gadfly
, PyPlot
, Winston
, etc., cada um dos quais implementa sua própria abordagem e metodologia para uso.
Same Ao mesmo tempo, SortingAlgorithms
fornece uma interface completa para o uso de algoritmos de classificação. - Nos casos em que os pacotes são um invólucro em algumas bibliotecas de terceiros, eles mantêm o nome dessa biblioteca. Exemplos:
◦ CPLEX.jl
é um wrapper sobre a biblioteca CPLEX
.
O MATLAB.jl
fornece uma interface para ativar o MATLAB
partir de Julia.
Ao mesmo tempo, o nome do repositório git geralmente possui o sufixo ".jl".
Geração de pacotes
A maneira mais fácil de criar um pacote é gerá-lo com um gerador incorporado na Julia. Para fazer isso, no console, você precisa ir para o diretório em que o pacote deve ser criado, então execute julia e coloque-o no modo de gerenciamento de pacotes:
julia> ]
A etapa final é iniciar o gerador de pacotes, especificando o nome que queremos dar ao pacote.
(v1.2) pkg> generate HelloWorld
Como resultado, um novo diretório aparece no diretório atual correspondente ao nome do pacote, cuja composição pode ser vista usando o comando tree
(se estiver instalado):
shell> cd HelloWorld shell> tree . . ├── Project.toml └── src └── HelloWorld.jl 1 directory, 2 files
Nesse caso, vemos um conjunto mínimo, mas insuficiente, de arquivos para um projeto bem projetado.Para mais detalhes, consulte https://julialang.imtqy.com/Pkg.jl/v1/creating-packages/ .
Uma maneira alternativa de criar pacotes é com o gerador PkgTemplates.jl . Diferentemente do gerador embutido, ele permite gerar imediatamente um conjunto completo de arquivos de serviço para a manutenção do pacote. A única desvantagem é que ele próprio deve ser instalado como um pacote.
O procedimento para criar um pacote com sua ajuda é o seguinte. Conectamos um pacote:
julia> using PkgTemplates
Criamos um modelo que inclui uma lista de autores, uma licença, requisitos para Julia, uma lista de plugins para sistemas de integração contínua (um exemplo da documentação para PkgTemplates
):
julia> t = Template(; user="myusername",
Temos o modelo:
Template: → User: myusername → Host: github.com → License: ISC (Chris de Graaf, Invenia Technical Computing Corporation 2018) → Package directory: ~/code → Minimum Julia version: v0.7 → SSH remote: No → Commit Manifest.toml: No → Plugins: • AppVeyor: → Config file: Default → 0 gitignore entries • Codecov: → Config file: None → 3 gitignore entries: "*.jl.cov", "*.jl.*.cov", "*.jl.mem" • Coveralls: → Config file: None → 3 gitignore entries: "*.jl.cov", "*.jl.*.cov", "*.jl.mem" • GitHubPages: → 0 asset files → 2 gitignore entries: "/docs/build/", "/docs/site/" • TravisCI: → Config file: Default → 0 gitignore entries
Agora, usando este modelo, podemos criar pacotes simplesmente especificando seu nome:
julia> generate(t, "MyPkg1")
Em uma versão mínima, o modelo pode ficar assim:
julia> t = Template(; user="rssdev10", authors=["rssdev10"]) Template: → User: rssdev10 → Host: github.com → License: MIT (rssdev10 2019) → Package directory: ~/.julia/dev → Minimum Julia version: v1.0 → SSH remote: No → Add packages to main environment: Yes → Commit Manifest.toml: No → Plugins: None
Se criarmos um pacote chamado MyPkg2 a partir deste modelo:
julia> generate(t, "MyPkg2")
Em seguida, podemos verificar o resultado diretamente de Julia:
julia> run(`git -C $(joinpath(t.dir, "MyPkg2")) ls-files`); .appveyor.yml .gitignore .travis.yml LICENSE Project.toml README.md REQUIRE docs/Manifest.toml docs/Project.toml docs/make.jl docs/src/index.md src/MyPkg2.jl test/runtests.jl
Os seguintes campos devem ser observados:
user="myusername"
, é o nome da entrada de registro do git.dir
- diretório para posicionamento do código do pacote. Se não especificado, ele será criado no diretório de desenvolvimento ~/.julia/dev
. Além disso, de acordo com as regras dos sistemas de arquivos unix, o ~/.julia
está oculto.
Após criar o projeto, um conjunto suficiente de arquivos será gerado e um repositório git será criado. Além disso, todos os arquivos gerados serão adicionados a este repositório automaticamente.
Local típico do arquivo em um projeto
Emprestaremos uma foto com uma disposição típica de arquivos e seu conteúdo em https://en.wikibooks.org/wiki/Introducing_Julia/Modules_and_packages , mas expandiremos um pouco:
Calculus.jl/
Acrescentamos que o diretório deps
pode conter os arquivos necessários para a montagem correta do pacote. Por exemplo, deps/build.jl
é um script que é executado automaticamente quando o pacote é instalado. O script pode conter qualquer código para preparação de dados (baixar um conjunto de dados ou executar pré-processamento) ou outros programas necessários para o trabalho.
Note-se que pode haver apenas um módulo principal em um projeto. Ou seja, no exemplo acima - Calculus
. No entanto, no mesmo exemplo, há um módulo aninhado Derivative
que se conecta via include
. Preste atenção nisso. include
inclui o arquivo como texto, não como um módulo, o que ocorre com o using
ou import
. As duas últimas funções não incluem apenas o módulo, mas forçam Julia a compilá-lo como uma entidade separada. Além disso, Julia tentará encontrar esse módulo nos pacotes de dependência e emitirá um aviso de que ele está ausente no Project.toml
. Portanto, se nossa tarefa é fazer acesso hierárquico às funções, delimitando-as por espaços de nomes, incluímos arquivos através de include
e ativamos o módulo através de um ponto, indicando sua afiliação local. Isto é:
module Calculus include("derivative.jl") import .Derivative ... end
A função derivative
que é exportada do módulo Derivative
estará disponível para nós através de Calculus.Derivative.derivative()
Arquivo de Projeto Project.toml
O arquivo do projeto é um arquivo de texto. Suas seções principais estão divulgadas na descrição https://julialang.imtqy.com/Pkg.jl/v1/toml-files/
Depois que o arquivo é gerado, todos os campos necessários já estão presentes nele. No entanto, pode ser necessário alterar parte da descrição, alterar a composição dos pacotes, suas versões e dependências específicas de diferentes sistemas operacionais ou configurações.
Os principais campos são:
name = "Example" uuid = "7876af07-990d-54b4-ab0e-23690620f79a" version = "1.2.5"
name
- o nome do pacote escolhido de acordo com as regras de nomenclatura. uuid
é um identificador unificado que pode ser gerado por um gerador de pacotes ou qualquer outro gerador de uuid
. version
- número da versão do pacote no formato de três números decimais separados por pontos. Isso está de acordo com o formato do Semantic Versioning 2.0.0 . Antes da versão 1.0.0 declarada, qualquer alteração na interface do programa é possível. Após o lançamento desta versão, o proprietário do pacote deve cumprir as regras de compatibilidade. Quaisquer alterações compatíveis devem ser refletidas no número menor (à direita). Alterações incompatíveis devem ser acompanhadas por uma alteração no número alto. Naturalmente, não há controle automático sobre a regra de versão, mas a não conformidade com a regra simplesmente levará ao fato de que os usuários do pacote começarão a parar de usar e migrar massivamente para o pacote cujos autores cumprem essa regra.
Todas as dependências do pacote são apresentadas na seção [deps]
.
[deps] Example = "7876af07-990d-54b4-ab0e-23690620f79a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Esta seção contém uma lista de dependências diretas do nosso pacote. Dependências em cascata são refletidas no arquivo Manifest.toml
, que é gerado automaticamente no diretório do projeto. Todas as dependências são representadas pelos pares =
. E, geralmente, esta parte não é preenchida com as mãos. Para isso, são fornecidas as funções do pacote Pkg
. E, na maioria das vezes, isso é feito no REPL
, alternando para o modo de gerenciamento de pacotes - ]
. Próximo - as operações add
, rm
, st
, etc., mas sempre no contexto do pacote atual. Caso contrário, você precisa executar o activate .
.
Manifest.toml
pode ser salvo no sistema de controle de versão git
. Essa abordagem com dois arquivos permite corrigir rigidamente os pacotes na árvore de dependências durante o teste do produto de software, após o qual, para garantir que, se nosso pacote for implantado em um novo local, as mesmas versões de pacotes de terceiros serão repetidas no mesmo local. Ou, inversamente, na ausência do Manifest.toml
terá a oportunidade de usar qualquer versão disponível que atenda às condições básicas.
A seção [compat]
permite especificar versões específicas de pacotes necessárias.
[deps] Example = "7876af07-990d-54b4-ab0e-23690620f79a" [compat] Example = "1.2" julia = "1.1"
Os pacotes são identificados pelo nome usado anteriormente na seção [compat]
. julia
indica a versão da própria Julia.
Ao especificar versões, as regras listadas em https://julialang.imtqy.com/Pkg.jl/dev/compatibility/ se aplicam. No entanto, as mesmas regras são especificadas no controle de versão semântico .
Existem várias regras de versão. Por exemplo:
[compat] Example = "1.2, 2"
significa que qualquer versão no intervalo [1.2.0, 3.0.0)
é adequada, sem incluir o 3.0.0
. E isso é totalmente consistente com uma regra mais simples:
[compat] Example = "1.2"
Além disso, simplesmente especificar o número da versão é uma forma abreviada de "^1.2"
. Um exemplo de aplicação que se parece com:
[compat] PkgA = "^1.2.3" # [1.2.3, 2.0.0) PkgB = "^1.2" # [1.2.0, 2.0.0) PkgC = "^1" # [1.0.0, 2.0.0) PkgD = "^0.2.3" # [0.2.3, 0.3.0) PkgE = "^0.0.3" # [0.0.3, 0.0.4) PkgF = "^0.0" # [0.0.0, 0.1.0) PkgG = "^0" # [0.0.0, 1.0.0)
Se precisarmos especificar restrições mais rigorosas, é necessário usar um formulário com um til.
[compat] PkgA = "~1.2.3" # [1.2.3, 1.3.0) PkgB = "~1.2" # [1.2.0, 1.3.0) PkgC = "~1" # [1.0.0, 2.0.0) PkgD = "~0.2.3" # [0.2.3, 0.3.0) PkgE = "~0.0.3" # [0.0.3, 0.0.4) PkgF = "~0.0" # [0.0.0, 0.1.0) PkgG = "~0" # [0.0.0, 1.0.0)
Bem, e é claro, uma indicação de sinais de igualdade / desigualdades está disponível:
[compat] PkgA = ">= 1.2.3" # [1.2.3, ∞) PkgB = "≥ 1.2.3" # [1.2.3, ∞) PkgC = "= 1.2.3" # [1.2.3, 1.2.3] PkgD = "< 1.2.3" # [0.0.0, 1.2.2]
É possível especificar várias opções para dependências na seção [targets]
. Tradicionalmente, na Julia antes da versão 1.2, era usada para especificar dependências para o uso do pacote e para a execução de testes. Para fazer isso, pacotes adicionais foram especificados na seção [extras]
e configurações de destino com nomes de pacotes foram listadas em [targets]
.
[extras] Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Markdown", "Test"]
A partir do Julia 1.2, é recomendável adicionar um arquivo de projeto separado para os test/Project.toml
.
Dependências adicionais
Dependências adicionais podem ser conectadas através do deps/build.jl
; no entanto, o arquivo Artifacts.toml
é fornecido na estrutura do projeto Julia. A Pkg.Artifacts
gerenciamento de projetos Pkg.Artifacts
fornece funções para automatizar o carregamento de dependências adicionais. Um exemplo desse arquivo:
# Example Artifacts.toml file [socrates] git-tree-sha1 = "43563e7631a7eafae1f9f8d9d332e3de44ad7239" lazy = true [[socrates.download]] url = "https://github.com/staticfloat/small_bin/raw/master/socrates.tar.gz" sha256 = "e65d2f13f2085f2c279830e863292312a72930fee5ba3c792b14c33ce5c5cc58" [[socrates.download]] url = "https://github.com/staticfloat/small_bin/raw/master/socrates.tar.bz2" sha256 = "13fc17b97be41763b02cbb80e9d048302cec3bd3d446c2ed6e8210bddcd3ac76" [[c_simple]] arch = "x86_64" git-tree-sha1 = "4bdf4556050cb55b67b211d4e78009aaec378cbc" libc = "musl" os = "linux" [[c_simple.download]] sha256 = "411d6befd49942826ea1e59041bddf7dbb72fb871bb03165bf4e164b13ab5130" url = "https://github.com/JuliaBinaryWrappers/c_simple_jll.jl/releases/download/c_simple+v1.2.3+0/c_simple.v1.2.3.x86_64-linux-musl.tar.gz" [[c_simple]] arch = "x86_64" git-tree-sha1 = "51264dbc770cd38aeb15f93536c29dc38c727e4c" os = "macos" [[c_simple.download]] sha256 = "6c17d9e1dc95ba86ec7462637824afe7a25b8509cc51453f0eb86eda03ed4dc3" url = "https://github.com/JuliaBinaryWrappers/c_simple_jll.jl/releases/download/c_simple+v1.2.3+0/c_simple.v1.2.3.x86_64-apple-darwin14.tar.gz" [processed_output] git-tree-sha1 = "1c223e66f1a8e0fae1f9fcb9d3f2e3ce48a82200"
Não vamos nos aprofundar mais em detalhes, pois a descrição adicional depende do caso de uso específico. As funções da biblioteca artifact_hash
, download
, create_artifact
, bind_artifact
. Consulte a documentação https://julialang.imtqy.com/Pkg.jl/dev/artifacts/ para obter mais detalhes.
Implementação e depuração do código principal
Obviamente, explicitamente ou implicitamente especificamos o diretório de desenvolvimento ao criar o pacote. No entanto, se necessário, podemos alterá-lo. Se o pacote foi gerado pelo PkgTemplates
com parâmetros padrão, procure-o no ~/.julia/dev
. Apesar do diretório estar oculto, a transição para ele é possível através de um link direto no navegador de arquivos. Para o MacOS no Finder, por exemplo, isso é feito pressionando Command + Shift + G. Se o pacote for criado em qualquer outro diretório, basta abri-lo em um editor de texto. O melhor editor para trabalhar com o código Julia é o Atom e tudo o que o plugin uber-juno
suporta. Nesse caso, você obtém um editor de texto com formatação automática de código, um console REPL para execução interativa de código, a capacidade de executar apenas fragmentos de código selecionados e visualizar os resultados, incluindo gráficos. E também, um depurador passo a passo. Embora, devemos admitir que, no momento, é bastante lento, portanto, o modo de depuração atual - primeiro pensamos que queremos verificar e colocar a saída de depuração, depois executamos o teste para teste.
É recomendável que você observe padrões de design comuns para linguagens de programação dinâmicas . Além disso, o livro "Testes práticos de design com Julia 1.0. Tom Kwong" e um exemplo de código . E ao implementar programas, você deve considerar as recomendações no estilo de programação do Julia Style Guide .
Dos meandros da depuração, o pacote Revise.jl
pode ser observado. Sua ativação pode ser definida no arquivo .julia/config/startup.jl
apenas para o modo interativo, no qual o REPL pode ser executado a partir do editor Atom. Revise permite editar o código de função dentro de nosso pacote sem reiniciar a sessão REPL, e cada execução usando / import em nossos testes habilitará essas atualizações.
Para um desenvolvimento eficaz, é recomendável desenvolver em paralelo o código principal e os testes que o testam. Isso permite implementar apenas o que é realmente necessário, porque, caso contrário, nos testes, obviamente haverá funções desnecessárias. Portanto, eles devem ser removidos. Em essência, Julia não oferece nada específico nos princípios de desenvolvimento. No entanto, a ênfase no desenvolvimento por meio do teste de unidade é dada aqui porque Julia compila o código lentamente, e no modo de depuração passo a passo, o desempenho é muito reduzido. Ou seja, depende do desenvolvimento dos testes, de sua organização, da rapidez com que o pacote que está sendo desenvolvido será depurado e verificado.
Testes
Um local de teste típico é o diretório de teste. O arquivo test/runtests.jl
é o ponto de partida para todos os testes.
Em relação ao exemplo acima mencionado, a forma típica do arquivo é:
using Calculus
Arquivos de testes específicos são recomendados para serem desenvolvidos com base no agrupamento de funções testadas. Por exemplo, no módulo Calculus
mencionado, podem estar presentes vários algoritmos para calcular derivadas, integrais, etc. É lógico testá-los com vários testes localizados em arquivos diferentes.
Para testes de unidade, Julia fornece o módulo Test
do conjunto da biblioteca base. A macro @test
é definida neste módulo, cujo objetivo é verificar a correção da instrução especificada. Exemplos:
julia> @test true Test Passed julia> @test [1, 2] + [2, 1] == [3, 3] Test Passed julia> @test π ≈ 3.14 atol=0.01 Test Passed
Preste atenção na forma completa do operador de comparação aproximado ≈
.
A instrução que verifica a opção de exceção é @test_throws
. Exemplo - crie uma matriz e acesse o índice além dela:
julia> @test_throws BoundsError [1, 2, 3][4] Test Passed Thrown: BoundsError
Uma construção útil é @testset
. Permite agrupar instruções individuais em um teste conectado logicamente. Por exemplo:
julia> @testset "trigonometric identities" begin θ = 2/3*π @test sin(-θ) ≈ -sin(θ) @test cos(-θ) ≈ cos(θ) @test sin(2θ) ≈ 2*sin(θ)*cos(θ) @test cos(2θ) ≈ cos(θ)^2 - sin(θ)^2 end; Test Summary: | Pass Total trigonometric identities | 4 4
Para cada conjunto declarado através de @testset
, sua própria tabela de testes aprovada é formada. Os conjuntos de teste podem ser aninhados. Em caso de aprovação bem-sucedida, uma tabela de resumo é emitida, em caso de falha - para cada grupo de testes, suas próprias estatísticas serão emitidas.
julia> @testset "Foo Tests" begin @testset "Animals" begin @testset "Felines" begin @test foo("cat") == 9 end @testset "Canines" begin @test foo("dog") == 9 end end @testset "Arrays" begin @test foo(zeros(2)) == 4 @test foo(fill(1.0, 4)) == 15 end end Arrays: Test Failed Expression: foo(fill(1.0, 4)) == 15 Evaluated: 16 == 15 [...] Test Summary: | Pass Fail Total Foo Tests | 3 1 4 Animals | 2 2 Arrays | 1 1 2 ERROR: Some tests did not pass: 3 passed, 1 failed, 0 errored, 0 broken.
@test_broken
, @test_skip
.
. julia
:
--code-coverage={none|user|all}, --code-coverage Count executions of source lines (omitting setting is equivalent to "user") --code-coverage=tracefile.info Append coverage information to the LCOV tracefile (filename supports format tokens). --track-allocation={none|user|all}, --track-allocation Count bytes allocated by each source line (omitting setting is equivalent to "user")
code-coverage
— . ( ), . , . .cov
. .
:
- function vectorize(str::String) 96 tokens = str |> tokenizer |> wordpiece 48 text = ["[CLS]"; tokens; "[SEP]"] 48 token_indices = vocab(text) 48 segment_indices = [fill(1, length(tokens) + 2);] 48 sample = (tok = token_indices, segment = segment_indices) 48 bert_embedding = sample |> bert_model.embed 48 collect(sum(bert_embedding, dims=2)[:]) - end
track-allocation
— . , , , , .mem
.
:
- function vectorize(str::String) 0 tokens = str |> tokenizer |> wordpiece 6766790 text = ["[CLS]"; tokens; "[SEP]"] 0 token_indices = vocab(text) 11392 segment_indices = [fill(1, length(tokens) + 2);] 1536 sample = (tok = token_indices, segment = segment_indices) 0 bert_embedding = sample |> bert_model.embed 170496 collect(sum(bert_embedding, dims=2)[:]) - end
, . , , , , . , . .
— :
julia --project=@. --code-coverage --track-allocation test/runtests.jl
— Profile.jl
@profile
. https://julialang.org/blog/2019/09/profilers . @noinline
, . , fib
fib_r
.
julia> @noinline function fib(n) return n > 1 ? fib_r(n - 1) + fib_r(n - 2) : 1 end julia> @noinline fib_r(n) = fib(n) julia> @time fib(40) 0.738735 seconds (3.16 k allocations: 176.626 KiB) 165580141 julia> using Profile julia> @profile fib(40) 165580141 julia> Profile.print(format=:flat, sortedby=:count) Count File Line Function 12 int.jl 52 - 14 int.jl 53 + 212 boot.jl 330 eval 5717 REPL[2] 1 fib_r 6028 REPL[1] 2 fib julia> count(==(0), Profile.fetch()) 585
@profile fib(40)
. Profile.print(format=:flat, sortedby=:count)
. , , , fib_r
fib
, . , :
julia> Profile.print(format=:tree) 260 REPL[1]:2; fib(::Int64) 112 REPL[1]:1; fib_r(::Int64) 212 task.jl:333; REPL.var"##26#27" 212 REPL.jl:118; macro expansion 212 REPL.jl:86; eval_user_input 212 boot.jl:330; eval ╎ 210 REPL[1]:2; fib ╎ 210 REPL[1]:1; fib_r ╎ 210 REPL[1]:2; fib ╎ 210 REPL[1]:1; fib_r ╎ 210 REPL[1]:2; fib ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ 210 REPL[1]:2; fib ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ 210 REPL[1]:2; fib ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ ╎ 210 REPL[1]:2; fib ╎ ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ ╎ 210 REPL[1]:2; fib ╎ ╎ ╎ 210 REPL[1]:1; fib_r ╎ ╎ ╎ 210 REPL[1]:2; fib ...
. PProf.jl, .

. https://github.com/vchuravy/PProf.jl .
doc
. https://habr.com/ru/post/439442/
, , Julia .
Project.toml
, . , , - , , .
, , . , — . :
, , . , , git clone, . PackageCompiler.jl
. , , - .
C
- , , ( - , ), deps, deps/build.jl
. . , , , . , , , , . , , build.jl
, :
. julia --project=@.
Julia Project.toml
. , — build.jl
, executable
. , julia --project=@. build.jl
.
Pkg.activate(".")
( Project.toml
).
Pkg.build()
, C-, . deps/build.jl
, .
Pkg.test()
. , -, , . -, , . coverage=true
. , . build.jl
.
, . , PkgTempletes
. — Gitlab CI, Travis CI, GitHub , .
Conclusão
, , Julia. , , . , — , -, , . , .
Referências