AnyMath 文档

常见问题解答

一般事务

朱莉娅是以某人或某事的名字命名的吗?

非也。.

为什么不编译Matlab/Python/R/。.. 给茱莉亚打密码?

由于许多人熟悉其他动态语言的语法,并且已经用这些语言编写了大量代码,因此很自然地想知道为什么我们不只是将Matlab或Python前端插入Julia后端(或"`转位,转位`"code to Julia),以便在不需要程序员学习新语言的情况下获得Julia的所有性能优势。 简单,对吧?

基本的问题是Julia的compiler_没有什么特别之处:我们使用一个普通的编译器(LLVM),没有"`秘制酱汁`"其他语言开发人员不知道。 事实上,Julia的编译器在许多方面比其他动态语言(例如PyPy或LuaJIT)简单得多。 Julia的性能优势几乎完全来源于它的前端:它的语言语义允许 精心编写的Julia程序为编译器提供更多的机会,以生成高效的代码和内存布局。 如果您尝试将Matlab或Python代码编译为Julia,我们的编译器将受到Matlab或Python语义的限制,无法生成比这些语言的现有编译器更好的代码(可能更糟)。 语义的关键作用也是为什么几个现有的Python编译器(如Numba和Pythran)只尝试优化语言的一小部分(例如对Numpy数组和标量的操作),并且对于这个子集,他们已经在做 在这些项目上工作的人非常聪明,并且已经完成了惊人的事情,但是将编译器改造成一种被设计为被解释的语言是一个非常困难的问题。

Julia的优势在于,良好的性能不仅限于"`内置式`"类型和操作,并且可以编写高级类型泛型代码,该代码适用于任意用户定义的类型,同时保持快速和内存效率。 像Python这样的语言中的类型根本没有为编译器提供足够的信息来实现类似的功能,所以一旦你使用这些语言作为Julia前端,你就会被卡住。

出于类似的原因,自动翻译到Julia通常也会生成不可读的、缓慢的、非惯用的代码,这对于来自另一种语言的本地Julia端口来说不是一个很好的起点。

另一方面,language_interoperability_非常有用:我们希望利用Julia的其他语言中现有的高质量代码(反之亦然)! 实现这一点的最好方法不是转译器,而是通过简单的语言间调用工具。 我们已经在这方面努力工作了,从内置的 ccall 内部(调用C和Fortran库)https://github.com/JuliaInterop[JuliaInterop]将Julia连接到Python,Matlab,C的软件包++,以及更多。

公众空气污染指数

Julia如何定义其公共API?

朱莉娅的公众https://en.wikipedia.org/wiki/API[API]是在公共绑定的文档中描述的行为 基地 和标准库。 如果函数、类型和常量不是公共的,那么它们就不是公共API的一部分,即使它们有文档字符串或在文档中描述过。 此外,只有公共绑定的记录行为是公共API的一部分。 公共绑定的未记录行为是内部的。

公共绑定是那些标记为 公共食物出口食品.

换句话说:

*公共绑定的记录行为是公共API的一部分。 *公共绑定的未记录行为不是公共API的一部分。 *私有绑定的记录行为不是公共API的一部分。 *私有绑定的未记录行为不是公共API的一部分。

你可以从一个模块中获得一个完整的公共绑定列表。 名称(MyModule).

鼓励包作者类似地定义他们的公共API。

Julia的公共API中的任何内容都由https://semver.org/[SemVer],因此在Julia2.0之前不会被删除或接收有意义的突破性更改。

有一个有用的未记录函数/类型/常量。 我可以使用它吗?

如果您使用非公共API,更新Julia可能会破坏您的代码。 如果代码是自包含的,那么将其复制到项目中可能是个好主意。 如果你想依赖一个复杂的非公共API,特别是在从一个稳定的包中使用它时,打开一个https://github.com/JuliaLang/julia/issues[问题]或https://github.com/JuliaLang/julia/pulls[拉请求]开始讨论将其转换为公共API。 但是,我们不鼓励创建公开稳定公共接口的包,同时依赖Julia的非公共实现细节并缓冲不同Julia版本之间的差异。

文档不够准确。 我可以依靠现有的行为吗?

请打开一个https://github.com/JuliaLang/julia/issues[问题]或https://github.com/JuliaLang/julia/pulls[拉取请求]开始讨论将现有行为转换为公共API。

会议及代表

如何删除内存中的对象?

朱莉娅没有MATLAB的模拟 清楚 函数;一旦在Julia会话中定义了一个名称(从技术上讲,在模块中 主要),它始终存在。

如果您关注内存使用情况,则始终可以将对象替换为消耗较少内存的对象。 例如,如果 A 是一个千兆字节大小的阵列,你不再需要,你可以释放内存与 A=没有. 内存将在下次垃圾回收器运行时释放;您可以强制执行此操作 GC。gc(). 此外,试图使用 A 可能会导致错误,因为大多数方法都没有在类型上定义 什么都没有.

脚本编写

如何检查当前文件是否作为主脚本运行?

当文件作为主脚本运行时,使用 朱莉娅档案。jl 人们可能想要激活额外的功能,如命令行参数处理。 确定文件以这种方式运行的一种方法是检查是否 abspath(PROGRAM_FILE)==@__文件__真的.

但是,建议不要编写兼作脚本和可导入库的文件。 如果需要既可作为库又可作为脚本的功能,则最好将其写为库,然后将功能导入到不同的脚本中。

如何在脚本中捕获CTRL-C?

运行Julia脚本 朱莉娅档案。jl 不扔 InterruptException异常当您尝试用CTRL-C(SIGINT)终止它时。 要在终止Julia脚本之前运行某个代码,这可能是由CTRL-C引起的,也可能不是由CTRL-C引起的,请使用 atexit,atexit. 或者,您可以使用 julia-e’包括(popfirst!(ARGS))'文件。jl 在能够捕获的同时执行脚本 InterruptException异常试试块。 请注意,使用此策略 程序文件不会被设置。

如何将选项传递给 朱莉娅 使用 #!/usr/仓库/env?

将选项传递给 朱莉娅 在一个所谓的shebang线,如在 #!/usr/bin/env julia—​startup-file=no,不会在许多平台(bsd,macOS,Linux)上工作,其中内核与shell不同,不会在空格字符处分割参数。 选项 env-S,它将单个参数字符串在空格处拆分为多个参数,类似于shell,提供了一个简单的解决方法:

#!/usr/bin/env -S julia --color=yes --startup-file=no
@show ARGS  # put any Julia code here

注意选项 env-S 出现在FreeBSD6.0(2005)、macOS Sierra(2016)和GNU/Linux coreutils8.30(2018)中。

为什么不 支援服务 * 或管道脚本外部程序?

朱莉娅的 函数启动外部程序_directly_,而不调用https://en.wikipedia.org/wiki/Shell_(计算)[操作系统外壳](不像 系统("。..") Python,R或C等其他语言中的函数)。 这意味着 不执行通配符扩展 * (https://en.wikipedia.org/wiki/Glob_(编程)["globbing"]),也没有解释https://en.wikipedia.org/wiki/Pipeline_(Unix)[shell pipelines]like |>.

但是,您仍然可以使用Julia功能执行全局填充和管道。 例如,内置的 管道函数允许您链接外部程序和文件,类似于shell管道,以及https://github.com/vtjnash/Glob.jl[Glob.jl包]实现POSIX兼容的globbing。

当然,您可以通过shell显式传递shell和命令字符串来运行程序 ,例如 跑(sh-c"ls>文件。txt") 使用Unix Bourne shell,但您通常应该更喜欢纯Julia脚本,如 运行(流水线(ls,"文件。txt")). 我们默认避开shell的原因是https://julialang.org/blog/2012/03/shelling-out-sucks/[脱壳很糟糕]:通过shell启动进程很慢,很容易引用特殊字符,错误处理很差,并且在可移植性方面存在问题。 (Python开发人员来到了一个https://www.python.org/dev/peps/pep-0324/#motivation[类似的结论]。)

变量和赋值

我为什么要 不败者 从一个简单的循环?

你可能有类似的东西:

x = 0
while x < 10
    x += 1
end

请注意,它在交互式环境中工作正常(如Julia REPL),但给出了 不败者: x 未定义 当您尝试在脚本或其他文件中运行它时。 正在发生的事情是,Julia通常要求您*明确分配给局部作用域中的全局变量*。

这里, x 是全局变量, 定义一个 本地范围,以及 x+=1 是对该局部作用域中的全局的赋值。

如上所述,Julia(版本1.5或更高版本)允许您省略 全球 REPL(和许多其他交互式环境)中的代码关键字,以简化探索(例如,从函数复制粘贴代码以交互方式运行)。 但是,一旦你转到文件中的代码,Julia就需要一种更有纪律的全局变量方法。 你至少有三个选择:

  1. 将代码放入一个函数中(以便 x 是函数中的_local_变量)。 一般来说,使用函数而不是全局脚本是很好的软件工程(在线搜索"为什么全局变量不好"以查看许多解释)。 在Julia中,全局变量也是

  2. 将代码包装在 块。 (这使得 x 内的局部变量 让。.. 结束 声明,再次消除了对 全球).

  3. 显式标记 x 作为 全球 在分配给它之前,在局部范围内,例如写入 全局x+=1.

更多的解释可以在手册部分找到 在软范围

功能

我通过了一个论点 x 到一个函数,在该函数内部修改它,但在外部,变量 x 仍未改变。 为啥?

假设你这样调用一个函数:

julia> x = 10
10

julia> function change_value!(y)
           y = 17
       end
change_value! (generic function with 1 method)

julia> change_value!(x)
17

julia> x # x is unchanged!
10

在Julia中,变量的绑定 x 不能通过传递来改变 x 作为函数的参数。 打电话时 change_value!(x) 在上面的例子中, y 是一个新创建的变量,最初绑定到 x,即 10;则 y 是反弹到常数 17,而变量 x 外部范围保持不变。

但是,如果 x 绑定到类型的对象 阵列 (或任何其他_mutable_类型)。 从函数内部,您无法"解除绑定" x 从这个数组,但你_can_改变其内容。 例如:

julia> x = [1,2,3]
3-element Vector{Int64}:
 1
 2
 3

julia> function change_array!(A)
           A[1] = 5
       end
change_array! (generic function with 1 method)

julia> change_array!(x)
5

julia> x
3-element Vector{Int64}:
 5
 2
 3

这里我们创建了一个函数 change_array!,即分配 5 到传递数组的第一个元素(绑定到 x 在呼叫站点,并绑定到 A 功能内)。 请注意,在函数调用之后, x 仍然绑定到同一个数组,但该数组的内容发生了变化:变量 Ax 不同的绑定指的是同一个可变的 阵列 对象。

我可以使用吗 使用进口 函数内部?

不,你是不允许有一个 使用进口 函数内部的语句。 如果要导入模块,但只在特定函数或一组函数中使用其绑定,则有两个选项:

  1. 使用方法 进口:

  import Foo
  function bar(...)
      # ... refer to Foo bindings via Foo.baz ...
  end

这将加载模块 [医]脚 并定义一个变量 [医]脚 它引用模块,但不会将模块中的任何其他绑定导入当前命名空间。 你指的是 [医]脚 通过其限定名称绑定 Foo。酒吧 等。

  1. 将您的函数包装在模块中:

  module Bar
  export bar
  using Foo
  function bar(...)
      # ... refer to Foo.baz as simply baz ....
  end
  end
  using Bar

这将从以下位置导入所有绑定 [医]脚,但只在模块内部 酒吧.

什么是 ... 操作员做什么?

的两个用途 ... 操作员:泥浆和飞溅

许多新来的朱莉娅发现使用 ... 操作员混淆。 是什么让 ... 运算符令人困惑的是,它意味着取决于上下文的两个不同的东西。

... 在函数定义中,将多个参数组合成一个参数

在函数定义的上下文中, ... 运算符用于将许多不同的参数组合成一个参数。 这种使用 ... 将许多不同的参数组合成一个参数称为slurping:

julia> function printargs(args...)
           println(typeof(args))
           for (i, arg) in enumerate(args)
               println("Arg #$i = $arg")
           end
       end
printargs (generic function with 1 method)

julia> printargs(1, 2, 3)
Tuple{Int64, Int64, Int64}
Arg #1 = 1
Arg #2 = 2
Arg #3 = 3

如果Julia是一种更自由地使用ASCII字符的语言,那么slurping运算符可能被写成 <-... 而不是 ....

... 在函数调用中将一个参数拆分为许多不同的参数

与使用 ... 运算符表示在定义函数时将许多不同的参数转换为一个参数, ... 当在函数调用的上下文中使用时,运算符还用于将单个函数参数拆分为许多不同的参数。 这种使用 ... 被称为飞溅:

julia> function threeargs(a, b, c)
           println("a = $a::$(typeof(a))")
           println("b = $b::$(typeof(b))")
           println("c = $c::$(typeof(c))")
       end
threeargs (generic function with 1 method)

julia> x = [1, 2, 3]
3-element Vector{Int64}:
 1
 2
 3

julia> threeargs(x...)
a = 1::Int64
b = 2::Int64
c = 3::Int64

如果Julia是一种更自由地使用ASCII字符的语言,那么splatting运算符可能被写成 ...-> 而不是 ....

赋值的返回值是多少?

操作员 = 因此,总是返回右侧:

julia> function threeint()
           x::Int = 3.0
           x # returns variable x
       end
threeint (generic function with 1 method)

julia> function threefloat()
           x::Int = 3.0 # returns 3.0
       end
threefloat (generic function with 1 method)

julia> threeint()
3

julia> threefloat()
3.0

同样地:

julia> function twothreetup()
           x, y = [2, 3] # assigns 2 to x and 3 to y
           x, y # returns a tuple
       end
twothreetup (generic function with 1 method)

julia> function twothreearr()
           x, y = [2, 3] # returns an array
       end
twothreearr (generic function with 1 method)

julia> twothreetup()
(2, 3)

朱莉娅>twothreearr()
2元素向量{Int64}:
 2
 3

类型、类型声明和构造函数

"类型稳定"是什么意思?

这意味着输出的类型可以从输入的类型中预测。 特别是,这意味着输出的类型不能根据输入的_values_而变化。 下面的代码是_not_type-stable:

julia> function unstable(flag::Bool)
           if flag
               return 1
           else
               return 1.0
           end
       end
unstable (generic function with 1 method)

它返回一个 Int型 或一个 漂浮64取决于其参数的值。 由于Julia无法在编译时预测此函数的返回类型,因此使用它的任何计算都必须能够处理这两种类型的值,这使得很难生成快速的机器代码。

为什么朱莉娅给一个 N.域名,域名 对于某些看似明智的操作?

某些操作在数学上有意义,但会导致错误:

julia> sqrt(-2.0)
ERROR: DomainError with -2.0:
sqrt was called with a negative real argument but will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
[...]

这种行为是对类型稳定性要求的不便后果。 的情况下 sqrt,sqrt,大多数用户希望 sqrt(2.0) 给出一个实数,如果它产生了复数,会很不高兴 1.4142135623730951+0.0im. 人们可以写 sqrt,sqrt函数仅在传递负数(这是什么)时切换到复值输出 sqrt,sqrt在其他一些语言中),但结果不会是 类型稳定sqrt,sqrt函数的性能会很差。

在这些和其他情况下,您可以通过选择一个_input type_来获得您想要的结果,该_input type_表示您愿意接受一个可以表示结果的_output type_:

julia> sqrt(-2.0+0im)
0.0 + 1.4142135623730951im

如何约束或计算类型参数?

A的参数 参数类型可以保存类型或位值,类型本身选择如何使用这些参数。 例如, 阵列{Float64, 2} 由类型参数化 漂浮64 表示其元素类型和整数值 2 来表示其维数。 在定义自己的参数类型时,可以使用子类型约束来声明某个参数必须是子类型(<:)某种抽象类型或以前的类型参数。 但是,没有专用语法来声明参数必须是给定类型的_value_-也就是说,您不能直接声明类似维度的参数 伊萨 Int型 内的 结构体 定义,例如。 同样,您不能对类型参数进行计算(包括简单的加法或减法)。 相反,这些类型的约束和关系可以通过附加的类型参数来表示,这些参数是在类型的 构造函数

作为一个例子,考虑

struct ConstrainedType{T,N,N+1} # NOTE: INVALID SYNTAX
    A::Array{T,N}
    B::Array{T,N+1}
end

用户希望强制第三种类型参数始终是第二个加一个。 这可以用一个明确的类型参数来实现,该参数由 内部构造函数方法(其中它可以与其他检查组合):

struct ConstrainedType{T,N,M}
    A::Array{T,N}
    B::Array{T,M}
    function ConstrainedType(A::Array{T,N}, B::Array{T,M}) where {T,N,M}
        N + 1 == M || throw(ArgumentError("second argument should have one more axis" ))
        new{T,N,M}(A, B)
    end
end

此检查通常是_costless_,因为编译器可以忽略对有效具体类型的检查。 如果第二个参数也计算,它可能是有利的,提供一个 外部构造函数方法:

ConstrainedType(A) = ConstrainedType(A, compute_B(A))

为什么Julia使用本机整数算术?

Julia使用机器算术进行整数计算。 这意味着,范围 Int型 值是有界的,并在两端环绕,以便添加,减去和乘以整数可以溢出或下溢,导致一些结果起初可能令人不安:

julia> x = typemax(Int)
9223372036854775807

julia> y = x+1
-9223372036854775808

julia> z = -y
-9223372036854775808

julia> 2&ast;z
0

显然,这与数学整数的行为方式相去甚远,您可能认为高级编程语言向用户公开这一点并不理想。 然而,对于效率和透明度很高的数字工作,替代方案更糟糕。

要考虑的一种替代方法是检查每个整数操作是否溢出,并将结果提升为更大的整数类型,例如 Int128比金特溢出的情况下。 不幸的是,这会在每个整数操作上引入重大开销(想想递增循环计数器)-它需要发射代码在算术指令和分支之后执行运行时溢出检查以处理潜在的溢出。 更糟糕的是,这会导致涉及整数的每个计算都是类型不稳定的。 正如我们上面提到的, 类型稳定性至关重要有效生成高效代码。 如果你不能指望整数运算的结果是整数,那么就不可能像C和Fortran编译器那样生成快速,简单的代码。

这种方法的一个变体,避免了类型不稳定的出现,是合并 Int型比金特类型为单个混合整数类型,当结果不再适合机器整数的大小时,它会在内部更改表示形式。 虽然这表面上避免了Julia代码级别的类型不稳定,但它只是通过将所有相同的困难强加给实现这种混合整数类型的C代码来解决问题。 这种方法可以工作,甚至可以在许多情况下做得相当快,但有几个缺点。 一个问题是整数和整数数组的内存中表示不再与c,Fortran和其他语言使用的自然表示与本机机器整数相匹配。 因此,为了与这些语言进行互操作,我们最终需要引入本机整数类型。 整数的任何无界表示都不能具有固定数量的位,因此不能内联存储在具有固定大小插槽的数组中-大整数值将始终需要单独的堆分配存储。 当然,无论使用多么聪明的混合整数实现,总会有性能陷阱-性能意外下降的情况。 复杂的表示,缺乏与C和Fortran的互操作性,无法在没有额外堆存储的情况下表示整数数组,以及不可预测的性能特征使得即使是最聪明的混合整数实现也

使用混合整数或提升到Bigint的替代方法是使用饱和整数算术,其中添加到最大整数值使其保持不变,同样用于从最小整数值中减去。 这正是Matlab™所做的:

>> int64(9223372036854775807)

ans =

  9223372036854775807

>>int64(9223372036854775807)+1

ans=

  9223372036854775807

>>int64(-9223372036854775808)

ans=

 -9223372036854775808

>>int64(-9223372036854775808)-1

ans=

 -9223372036854775808

乍一看,这似乎足够合理,因为9223372036854775807比-9223372036854775808更接近9223372036854775808,整数仍然以与C和Fortran兼容的自然方式以固定大小表示。 然而,饱和整数算术是非常有问题的。 第一个也是最明显的问题是,这不是机器整数运算的工作方式,因此实现饱和运算需要在每次机器整数运算后发出指令,以检查下溢或上溢,并将结果替换 打字(Int)打字(Int)酌情。 仅这一点就将每个整数操作从单个快速指令扩展为半打指令,可能包括分支。 哎哟。 但它变得更糟-饱和整数算术不是关联的。 考虑这个Matlab计算:

>> n = int64(2)^62
4611686018427387904

>> n + (n - 1)
9223372036854775807

>> (n + n) - 1
9223372036854775806

这使得很难编写许多基本的整数算法,因为许多常用技术依赖于机器加法与overflow_is_associative的事实。 考虑查找整数值之间的中点 嗨。 在Julia中使用表达式 (lo+hi)>>>1:

julia> n = 2^62
4611686018427387904

朱莉娅>(n+2n)>>>1
6917529027641081856

看到了吗? 没问题!. 这是2之间的正确中点^62和2^63、尽管事实上 n+2n 是-4611686018427387904. 现在在Matlab中尝试一下:

>> (n + 2&ast;n)/2

ans =

  4611686018427387904

哎呀。 添加一个 >>> 运算符对Matlab没有帮助,因为在添加时发生饱和 n2n 已经破坏了计算正确中点所需的信息。

对于那些不能依赖这种技术的程序员来说,缺乏关联性不仅是不幸的,而且它也会击败编译器可能想要做的优化整数算术的任何事情。 例如,由于Julia整数使用正常的机器整数算术,LLVM可以自由地积极优化简单的小函数,如 f(k)=5k-1. 这个功能的机器代码就是这样:

julia> code_native(f, Tuple{Int})
  .text
Filename: none
  pushq %rbp
  movq  %rsp, %rbp
Source line: 1
  leaq  -1(%rdi,%rdi,4), %rax
  popq  %rbp
  retq
  nopl  (%rax,%rax)

函数的实际主体是单个 利亚克 指令,它计算整数乘加一次。 这是更有益的,当 f 内联到另一个函数中:

julia> function g(k, n)
           for i = 1:n
               k = f(k)
           end
           return k
       end
g (generic function with 1 methods)

julia> code_native(g, Tuple{Int,Int})
  .text
Filename: none
  pushq %rbp
  movq  %rsp, %rbp
Source line: 2
  testq %rsi, %rsi
  jle L26
  nopl  (%rax)
Source line: 3
L16:
  leaq  -1(%rdi,%rdi,4), %rdi
Source line: 2
  decq  %rsi
  jne L16
Source line: 5
L26:
  movq  %rdi, %rax
  popq  %rbp
  retq
  nop

自从呼吁 f 得到内联,循环体最终只是一个单一的 利亚克 指示。 接下来,考虑如果我们使循环迭代次数固定会发生什么:

julia> function g(k)
           for i = 1:10
               k = f(k)
           end
           return k
       end
g (generic function with 2 methods)

julia> code_native(g,(Int,))
  .text
Filename: none
  pushq %rbp
  movq  %rsp, %rbp
Source line: 3
  imulq $9765625, %rdi, %rax    # imm = 0x9502F9
  addq  $-2441406, %rax         # imm = 0xFFDABF42
Source line: 5
  popq  %rbp
  retq
  nopw  %cs:(%rax,%rax)

因为编译器知道整数加法和乘法是关联的,并且乘法分布在加法上-这两个都不是饱和算术的真实-它可以将整个循环优化为一个乘法和一个加法。 饱和算术完全击败了这种优化,因为关联性和分布性可能在每次循环迭代中失败,导致不同的结果,这取决于失败发生在哪个迭代中。 编译器可以展开循环,但它不能代数地将多个操作减少为更少的等效操作。

整数算术无提示溢出的最合理的替代方法是在任何地方进行检查算术,在加,减和乘溢出时引发错误,产生值不正确的值。 在这https://danluu.com/integer-overflow/[博客文章],Dan Luu分析了这一点,发现这种方法理论上应该具有的微不足道的成本,但由于编译器(LLVM和GCC)没有优雅地围绕增加的溢出检查进行优化,它最终 如果这在将来有所改善,我们可以考虑在Julia中默认检查整数算术,但现在,我们必须忍受溢出的可能性。

同时,可以通过使用外部库来实现溢出安全的整数操作,例如https://github.com/JeffreySarnoff/SaferIntegers.jl[SaferIntegers.jl].请注意,如前所述,使用这些库会显着增加使用已检查整数类型的代码的执行时间。 但是,对于有限的使用,这远不是一个问题,如果它用于所有整数操作。 您可以按照讨论的状态https://github.com/JuliaLang/julia/issues/855[这里]。

什么是一个可能的原因 不败者 在远程执行期间?

正如错误所述,一个直接的原因 不败者 在远程节点上,不存在该名称的绑定。 让我们探讨一些可能的原因。

julia> module Foo
           foo() = remotecall_fetch(x->x, 2, "Hello")
       end

julia> Foo.foo()
ERROR: On worker 2:
UndefVarError: `Foo` not defined in `Main`
Stacktrace:
[...]

关闭 x->x 携带引用到 [医]脚,而自 [医]脚 在节点2上不可用, 不败者 被抛出。

除此以外的模块下的全局变量 主要 不按值序列化到远程节点。 只发送引用。 创建全局绑定的函数(除了 主要)可能导致 不败者 待会再扔。

julia> @everywhere module Foo
           function foo()
               global gvar = "Hello"
               remotecall_fetch(()->gvar, 2)
           end
       end

julia> Foo.foo()
ERROR: On worker 2:
UndefVarError: `gvar` not defined in `Main.Foo`
Stacktrace:
[...]

在上面的例子中, @everywhere模块Foo 定义 [医]脚 在所有节点上。 然而, Foo。食物() 创建了一个新的全局绑定 格瓦尔 在本地节点上,但在节点2上没有找到这一点,导致 不败者 错误。

请注意,这不适用于在module下创建的全局变量 主要. 模块下的全局变量 主要 序列化和新绑定创建于 主要 在远程节点上。

julia> gvar_self = "Node1"
"Node1"

julia> remotecall_fetch(()->gvar_self, 2)
"Node1"

julia> remotecall_fetch(varinfo, 2)
name          size summary
––––––––– –––––––– –––––––
Base               Module
Core               Module
Main               Module
gvar_self 13 bytes String

这不适用于 功能结构体 声明。 但是,绑定到全局变量的匿名函数被序列化,如下所示。

julia> bar() = 1
bar (generic function with 1 method)

julia>remotecall_fetch(酒吧,2)
错误:在worker2上:
不败者: `#酒吧` 未定义于 `主要`
[...]

朱莉娅>anon_bar=()->1
(::#21)(带1个方法的泛型函数)

julia>remotecall_fetch(anon_bar,2)
1

故障排除"方法不匹配":参数类型不变性和 方法;方法s

为什么声明不起作用 foo(bar::Vector{Real}) = 42 然后打电话 食物([1])? )-42-and-then-call-foo([1])?}

正如你会看到,如果你尝试这个,结果是一个 方法;方法:

julia> foo(x::Vector{Real}) = 42
foo (generic function with 1 method)

julia> foo([1])
ERROR: MethodError: no method matching foo(::Vector{Int64})
The function `foo` exists, but no method is defined for this combination of argument types.

Closest candidates are:
  foo(!Matched::Vector{Real})
   @ Main none:1

Stacktrace:
[...]

这是因为 向量{Real} 不是 向量{Int}! 你可以用类似的东西来解决这个问题 foo(bar::Vector{T})在哪里 {T<:Real} (或简短表格 foo(bar::Vector{<:Real}) 如果静态参数 T 在函数的主体中不需要)。 该 T 是一个通配符:您首先指定它必须是Real的子类型,然后指定函数采用该类型的元素的向量。

同样的问题适用于任何复合类型 Comp公司,不只是 向量资料. 如果 Comp公司 有一个类型声明的参数 Y,然后另一种类型 公司2 具有类型的参数 X<:Y 不是 Comp公司. 这是类型不变性(相比之下,Tuple在其参数中是类型协变的)。 见 参数复合类型有关这些的更多解释。

为什么Julia使用 * 对于字符串连接? 为什么不呢? + 还是别的什么?

该 xref:manual/strings.adoc#man-concatenation【主要论点】反对 + 是字符串连接不是交换的,而 + 一般用作交换运算符。 虽然Julia社区认识到其他语言使用不同的运算符和 * 对于某些用户来说可能不熟悉,它传达了某些代数属性。

请注意,您还可以使用 字符串(。..) 连接字符串(以及转换为字符串的其他值);类似地, 重复一遍 可以用来代替 ^ 重复字符串。 该 插值语法对于构造字符串也很有用。

软件包和模块

"使用"和"导入"有什么区别?

之间有几个区别 使用进口 (请参阅https://docs.julialang.org/en/v1/manual/modules/#modules[Modules section]),但有一个重要的区别,乍一看可能看起来不直观,并且在表面上(即语法方面)它可能看起来非常小。 当加载模块与 使用,你需要说 函数Foo。吧(。.. 扩展模块 [医]脚的功能 酒吧 用一种新方法,但用 导入Foo。酒吧,你只需要说 功能栏(。.. 它会自动扩展模块 [医]脚的功能 酒吧.

这是重要的,已经给出了单独的语法的原因是,你不想意外地扩展一个你不知道存在的函数,因为这很容易导致一个错误。 这很可能发生在采用像字符串或整数这样的通用类型的方法中,因为您和其他模块都可以定义一个方法来处理这种通用类型。 如果您使用 进口,然后你将替换其他模块的实现 bar(s::AbstractString) 有了你的新实现,它可以很容易地做一些完全不同的事情(并打破模块Foo中依赖于调用bar的其他函数的所有/许多未来用法)。

虚无与缺失值

"Null","虚无"或"missingness"在Julia中是如何工作的?

与许多语言(例如,C和Java)不同,Julia对象默认情况下不能为"null"。 当引用(变量,对象字段或数组元素)未初始化时,访问它将立即引发错误。 这种情况可以使用检测 被定义的is分配功能。

有些函数仅用于其副作用,不需要返回值。 在这些情况下,约定是返回值 什么都没有,它只是一个类型的单例对象 什么都没有. 这是一个没有字段的普通类型;除了这个约定之外,它没有什么特别之处,并且REPL不会为它打印任何东西。 一些语言结构,否则不会有一个值也会产生 什么都没有,例如 如果为false;结束.

对于值的情况 x 类型 T 只存在于有时, 工会{T, Nothing} 类型可用于函数参数、对象字段和数组元素类型,相当于https://en.wikipedia.org/wiki/Nullable_type[脧锚脧赂`可空`, 选项也许吧]在其他语言。 如果值本身可以 什么都没有 (值得注意的是,当 T任何),该 联盟{Some{T},没有} 型更合适,因为 x==无 然后指示没有值,并且 X==一些(没有) 表示存在等于 什么都没有. 该 有些东西功能允许展开 一些 对象并使用默认值而不是 什么都没有 争论。 请注意,编译器能够生成有效的代码时,与 工会{T, Nothing} 参数或字段。

在统计意义上表示缺失的数据( 在R或 NULL 在SQL中),使用 失踪对象。 查看 缺失值部分了解更多详情。

在某些语言中,空元组(())被认为是虚无的规范形式。 但是,在julia中,最好将其视为恰好包含零值的常规元组。

空(或"底部")类型,写成 联合{} (空联合类型),是一种没有值和子类型(除了本身)的类型。 您通常不需要使用这种类型。

记忆

为什么 x+=y 分配内存时 xy 是数组吗?

在朱莉娅, x+=y 在降低过程中被替换 x=x+y. 对于数组,这会产生这样的结果,而不是将结果存储在内存中的相同位置 x,它分配一个新的数组来存储结果。 如果你喜欢变异 x,使用 x.+=y 以单独更新每个元素。

虽然这种行为可能会让一些人感到惊讶,但选择是深思熟虑的。 主要原因是Julia中存在不可变对象,一旦创建就不能改变它们的值。 事实上,一个数字是一个不可变的对象;语句 x=5;x+=1 不要修改 5,它们修改绑定到的值 x. 对于不可变的,更改值的唯一方法是重新分配它。

要进一步放大一点,请考虑以下函数:

function power_by_squaring(x, n::Int)
    ispow2(n) || error("This implementation only works for powers of 2")
    while n >= 2
        x &ast;= x
        n >>= 1
    end
    x
end

在一个电话之后 x=5;y=power_by_squaring(x,4),你会得到预期的结果: x==5&&y==625. 但是,现在假设 *=,当与矩阵一起使用时,改为突变左手侧。 会有两个问题:

*对于一般方阵, A=A*B 没有临时存储就无法实现: A[1,1] 在你在右手边使用它之前,被计算并存储在左手边。 *假设你愿意为计算分配一个临时的(这将消除大部分的制造点 *= 如果你利用了 x,那么这个函数对于可变输入和不可变输入的行为会有所不同。 特别是,对于不可变 x,电话后,你会有(一般) y!=x,但对于可变 x 你会的 y==x.

由于支持泛型编程被认为比可以通过其他方式(例如,使用广播或显式循环)实现的潜在性能优化更重要,因此运营商喜欢 +=*= 通过重新绑定新值来工作。

异步IO和并发同步写入

为什么对同一个流的并发写入会导致混合输出?

虽然流式I/O API是同步的,但底层实现是完全异步的。

考虑以下打印输出:

julia> @sync for i in 1:3
           Threads.@spawn write(stdout, string(i), " Foo ", " Bar ")
       end
123 Foo  Foo  Foo  Bar  Bar  Bar

这是因为,而 调用是同步的,每个参数的写入在等待I/O的那部分完成的同时产生给其他任务。

印刷业打印,打印 在呼叫期间"锁定"流。 因此改变 打印,打印 在上面的例子中导致:

julia> @sync for i in 1:3
           Threads.@spawn println(stdout, string(i), " Foo ", " Bar ")
       end
1 Foo  Bar
2 Foo  Bar
3 Foo  Bar

你可以用一个 重入锁,重入锁 像这样:

julia> l = ReentrantLock();

julia> @sync for i in 1:3
           Threads.@spawn begin
               lock(l)
               try
                   write(stdout, string(i), " Foo ", " Bar ")
               finally
                   unlock(l)
               end
           end
       end
1 Foo  Bar 2 Foo  Bar 3 Foo  Bar

数组

零维数组和标量有什么区别?

零维数组是形式的数组 阵列{T,0}. 它们的行为类似于标量,但存在重要差异。 它们值得特别提及,因为它们是一个特殊情况,考虑到数组的通用定义,这是合乎逻辑的,但起初可能有点不直观。 以下行定义了一个零维数组:

julia> A = zeros()
0-dimensional Array{Float64,0}:
0.0

在这个例子中, A 是一个包含一个元素的可变容器,可以通过 A[]=1.0 并与检索 A[]. 所有零维数组具有相同的大小(尺寸(A)==()),和长度(长度(A)==1). 特别是,零维数组不是空的。 如果你发现这个不直观,这里有一些想法可能有助于理解Julia的定义。

*零维数组是向量的"线"和矩阵的"平面"的"点"。 就像一条线没有面积(但仍然代表一组事物)一样,一个点根本没有长度或任何尺寸(但仍然代表一个事物)。 *我们定义 prod(()) 为1,数组中元素的总数是大小的乘积。 零维数组的大小为 (),因此其长度为 1. *零维数组本身没有任何索引的维度-它们只是 A[]. 我们可以对它们应用与所有其他数组维度相同的"尾随一"规则,因此您确实可以将它们索引为 A[1], A[1,1] 等;见 省略和额外索引

了解与普通标量的差异也很重要。 标量不是可变容器(即使它们是可迭代的并定义如下内容 长度, getindex,getindexe.g. 1[] == 1). 特别是,如果 x=0.0 被定义为一个标量,它是一个错误,试图通过改变它的值 x[]=1.0. 一个标量 x 可以通过转换成包含它的零维数组 填充(x),并且相反地,零维数组 a 可以通过以下方式转换为包含的标量 a[]. 另一个区别是标量可以参与线性代数运算,例如 2*兰特(2,2),但与零维数组的类似操作 填充(2)*兰德(2,2) 是错误。

为什么我的线性代数运算的Julia基准与其他语言不同?

您可能会发现线性代数构建块的简单基准如下

using BenchmarkTools
A = randn(1000, 1000)
B = randn(1000, 1000)
@btime $A \ $B
@btime $A &ast; $B

与其他语言(如Matlab或R)相比,可以有所不同。

由于像这样的操作是对相关BLAS函数的非常薄的包装,因此差异的原因很可能是

  1. 每种语言正在使用的BLAS库,

  2. 并发线程数。

Julia编译并使用自己的OpenBLAS副本,目前线程上限为 8 (或者你的核心数量)。

修改OpenBLAS设置或使用不同的BLAS库编译Julia,例如https://software.intel.com/en-us/mkl[英特尔MKL],可能会提供性能改进。 您可以使用https://github.com/JuliaComputing/MKL.jl[MKL.jl],一个使Julia的线性代数使用英特尔MKL BLAS和LAPACK而不是OpenBLAS的软件包,或者在讨论区中搜索有关如何手动设置的建议。 请注意,英特尔MKL不能与Julia捆绑在一起,因为它不是开源的。

计算集群

如何管理分布式文件系统中的预编译缓存?

在具有共享文件系统的高性能计算(HPC)设施中使用Julia时,建议使用共享仓库(通过 JULIA_DEPOT_PATH环境变量)。 自Julia v1.10以来,功能相似的worker上的多个Julia进程以及使用相同的depot将通过pidfile锁进行协调,以便在其他进程等待时只在一个进程上进行预编译。 预编译进程将指示该进程何时正在预编译或等待另一个正在预编译的进程。 如果非交互式的消息是通过 @调试.

但是,由于二进制代码的缓存,自v1.9以来的缓存拒绝更加严格,用户可能需要设置 JULIA_CPU_目标环境变量适当地获取在整个HPC环境中可用的单个缓存。

朱莉娅发布

我想使用Julia的Stable,LTS还是nightly版本?

Julia的稳定版本是Julia的最新发布版本,这是大多数人想要运行的版本。 它具有最新的功能,包括改进的性能。 Julia的稳定版本是根据https://semver.org/[SemVer]作为v1。X.y.一个新的小版本的Julia对应于一个新的稳定版本大约每4-5个月在几个星期的测试作为一个候选版本。 与LTS版本不同的是,在Julia的另一个稳定版本发布后,稳定版本通常不会收到错误修正。 但是,随着Julia v1的每个版本的发布,始终可以升级到下一个稳定版本。x将继续运行为早期版本编写的代码。

如果您正在寻找一个非常稳定的代码库,您可能更喜欢Julia的Lts(长期支持)版本。 Julia的当前LTS版本根据SemVer作为v1.6进行版本控制。这个分支将继续接收错误修正,直到选择一个新的LTS分支,此时v1.6。x系列将不再收到定期的错误修复和所有,但最保守的用户将被建议升级到新的LTS版本系列。 作为软件包开发人员,您可能更喜欢为LTS版本开发,以最大限度地增加可以使用您的软件包的用户数量。 根据SemVer,为v1.0编写的代码将继续适用于所有未来的LTS和稳定版本。 一般来说,即使针对LTS,也可以在最新的稳定版本中开发和运行代码,以利用改进的性能;只要避免使用新功能(例如添加的库函数或新方法)。

如果您想利用该语言的最新更新,您可能更喜欢Julia的夜间版本,并且不介意今天可用的版本偶尔不起作用。 顾名思义,每晚版本的发布大约每晚发布一次(取决于构建基础设施的稳定性)。 一般来说,每个发布的代码使用起来相当安全-您的代码不会着火。 但是,它们可能是偶尔的回归或问题,直到更彻底的发布前测试才会发现。 您可能希望针对每晚版本进行测试,以确保在发布之前捕获影响您的用例的此类回归。

最后,您也可以考虑为自己从source构建Julia。 此选项主要适用于那些熟悉命令行或有兴趣学习的人。 如果这描述了你,你可能也有兴趣阅读我们的https://github.com/JuliaLang/julia/blob/master/CONTRIBUTING.md[贡献准则]。

该https://julialang.org/install/[朱莉亚普 安装管理器]具有预先定义的通道命名 发行版lts 对于最新的稳定版本和当前的LTS版本,以及特定版本的渠道。

更新Julia版本后,如何传输已安装软件包的列表?

Julia的每个次要版本都有自己的默认值https://docs.julialang.org/en/v1/manual/code-loading/#Environments-1因此,在安装新的Julia次要版本时,默认情况下,您使用以前的次要版本添加的软件包将不可用。 给定julia版本的环境由文件定义 工程。汤姆尔清单。汤姆尔 在与版本号匹配的文件夹中 .朱莉娅/环境/,例如, .朱莉娅/环境/v1.3.

如果你安装了一个新的小版本的朱莉娅,说 1.4,并希望在其默认环境中使用与以前版本相同的软件包(例如 1.3),您可以复制文件的内容 工程。汤姆尔1.3 文件夹至 1.4. 然后,在新Julia版本的会话中,通过键入密钥进入"包管理模式 ],并运行命令https://julialang.github.io/Pkg.jl/v1/api/#Pkg.instantiate[脧锚脧赂`实例化`].

此操作将从复制的文件中解析与目标Julia版本兼容的一组可行软件包,并在合适的情况下安装或更新它们。 如果您不仅要复制一组包,还要复制您在以前的Julia版本中使用的版本,您还应该复制 清单。汤姆尔 运行Pkg命令前的文件 实例化. 但是,请注意,包可能会定义兼容性约束,这些约束可能会受到更改Julia版本的影响,因此您所拥有的确切版本集 1.3 可能不起作用 1.4.