Among other things, the new history search has support for:
Showing what REPL mode was used for the command.
Selecting multiple search results to put into the prompt buffer.
Syntax highlighting of the code, matching the REPL itself.
Enter the history search and type ? to see the full help.
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.
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`
Hashing changes
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));
1.12
@btime hash($s) 8.555 μs (0 allocations: 0 bytes) 0x5fbd2717019846ea
1.13
@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);
1.12
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
1.13
Base.codeunit(m::MyString) = codeunit(m.s)
Base.codeunits(m::MyString) = codeunits(m.s)
@btime hash($m)
1.750 μs (0 allocations: 0 bytes)
0x718308e795047519The 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.
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.
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
1.12
julia> @time work(5_000_000) 1.699095 seconds (10.00 M allocations: 2.688 GiB, 79.80% gc time)
1.13
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:1734This 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
Tracing top-level evaluation with –trace-eval
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: #= /Users/me/.julia/config/startup.jl:1 =# eval: #= /Users/me/.julia/config/startup.jl:2 =# eval: #= /Users/me/.julia/config/startup.jl:3 =# eval: #= script.jl:1 =# eval: #= script.jl:2 =# 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:
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.
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: