Skip to content

Track backedges through not-yet-defined bindings? #53958

Closed
@Tortar

Description

@Tortar

I was reading the thread at https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481 and I found a very strange behaviour, consider this code to be run on the REPL:

input = "wjjqevffkkgbcehhiqpvqutmwxawzvjnbvukmlzxyhkgfddzfjhcujnlkjbdfgghjhujkiuytghjioplkjhgfdsaqwertyujioplkjhgfdsaqwertzuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkj"

function ispangram10(input::String)
    a,A,z,Z = UInt8('a'),UInt8('A'),UInt8('z'),UInt8('Z')
    appears::UInt32 = ~((one(UInt32)<<26)-one(UInt32))
    @inbounds for b in codeunits(input)
        if A  b  Z
            appears |= one(UInt32) << shift_safe(UInt32, b-A)
        elseif a  b  z
            appears |= one(UInt32) << shift_safe(UInt32, b-a)
        end
        appears == (-1%UInt32) && return true
    end
    return false
end
           
ispangram10(input) # this will error

shift_safe(::Type{T}, s::Integer) where {T} = s & (8 * sizeof(T) - 1)

ispangram10(input)
@time ispangram10(input)

you will see that the last timing is

julia> @time ispangram10(input)
  0.000032 seconds (137 allocations: 2.141 KiB)

now if you instead run this:

input = "wjjqevffkkgbcehhiqpvqutmwxawzvjnbvukmlzxyhkgfddzfjhcujnlkjbdfgghjhujkiuytghjioplkjhgfdsaqwertyujioplkjhgfdsaqwertzuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkj"

function ispangram10(input::String)
    a,A,z,Z = UInt8('a'),UInt8('A'),UInt8('z'),UInt8('Z')
    appears::UInt32 = ~((one(UInt32)<<26)-one(UInt32))
    @inbounds for b in codeunits(input)
        if A  b  Z
            appears |= one(UInt32) << shift_safe(UInt32, b-A)
        elseif a  b  z
            appears |= one(UInt32) << shift_safe(UInt32, b-a)
        end
        appears == (-1%UInt32) && return true
    end
    return false
end
           
#ispangram10(input) # this is the only difference, it is commented in this version

shift_safe(::Type{T}, s::Integer) where {T} = s & (8 * sizeof(T) - 1)

ispangram10(input)
@time ispangram10(input)

it returns instead

julia> @time ispangram10(input)
  0.000002 seconds

What is it happening here? Why the error in the first version makes the function perf much worse afterwards?

julia> versioninfo()
Julia Version 1.10.2
Commit bd47eca2c8a (2024-03-01 10:14 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 12 × AMD Ryzen 5 5600H with Radeon Graphics
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-15.0.7 (ORCJIT, znver3)
Threads: 1 default, 0 interactive, 1 GC (on 12 virtual cores)

edit: tested also on 1.11-alpha2, same behaviour

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions