这些分配是从哪里来的,声明参数的类型是如何阻止它们的?
Where are these allocations coming from and how does declaring the parameters' types prevent them?
所以我正在通过解决 ProjectEuler 问题来学习 Julia,并且我为 problem 27:
想出了这段代码
function isPrime(num)
num < 2 && return false
for i in 2:trunc(Int, √num)
if num % i == 0
return false
end
end
return true
end
function quadratic(n, a, b)
return n^2 + a*n + b
end
function consecutivePrimes(a, b)
count = 0
tryNum = 0
while true
currResult = quadratic(tryNum, a, b)
!isPrime(currResult) && (return count)
count += 1
tryNum += 1
end
end
function longestConsecutivePrimes(modA, modB)
longest = (0,0,0)
for a in -modA:modA, b in -modB:modB
consecutive = consecutivePrimes(a, b)
longest[1] < consecutive && (longest = (consecutive, a, b))
end
return longest
end
A, B = 999, 1000
@time size, a, b = longestConsecutivePrimes(A, B)
println("size: $size, a: $a, b: $b")
println("a*b = $(a*b)")
产生
0.106938 seconds (36.28 k allocations: 2.239 MiB, 38.39% compilation time)
size: 71, a: -61, b: 971
a*b = -59231
现在,通过将函数 longestConsecutivePrimes 中的参数替换为
function longestConsecutivePrimes(modA::Int64, modB::Int64)
当 运行 在独立于之前的终端的单独终端中时,脚本会产生
0.064875 seconds (1 allocation: 16 bytes)
size: 71, a: -61, b: 971
a*b = -59231
它的速度是原来的两倍,而且只有 1 个分配。
每个脚本 运行 都是在 linux 终端中创建的,而不是在 Julia REPL 中,在每个 运行 之间关闭终端,所以一个 运行 不会从别人的合辑中“获利”
分配是从哪里来的?声明参数类型如何阻止它们?
我觉得理解这一点将有助于我改进我以前和将来编写的 Julia 脚本。
你在计时编译。如果您再次 运行 无类型函数,您会看到它 运行 没有额外分配。
此处,当 A
和 B
不再是全局变量时,分配也会消失,因此差异似乎可能来自类型推断更加困难(即来自类型推断代码的分配本身)。
Is it considered bad practice to leave the end of my script as is, outside a function? Would it be more Julian to wrap it in a function that prints the result and the benchmarks?
这里特别重要的是avoid (non-const
ant) global variables。在全局范围内使用简单的 print
语句(或带有 setup
代码的 @benchmark
行)很好,但是任何更复杂的东西都可能需要 non-constant 变量,因此应该放在进入手册推荐的功能。 (另请参阅同一手册页中的“注释从未键入位置获取的值”部分,这是您的第二个脚本所做的。)
编辑:我会留下这个,以防将来它对其他人有用,但这个解释不适用于评论中提到的 OP 的情况。
第一次 @time
d 函数时,由于您使用两个 Int
参数调用它,Julia 会自动为 modA
和 modB
是 Int
s。 @time
宏在其结果中也包含了此编译步骤所花费的分配和时间(正如您从输出的“38.39% 编译时间”部分中看到的那样)。
当您将函数更改为具有类型注释并再次 运行 时,Julia 不需要编译任何东西,因为 longestConsecutivePrimes
的编译版本需要两个 Int
参数已经存在。所以在这个运行中,只有函数本身的执行被计时,只有它自己的分配被报告。
因此添加类型注释不会更改分配或 运行宁时间。然而,它所做的是向 longestConsecutivePrimes
函数添加了一个新的“方法”——一个具有更专门化参数的方法。因此,在这一点之后,任何使用纯 Int
参数调用函数都将使用您的第二个定义,而使用其他类型的调用 - UInt8
或 Int128
甚至 String
例如. - 将使用您对函数的第一个定义。它是为 Any
类型有效定义的。当然,在这种情况下,这两个定义恰好是相同的。但总的来说,这种 type-specializing 函数的方式是使用 Julia 的多重分派范式的核心。
所以我正在通过解决 ProjectEuler 问题来学习 Julia,并且我为 problem 27:
想出了这段代码function isPrime(num)
num < 2 && return false
for i in 2:trunc(Int, √num)
if num % i == 0
return false
end
end
return true
end
function quadratic(n, a, b)
return n^2 + a*n + b
end
function consecutivePrimes(a, b)
count = 0
tryNum = 0
while true
currResult = quadratic(tryNum, a, b)
!isPrime(currResult) && (return count)
count += 1
tryNum += 1
end
end
function longestConsecutivePrimes(modA, modB)
longest = (0,0,0)
for a in -modA:modA, b in -modB:modB
consecutive = consecutivePrimes(a, b)
longest[1] < consecutive && (longest = (consecutive, a, b))
end
return longest
end
A, B = 999, 1000
@time size, a, b = longestConsecutivePrimes(A, B)
println("size: $size, a: $a, b: $b")
println("a*b = $(a*b)")
产生
0.106938 seconds (36.28 k allocations: 2.239 MiB, 38.39% compilation time)
size: 71, a: -61, b: 971
a*b = -59231
现在,通过将函数 longestConsecutivePrimes 中的参数替换为
function longestConsecutivePrimes(modA::Int64, modB::Int64)
当 运行 在独立于之前的终端的单独终端中时,脚本会产生
0.064875 seconds (1 allocation: 16 bytes)
size: 71, a: -61, b: 971
a*b = -59231
它的速度是原来的两倍,而且只有 1 个分配。
每个脚本 运行 都是在 linux 终端中创建的,而不是在 Julia REPL 中,在每个 运行 之间关闭终端,所以一个 运行 不会从别人的合辑中“获利”
分配是从哪里来的?声明参数类型如何阻止它们?
我觉得理解这一点将有助于我改进我以前和将来编写的 Julia 脚本。
你在计时编译。如果您再次 运行 无类型函数,您会看到它 运行 没有额外分配。
此处,当 A
和 B
不再是全局变量时,分配也会消失,因此差异似乎可能来自类型推断更加困难(即来自类型推断代码的分配本身)。
Is it considered bad practice to leave the end of my script as is, outside a function? Would it be more Julian to wrap it in a function that prints the result and the benchmarks?
这里特别重要的是avoid (non-const
ant) global variables。在全局范围内使用简单的 print
语句(或带有 setup
代码的 @benchmark
行)很好,但是任何更复杂的东西都可能需要 non-constant 变量,因此应该放在进入手册推荐的功能。 (另请参阅同一手册页中的“注释从未键入位置获取的值”部分,这是您的第二个脚本所做的。)
编辑:我会留下这个,以防将来它对其他人有用,但这个解释不适用于评论中提到的 OP 的情况。
第一次 @time
d 函数时,由于您使用两个 Int
参数调用它,Julia 会自动为 modA
和 modB
是 Int
s。 @time
宏在其结果中也包含了此编译步骤所花费的分配和时间(正如您从输出的“38.39% 编译时间”部分中看到的那样)。
当您将函数更改为具有类型注释并再次 运行 时,Julia 不需要编译任何东西,因为 longestConsecutivePrimes
的编译版本需要两个 Int
参数已经存在。所以在这个运行中,只有函数本身的执行被计时,只有它自己的分配被报告。
因此添加类型注释不会更改分配或 运行宁时间。然而,它所做的是向 longestConsecutivePrimes
函数添加了一个新的“方法”——一个具有更专门化参数的方法。因此,在这一点之后,任何使用纯 Int
参数调用函数都将使用您的第二个定义,而使用其他类型的调用 - UInt8
或 Int128
甚至 String
例如. - 将使用您对函数的第一个定义。它是为 Any
类型有效定义的。当然,在这种情况下,这两个定义恰好是相同的。但总的来说,这种 type-specializing 函数的方式是使用 Julia 的多重分派范式的核心。