Julia 1.13 版本亮点
Julia 1.13 Highlights

原始链接: https://julialang.org/blog/2026/09/julia-1.13-highlights/

Julia 1.13 现已发布,带来了显著的性能提升和易用性改进。 **性能与效率:** * **更快的启动与预编译:** 包预编译速度比 1.12 版本提升约 30%,整体启动时间缩短约 20%。 * **垃圾回收 (GC):** 完全垃圾回收速度大幅提升,因为系统镜像中的对象已被永久标记,这意味着回收时间现在取决于用户的堆内存大小,而非已加载代码的总量。 * **哈希算法:** 采用全新的高性能纯 Julia 哈希算法 (RapidhashNano) 替代了 MurmurHash3,显著提升了字符串和数值类型的处理速度。 * **多线程:** 调度程序的改进大幅降低了在频繁创建任务负载下的开销,特别是在 Windows 系统上。 **REPL 与工作流改进:** * **原生 REPL:** REPL 现在内置了语法高亮,并增强了类似 `fzf` 的模糊搜索历史记录功能。 * **开发工具:** 改进后的内省宏允许将类型直接传递给 `@which` 等工具。新增的 `--trace-eval` 标志有助于调试加载耗时。 * **Pkg 更新:** Pkg 现在使用 zstd 压缩以加快下载速度,并包含更智能的依赖解析机制,以避免冗余的预编译。 * **Juliaup:** 新增了用于管理版本和配置的图形界面 (`juliaup gui`)。

Hacker News 最新 | 往期 | 评论 | 提问 | 展示 | 招聘 | 提交 登录 Julia 1.13 版本亮点 (julialang.org) 31 分,eigenspace 发布于 1 小时前 | 隐藏 | 往期 | 收藏 | 2 条评论 | 帮助 eigenspace 1 小时前 | 下一条 [–] 由于发布周期的原因,大多数新的主要功能都被推迟到了 v1.14 版本。本次发布主要是一个迭代版本,侧重于提升各项性能、修复漏洞以及进行常规打磨。 不过,垃圾回收(GC)速度的提升、启动延迟的降低、中断处理的优化、新的 REPL 功能以及更快的包管理,这些依然是非常棒的改进。我特别高兴的是,现在当你 `add` 一个非注册包时,包的 `[sources]` 部分可以递归应用了。 回复 brudgers 32 分钟前 | 上一条 [–] 当前讨论:https://news.ycombinator.com/item?id=49651384 回复 指南 | 常见问题 | 列表 | API | 安全 | 法律 | 申请 YC | 联系 搜索:
相关文章

原文

Julia version 1.13 has been released. We want to thank all the contributors to this release and all the testers who helped find regressions and issues in the pre-releases. Without you, this release would not have been possible.

The full list of changes can be found in the NEWS file, but here we'll give a more in-depth overview of some of the release highlights.

Ian Butterworth, many others

Julia 1.13 takes roughly 30% less time to precompile packages than 1.12, and roughly 10-20% less time than 1.10 (LTS) depending on the machine.

Time To First X (TTFX), the time from starting Julia to getting a first result, is made up of three main costs: precompiling packages, loading them, and running the code. With the help of the community-submitted workflows at Julia-TTFX-Snippets, we have started measuring these costs more systematically on real-world examples and optimizing Julia against them.

The chart below shows the geometric mean across all 39 currently submitted workflows, on two machines. Precompilation is the fastest of 2 runs; load and execution times are the fastest of 3 runs.

This monitoring is now also part of Julia's own development process: new TTFX CI jobs run on relevant pull requests and on every commit to master, and the results are tracked at perf.julialang.org/ttfx. That tracking went live on September 7, 2026; measurements before then were ad hoc.

Julia 1.13 startup is also ~20% faster than 1.12.

% hyperfine --warmup 3 --runs 20 -N \
  --command-name "julia 1.12" "julia +1.12 --startup-file=no -e ''" \
  --command-name "julia 1.13" "julia +1.13 --startup-file=no -e ''"
Benchmark 1: julia 1.12
  Time (mean ± σ):      69.1 ms ±   1.0 ms    [User: 50.1 ms, System: 18.1 ms]
  Range (min … max):    68.0 ms …  72.6 ms    20 runs

Benchmark 2: julia 1.13
  Time (mean ± σ):      56.7 ms ±   0.5 ms    [User: 49.1 ms, System: 18.9 ms]
  Range (min … max):    56.0 ms …  58.1 ms    20 runs

Summary
  julia 1.13 ran
    1.22 ± 0.02 times faster than julia 1.12

Timothy, Kristoffer Carlsson

The Julia REPL now has syntax highlighting (without having to load an external package like OhMyREPL.jl):

REPL syntax highlighting

By default, the color scheme is quite conservative, but it is easy to customize (see the documentation for the REPL). As an example, here is the same code but using the Monokai color scheme:

REPL syntax highlighting with the Monokai color scheme

Bracketed paste allows an application running in a terminal to know when text is being pasted (as opposed to just being typed). This can allow for more efficient and correct processing of the text being pasted. This functionality has been enabled on Linux and macOS for a long time but is now also finally available on Windows. As a concrete example, the videos below show the behavior of pasting a ~500-line function into the Julia REPL before and after enabling bracketed paste on Windows.

Before:

After:

Miles Cranmer, Jeff Bezanson

Like the existing @__MODULE__ and @__FILE__ macros, the new @__FUNCTION__ macro references the innermost containing function even if that function is anonymous. This should work in all kinds of functions, and is public API, unlike the internal variable #self#.

julia> fact = n -> n <= 1 ? 1 : n * @__FUNCTION__()(n - 1);

julia> fact(5)
120

Andy Dienes, Jameson Nash

The hash function has been replaced. The byte-hashing algorithm is now RapidhashNano. This hash is used by default for AbstractString and many numeric types like BigInt, Rational, and large Real or Integer values. It is also much easier now for custom types to opt in to the generic implementations without having to first convert to a supported type (like String). This change offers several advantages compared to the pre-existing implementation based on MurmurHash3. It has significantly better performance, is a streaming hash so it no longer requires the length of the input up front, and has moved from C to pure Julia for better readability and maintainability.

To demonstrate the performance improvement on long strings:

using BenchmarkTools, Downloads

io = IOBuffer()
Downloads.download("https://www.gutenberg.org/cache/epub/1080/pg1080.txt", io)
s = String(take!(io));


@btime hash($s)
  8.555 μs (0 allocations: 0 bytes)
0x5fbd2717019846ea


@btime hash($s)
  1.742 μs (0 allocations: 0 bytes)
0x718308e795047519

And a demonstration of opting in to a faster fallback:

struct MyString <: AbstractString
    s::String
end
m = MyString(s);


Base.iterate(m::MyString) = iterate(m.s)
Base.iterate(m::MyString, i::Integer) = iterate(m.s, i)
@btime hash($m)
  204.583 μs (21 allocations: 107.02 KiB)
0x5fbd2717019846ea


Base.codeunit(m::MyString) = codeunit(m.s)
Base.codeunits(m::MyString) = codeunits(m.s)
@btime hash($m)
  1.750 μs (0 allocations: 0 bytes)
0x718308e795047519

The hash for small fixed-width data has also changed. The final mixing step is now a single-round XMX construction with some carefully tuned constants, and the mixing step now properly avalanches when composing hash calls; previously the mixing step always simplified to a linear function at every composition depth. This change to the mixing step does introduce a data dependency (and thus potentially lower performance) when sequentially hashing elements together in a tight loop, e.g. foldr(hash, collection), but the algorithm for hashing AbstractArray has been partially unrolled at small to medium sizes, maintaining several hash accumulators in parallel, and will be much faster at most lengths.

Some important reminders: hash remains noncryptographic. Also, the default seed has changed. Custom hash methods should always accept the seed as an argument like hash(x::MyType, h::UInt) and never provide a default value like hash(x::MyType, h::UInt=0), since the correct seed is determined by the caller.

Cody Tapscott

Every Julia session starts with a large number of objects that were loaded from the system image, and every package that gets loaded brings its own package image with even more of them: method tables, type information, compiled code, constants and so on. These objects are never freed, and they are rarely mutated, yet until now a full garbage collection would walk through all of them to mark them as reachable, just like any other object on the heap. For a session with a handful of large packages loaded, this could easily be the dominant cost of a full collection.

In Julia 1.13, objects in the sysimage and in package images are loaded as permanently marked and the mark phase never enters them. The few mutations that do happen to image objects (for example, when a method is added to an existing function) are tracked separately so that any new objects they point to are still kept alive. The effect is that the cost of a full collection now scales with the size of the heap that your program actually created, not with the amount of code that has been loaded.

The easiest way to see the difference is to time a full collection in a fresh session:

# 1.12
julia> @time GC.gc()
  0.035493 seconds (99.90% gc time)

# 1.13
julia> @time GC.gc()
  0.000528 seconds (99.08% gc time)

The table below shows the time for a full collection (GC.gc(true)) on an Apple M4 Pro, first in a bare session and then after loading some packages of increasing size. Incremental (young generation) collections are not affected by this change and are equally fast on both versions.

1.121.13
Bare session35 ms2 ms
using Revise50 ms11 ms
using Cthulhu59 ms18 ms
using PythonCall90 ms30 ms
using GLMakie187 ms68 ms

Since full collections are triggered more often for programs with a large live heap, this also shows up as reduced overall GC time in real workloads. The following example inserts random vectors into a Dict that is kept alive across iterations, so that a large fraction of the allocated objects get promoted to the old generation:

function work(n)
    d = Dict{Int,Vector{Float64}}()
    for i in 1:n
        d[i % 50_000] = rand(64)
    end
    return length(d)
end


julia> @time work(5_000_000)
  1.699095 seconds (10.00 M allocations: 2.688 GiB, 79.80% gc time)


julia> @time work(5_000_000)
  0.566276 seconds (10.00 M allocations: 2.688 GiB, 44.32% gc time)

For more details, see the pull request.

Kiran Pamnany, Jameson Nash, Ian Butterworth

Idle threads now park in a dedicated scheduler task instead of holding on to the last task they ran, so finished tasks can be garbage collected promptly (#57544). This lands alongside a set of related scheduler fixes, including ones that make interrupts reliable again (#62665):

  • Ctrl-C reaches user code again, including scripts blocked in sleep or IO, and Distributed.interrupt works.

  • The REPL survives repeated and badly timed Ctrl-C presses.

  • @spawn wakes one idle thread in the task's threadpool instead of every thread (#61826). Spawn-heavy code speeds up anywhere from not at all on macOS, to 1.1-1.6x on a 16-core Linux machine, to 10-300x on Windows and heavily oversubscribed machines, where waking every thread had been the dominant cost.

  • Several lost-task and deadlock races were fixed.

Work on a proper task cancellation mechanism is in progress and is planned for Julia 1.14.

The code introspection macros (@which, @code_typed, @code_warntype, etc.) now accept call expressions where arguments are given as types instead of values, using the same ::T syntax as in method definitions and stacktraces. Values and types can be freely mixed, and keyword arguments are supported:

julia> @which push!(::Vector{Int}, 1)
push!(a::Vector{T}, item) where T
     @ Base array.jl:1339

julia> @which sort!(::Vector{Int}; by = ::Function)
kwcall(::NamedTuple, ::typeof(sort!), v::AbstractVector{T}) where T
     @ Base.Sort sort.jl:1734

This means a frame can be copied straight out of a stacktrace and pasted into @which to find the method that was called:

julia> @which Base.Order.lt(o::Base.Order.Lt{typeof(isless)}, a::Int64, b::Int64)
lt(o::Base.Order.Lt, a, b)
     @ Base.Order ordering.jl:121

Broadcasting expressions are also supported in @code_lowered, @code_typed and @code_warntype:

julia> @code_warntype (::Vector{Int}) .+ 1.0

Ian Butterworth

The new --trace-eval command-line flag shows top-level evaluation progress, to help see how a test suite or script is advancing, e.g. to identify hangs. For instance:

% julia --trace-eval script.jl
eval: 
eval: 
eval: 
eval: 
eval: 
Hello world

It is also enabled automatically when the "debug logging" option is turned on for a CI run, as shown here for GitHub Actions:

GitHub Actions re-run dialog with "Enable debug logging" checked

Cody Tapscott, many others

The juliac.jl script in the Julia repo has been made into a proper package/application: JuliaC.jl.

More code can now be trimmed, such as finalizers, @cfunction and mapreduce.

Several bugs in the trimming process itself were also fixed, improving its reliability.

Kristoffer Carlsson

Pkg has received quite a bit of attention for 1.13. Here we list some of the more notable changes and improvements.

For downloads from a package server (registries, packages and artifacts), Pkg will now by default ask for a zstd-compressed archive instead of a gzipped one. For the type of files Pkg typically downloads, zstd compression tends to have both a better compression ratio and significantly better decompression performance. As an example, downloading the packages and artifacts for the packages Plots, Makie and ModelingToolkit results in the following numbers:

gzipzstd
Total downloads405405
Total download size307.99 MB239.31 MB
Total decompression time8.77 s5.50 s
Average decompression time21.98 ms13.77 ms

Some micro-optimizations have been made to the resolver and the registry processing, leading to generally better performance of Pkg operations. Some of these improvements have already been backported to 1.12, so to get a proper performance comparison we compare against 1.12.1, which did not get any of these backports.

To assess the impact on resolver speed, we do the following benchmark: we add Plots to an empty environment, remove it, and then benchmark the time it takes to add Plots again. This ensures that all the files for Plots are already downloaded. In addition, auto-precompilation is turned off and the registry cache is cleared so that it has to be re-read from scratch. This means that the time spent adding Plots to this environment is mostly registry processing and resolving:

julia> ENV["JULIA_PKG_PRECOMPILE_AUTO"] = 0


julia> empty!(Pkg.Registry.REGISTRY_CACHE); @time Pkg.add("Plots"; io=devnull)
  1.257017 seconds (8.83 M allocations: 681.328 MiB, 16.31% gc time)


julia> empty!(Pkg.Registry.REGISTRY_CACHE); @time Pkg.add("Plots"; io=devnull)
  0.745170 seconds (4.43 M allocations: 304.580 MiB, 26.90% gc time)

In addition, Pkg will now clone repos with more efficient settings, avoiding downloading unnecessary data:


julia> @time Pkg.add(name="Plots"; rev="master")
     Cloning git-repo `https://github.com/JuliaPlots/Plots.jl.git`
...
 10.953074 seconds (4.51 M allocations: 330.819 MiB, 1.68% gc time)


julia> @time Pkg.add(name="Plots"; rev="master")
     Cloning git-repo `https://github.com/JuliaPlots/Plots.jl.git`
...
  2.980337 seconds (2.87 M allocations: 189.202 MiB, 3.87% gc time)

Previously, to instantiate a manifest you needed to manually make sure that the registries required by that manifest were available. Now, the registry each package came from is recorded in the manifest and is automatically installed upon manifest instantiation (or other package operations).

Pkg now recursively collects [sources] entries from packages fetched by URL, allowing private dependency chains to resolve without requiring all dependencies of a private package to be in a registry.

Julia has always allowed changing the active project during a session and supports stacked environments (most commonly via the default environment), which introduces a rough edge that can lead to repeated precompilation of packages. For instance, a version of a package is loaded from the default environment during startup.jl, and then the user adds a new package to the active project that pulls in a different version of that dependency. Pkg precompiles the dependency graph of the active project, so the new version gets precompiled even though the already-loaded version would often have satisfied the compat constraints just as well.

In 1.13, Pkg prefers the currently loaded version of any package that is already loaded when resolving pkg> add, if the environment's compatibility constraints allow it, so nothing needs to be precompiled again. As usual, pkg> status will flag that a newer version is available.

Previously, Pkg.test always launched the test process with --check-bounds=yes, which forces bounds checking even inside @inbounds blocks. Since precompile cache files are specific to the bounds-checking mode, this meant that the package being tested and all of its dependencies typically had to be recompiled before the tests could even start, and those cache files were then useless for normal development. Pkg.test now leaves the bounds-checking mode alone, so the test process inherits it from the parent Julia session and can reuse the precompile files generated during development. To get the old behavior, either start Julia with --check-bounds=yes before running Pkg.test, or pass the flag explicitly with Pkg.test(; julia_args=["--check-bounds=yes"]).

Ian Butterworth

Juliaup, the Julia version manager, now has a graphical interface alongside its command line. It ships with Juliaup 1.22 and later on every platform Juliaup supports, so after a juliaup self update it can be opened with:

juliaup gui

The Installed tab shows each installed channel as a tile or a list row. From there a channel can be launched, launched with a custom project, arguments and environment variables, set as the default, or removed, and there are one-click actions to update everything and to garbage collect versions no channel uses any more.

The Juliaup GUI's Installed tab, showing installed Julia channels as tiles

The Available tab lists everything in the channel database, including release, lts, rc, nightly and pr{number} channels for testing pull requests, with an install button for each. It can also link an existing Julia binary to a custom channel name. The Configuration tab exposes Juliaup's settings, such as the version database update interval and automatic self-updates.

The Juliaup GUI's Available tab, listing channels that can be installed

联系我们 contact @ memedata.com