这些分配是从哪里来的,声明参数的类型是如何阻止它们的?

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 脚本。

你在计时编译。如果您再次 运行 无类型函数,您会看到它 运行 没有额外分配。

此处,当 AB 不再是全局变量时,分配也会消失,因此差异似乎可能来自类型推断更加困难(即来自类型推断代码的分配本身)。

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-constant) global variables。在全局范围内使用简单的 print 语句(或带有 setup 代码的 @benchmark 行)很好,但是任何更复杂的东西都可能需要 non-constant 变量,因此应该放在进入手册推荐的功能。 (另请参阅同一手册页中的“注释从未键入位置获取的值”部分,这是您的第二个脚本所做的。)


编辑:我会留下这个,以防将来它对其他人有用,但这个解释不适用于评论中提到的 OP 的情况。

第一次 @timed 函数时,由于您使用两个 Int 参数调用它,Julia 会自动为 modAmodBInts。 @time 宏在其结果中也包含了此编译步骤所花费的分配和时间(正如您从输出的“38.39% 编译时间”部分中看到的那样)。

当您将函数更改为具有类型注释并再次 运行 时,Julia 不需要编译任何东西,因为 longestConsecutivePrimes 的编译版本需要两个 Int 参数已经存在。所以在这个运行中,只有函数本身的执行被计时,只有它自己的分配被报告。

因此添加类型注释不会更改分配或 运行宁时间。然而,它所做的是向 longestConsecutivePrimes 函数添加了一个新的“方法”——一个具有更专门化参数的方法。因此,在这一点之后,任何使用纯 Int 参数调用函数都将使用您的第二个定义,而使用其他类型的调用 - UInt8Int128 甚至 String 例如. - 将使用您对函数的第一个定义。它是为 Any 类型有效定义的。当然,在这种情况下,这两个定义恰好是相同的。但总的来说,这种 type-specializing 函数的方式是使用 Julia 的多重分派范式的核心。