
Muy a menudo, al resolver problemas de análisis y preparación de datos, se escriben guiones únicos, cuyo soporte y desarrollo no se proporcionan en absoluto. Este enfoque tiene derecho a existir, especialmente en la comunidad estudiantil. Sin embargo, cuando hay más de una persona trabajando con el código, o si el código necesita mantenerse por más de un día hábil, la opción de organizar el trabajo en forma de un montón de archivos no es aceptable.
Por lo tanto, hoy hablaremos de un tema tan importante como crear un proyecto desde cero en el idioma Julia, cómo llenarlo y qué herramientas tecnológicas existen para apoyar el desarrollo.
Proyecto
Como ya se mencionó, los scripts de una sola vez o los cuadernos Jupyter tienen derecho a existir en el escritorio de una persona, especialmente cuando el lenguaje de programación se usa como una calculadora avanzada. Pero este enfoque es completamente inadecuado para el desarrollo de proyectos que deberían desarrollarse y operarse durante años. Y, por supuesto, Julia, como plataforma tecnológica, tiene herramientas que brindan a los desarrolladores esta oportunidad.
Para empezar, algunos puntos generales. Julia tiene un módulo Pkg para la gestión de paquetes. Cualquier biblioteca de Julia es un módulo. Si el módulo no está incluido en el kit base de Julia, se emite como un paquete separado. Para cada paquete hay un archivo de proyecto Project.toml
, que contiene una descripción del proyecto y su dependencia de otros paquetes. Hay un segundo archivo: Manifest.toml
, que, a diferencia de Project.toml
, se genera automáticamente y contiene una lista de todas las dependencias necesarias con los números de versión del paquete. El formato de archivo Toml es el lenguaje obvio y mínimo de Tom .
Reglas de nomenclatura de paquetes
Según la documentación , el nombre del paquete puede consistir en letras y números latinos. Y este nombre debe elegirse de tal manera que sea claro para la mayoría de los usuarios de Julia, y no solo para expertos en un área temática estrecha.
- Se debe evitar la jerga y las contracciones controvertidas utilizadas de manera diferente en diferentes áreas.
- No use la palabra Julia en el nombre del paquete.
- Los paquetes que proporcionan algunas funciones junto con los nuevos tipos declarados en ellos deben nombrarse en plural.
◦ DataFrames proporciona un tipo de DataFrame.
◦ BloomFilters proporciona el tipo BloomFilter.
◦ Al mismo tiempo, JuliaParser no proporciona un nuevo tipo, y la nueva funcionalidad es la función JuliaParser.parse (). - Se debe preferir la transparencia y la comprensión con el nombre completo a la abreviatura.
RandomMatrices
menos ambiguo que RndMat
o RMT
. - El nombre puede corresponder a los detalles del área temática. Ejemplos:
◦ Julia no tiene sus propios paquetes de dibujo de gráficos. En cambio, hay paquetes Gadfly
, PyPlot
, Winston
, etc., cada uno de los cuales implementa su propio enfoque y metodología para su uso.
◦ Al mismo tiempo, SortingAlgorithms
proporciona una interfaz completa para usar algoritmos de clasificación. - En los casos en que los paquetes son un contenedor sobre algunas bibliotecas de terceros, retienen el nombre de esta biblioteca. Ejemplos:
◦ CPLEX.jl
es un contenedor sobre la biblioteca CPLEX
.
◦ MATLAB.jl
proporciona una interfaz para activar MATLAB
desde Julia.
Al mismo tiempo, el nombre del repositorio de git generalmente tiene el sufijo ".jl".
Generación de paquetes
La forma más fácil de crear un paquete es generarlo con un generador integrado en Julia. Para hacer esto, en la consola, debe ir al directorio en el que se debe crear el paquete, luego ejecutar julia y ponerlo en modo de administración de paquetes:
julia> ]
El último paso es iniciar el generador de paquetes especificando el nombre que queremos dar al paquete.
(v1.2) pkg> generate HelloWorld
Como resultado, aparece un nuevo directorio en el directorio actual correspondiente al nombre del paquete, cuya composición se puede ver usando el comando de tree
(si está instalado):
shell> cd HelloWorld shell> tree . . ├── Project.toml └── src └── HelloWorld.jl 1 directory, 2 files
En este caso, vemos un conjunto de archivos mínimo pero insuficiente para un proyecto bien diseñado. Para obtener más detalles, consulte https://julialang.imtqy.com/Pkg.jl/v1/creating-packages/ .
Una forma alternativa de crear paquetes es con el generador PkgTemplates.jl . A diferencia del generador incorporado, le permite generar inmediatamente un conjunto completo de archivos de servicio para dar servicio al paquete. El único inconveniente es que debe instalarse como un paquete.
El procedimiento para crear un paquete con su ayuda es el siguiente. Conectamos un paquete:
julia> using PkgTemplates
Creamos una plantilla que incluye una lista de autores, una licencia, requisitos para Julia, una lista de complementos para sistemas de integración continua (un ejemplo de la documentación de PkgTemplates
):
julia> t = Template(; user="myusername",
Obtenemos la plantilla:
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
Ahora, usando esta plantilla, podemos crear paquetes simplemente especificando su nombre:
julia> generate(t, "MyPkg1")
En una versión mínima, la plantilla puede verse así:
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
Si creamos un paquete llamado MyPkg2 a partir de esta plantilla:
julia> generate(t, "MyPkg2")
Luego podemos verificar el resultado directamente 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
Deben tenerse en cuenta los siguientes campos:
user="myusername"
, es el nombre de la entrada de registro de git.dir
- directorio para la colocación del código del paquete. Si no se especifica, se creará en el directorio de desarrollo ~/.julia/dev
. Además, de acuerdo con las reglas de los sistemas de archivos Unix, el ~/.julia
está oculto.
Después de crear el proyecto, se generará un conjunto suficiente de archivos y se creará un repositorio git. Además, todos los archivos generados se agregarán a este repositorio automáticamente.
Ubicación típica de archivo en un proyecto
Tomaremos prestada una imagen con una disposición típica de archivos y sus contenidos de https://en.wikibooks.org/wiki/Introducing_Julia/Modules_and_packages , pero la ampliaremos un poco:
Calculus.jl/
deps
que el directorio deps
puede contener los archivos necesarios para el correcto ensamblaje del paquete. Por ejemplo, deps/build.jl
es un script que se ejecuta automáticamente cuando se instala el paquete. El script puede contener cualquier código para la preparación de datos (descargar un conjunto de datos o realizar un preprocesamiento) u otros programas necesarios para el trabajo.
Cabe señalar que solo puede haber un módulo principal en un proyecto. Es decir, en el ejemplo anterior - Calculus
. Sin embargo, en el mismo ejemplo, hay un módulo Derivative
anidado que se conecta mediante include
. Presta atención a esto. include
incluye el archivo como texto, no como un módulo, lo que ocurre con el using
o la import
. Las dos últimas funciones no solo incluyen el módulo, sino que obligan a Julia a compilarlo como una entidad separada. Además, Julia intentará encontrar este módulo en los paquetes de dependencia y emitirá una advertencia de que falta en Project.toml
. Por lo tanto, si nuestra tarea es hacer un acceso jerárquico a las funciones, delimitándolas por espacios de nombres, entonces incluimos archivos a través de include
y activamos el módulo a través de un punto, indicando su afiliación local. Eso es:
module Calculus include("derivative.jl") import .Derivative ... end
La función derivative
que se exporta desde el módulo Derivative
estará disponible para nosotros a través de Calculus.Derivative.derivative()
Project.toml Project File
El archivo del proyecto es un archivo de texto. Sus secciones principales se describen en la descripción https://julialang.imtqy.com/Pkg.jl/v1/toml-files/
Después de que se genera el archivo, todos los campos necesarios ya están presentes en él. Sin embargo, es posible que deba cambiar parte de la descripción, cambiar la composición de los paquetes, sus versiones y dependencias específicas de diferentes sistemas operativos o configuraciones.
Los campos principales son:
name = "Example" uuid = "7876af07-990d-54b4-ab0e-23690620f79a" version = "1.2.5"
name
: el nombre del paquete elegido de acuerdo con las reglas de nomenclatura. uuid
es un identificador unificado que puede ser generado por un generador de paquetes o cualquier otro generador de uuid
. version
: número de versión del paquete en el formato de tres números decimales separados por puntos. Esto se ajusta al formato de Semantic Versioning 2.0.0 . Antes de la versión declarada 1.0.0, cualquier cambio en la interfaz del programa es posible. Después del lanzamiento de esta versión, el propietario del paquete debe cumplir con las reglas de compatibilidad. Cualquier cambio compatible debe reflejarse en el número menor (derecha). Los cambios incompatibles deben ir acompañados de un cambio en el número alto. Naturalmente, no hay control automático sobre la regla de control de versiones, pero el incumplimiento de la regla simplemente conducirá al hecho de que los usuarios del paquete comenzarán a dejar de usar y migrar masivamente al paquete cuyos autores cumplen con esta regla.
Todas las dependencias del paquete se presentan en la sección [deps]
.
[deps] Example = "7876af07-990d-54b4-ab0e-23690620f79a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Esta sección contiene una lista de dependencias directas de nuestro paquete. Las dependencias en cascada se reflejan en el archivo Manifest.toml
, que se genera automáticamente en el directorio del proyecto. Todas las dependencias están representadas por pares de =
. Y, por lo general, esta parte no está llena de manos. Para esto, se proporcionan las funciones del paquete Pkg
. Y, con mayor frecuencia, esto se hace desde REPL
, cambiándolo al modo de administración de paquetes - ]
. Siguiente: las operaciones add
, rm
, st
, etc., pero siempre en el contexto del paquete actual. Si no, debe ejecutar activate .
.
Manifest.toml
se puede guardar en el sistema de control de versiones git
. Este enfoque con dos archivos le permite reparar rígidamente los paquetes en el árbol de dependencias durante la prueba del producto de software, después de lo cual se garantiza que si nuestro paquete se implementa en una nueva ubicación, las mismas versiones de paquetes de terceros se repetirán allí. O, por el contrario, en ausencia de Manifest.toml
le dará la oportunidad de usar cualquier versión disponible que satisfaga las condiciones básicas.
La sección [compat]
permite especificar versiones específicas de paquetes que requerimos.
[deps] Example = "7876af07-990d-54b4-ab0e-23690620f79a" [compat] Example = "1.2" julia = "1.1"
Los paquetes se identifican por el nombre utilizado previamente en la sección [compat]
. Julia indica la versión de Julia misma.
Al especificar versiones, se aplican las reglas enumeradas en https://julialang.imtqy.com/Pkg.jl/dev/compatibility/ . Sin embargo, las mismas reglas se especifican en el control de versiones semántico .
Hay varias reglas de versiones. Por ejemplo:
[compat] Example = "1.2, 2"
significa que cualquier versión en el rango [1.2.0, 3.0.0)
es adecuada, sin incluir 3.0.0
. Y esto es totalmente consistente con una regla más simple:
[compat] Example = "1.2"
Además, simplemente especificando el número de versión es la forma abreviada "^1.2"
. Un ejemplo de la aplicación de la cual se ve así:
[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)
Si necesitamos especificar restricciones más estrictas, es necesario usar un formulario con tilde.
[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)
Bueno y, por supuesto, una indicación de signos iguales / desigualdades está disponible:
[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]
Es posible especificar varias opciones para las dependencias en la sección [targets]
. Tradicionalmente, en Julia antes de la versión 1.2, se usaba para especificar dependencias para usar el paquete y ejecutar pruebas. Para hacer esto, se especificaron paquetes adicionales en la sección [extras]
, y las configuraciones de destino con nombres de paquetes se enumeraron en [targets]
.
[extras] Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Markdown", "Test"]
Comenzando con Julia 1.2, se recomienda que simplemente agregue un archivo de proyecto separado para las test/Project.toml
.
Dependencias Adicionales
Se pueden conectar dependencias adicionales a través del deps/build.jl
, sin embargo, el archivo Artifacts.toml
se proporciona en la estructura del proyecto Julia. La Pkg.Artifacts
gestión de proyectos Pkg.Artifacts
proporciona funciones para automatizar la carga de dependencias adicionales. Un ejemplo de tal archivo:
# 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"
No nos detendremos en más detalles, ya que la descripción adicional depende del caso de uso específico. Las funciones de la biblioteca artifact_hash
, download
, create_artifact
, bind_artifact
. Consulte la documentación https://julialang.imtqy.com/Pkg.jl/dev/artifacts/ para obtener más detalles.
Implementación de código principal y depuración
Por supuesto, especificamos explícita o implícitamente el directorio de desarrollo al crear el paquete. Sin embargo, si es necesario, podemos cambiarlo. Si el paquete fue generado por PkgTemplates
con parámetros predeterminados, búsquelo en el ~/.julia/dev
. A pesar de que el directorio está oculto, la transición a él es posible a través de un enlace directo en el navegador de archivos. Para MacOS en Finder, por ejemplo, esto se hace presionando Comando + Mayús + G. Si el paquete se crea en cualquier otro directorio, simplemente ábralo en un editor de texto. El mejor editor para trabajar con el código de Julia es Atom y todo lo que admite el complemento uber-juno
. En este caso, obtendrá un editor de texto con formato automático del código, una consola REPL para la ejecución interactiva del código, la capacidad de ejecutar solo fragmentos de código seleccionados y ver los resultados, incluida la visualización de gráficos. Y también, un depurador paso a paso. Sin embargo, debemos admitir que en este momento es bastante lento, por lo que el modo de depuración actual: primero creemos que queremos verificar y poner la salida de depuración, luego ejecutamos la prueba para probar.
Se recomienda que observe patrones de diseño comunes para lenguajes de programación dinámicos . Además, el libro "Patrones de diseño práctico con Julia 1.0. Tom Kwong" y el código de muestra para ello . Y al implementar programas, debe considerar las recomendaciones sobre el estilo de programación de Julia Style Guide .
De las complejidades de la depuración, se puede observar el paquete Revise.jl
. Su activación se puede establecer en el archivo .julia/config/startup.jl
solo para el modo interactivo, en el que REPL se puede ejecutar desde el editor Atom. Revisar le permite editar el código de función dentro de nuestro paquete sin reiniciar la sesión REPL, y cada ejecución que use / import en nuestras pruebas habilitará estas actualizaciones.
Para un desarrollo efectivo, se recomienda desarrollar en paralelo el código principal y las pruebas que lo prueban. Esto le permite implementar solo lo que realmente se necesita, porque de lo contrario, en las pruebas obviamente habrá funciones innecesarias. Por lo tanto, deben ser eliminados. En esencia, Julia no ofrece nada específico en los principios del desarrollo. Sin embargo, el énfasis en el desarrollo a través de pruebas unitarias se da aquí porque Julia compila el código bastante lentamente, y en el modo de depuración paso a paso el rendimiento se reduce mucho. Es decir, depende del desarrollo de las pruebas, su organización, qué tan rápido se depurará y verificará el paquete que se está desarrollando.
Pruebas
Una ubicación de prueba típica es el directorio de prueba. El archivo test/runtests.jl
es el punto de partida para todas las pruebas.
En relación con el ejemplo mencionado anteriormente, la forma típica del archivo es:
using Calculus
Se recomienda desarrollar archivos de pruebas específicas sobre la base de la agrupación de funciones probadas. Por ejemplo, en el módulo de Calculus
mencionado pueden estar presentes varios algoritmos para calcular derivadas, integrales, etc. Será lógico probarlos con varias pruebas ubicadas en diferentes archivos.
Para las pruebas unitarias, Julia proporciona el módulo de Test
del conjunto de la biblioteca base. La macro @test
se define en este módulo, cuyo propósito es verificar la exactitud de la declaración especificada. Ejemplos:
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 atención a la forma completa del operador de comparación aproximado ≈
.
La declaración que verifica la elección de la excepción es @test_throws
. Ejemplo: cree una matriz y acceda al índice más allá:
julia> @test_throws BoundsError [1, 2, 3][4] Test Passed Thrown: BoundsError
Una construcción útil es @testset
. Le permite agrupar declaraciones individuales en una prueba conectada lógicamente. Por ejemplo:
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 a través de @testset
, se forma su propia tabla de pruebas aprobadas. Las suites de prueba se pueden anidar. En caso de su aprobación exitosa, se emite una tabla resumen, en caso de falla, para cada grupo de pruebas se emitirán sus propias estadísticas.
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 , .
Conclusión
, , Julia. , , . , — , -, , . , .
Referencias