# Julia slower than Python for finding pangrams?

**URL:** <https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481>\
**Category:** Performance\
**Tags:** question\
**Created:** [April 3, 2024, 6:50pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481 "2024-04-03T18:50:12Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![Flavio\_Barros](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/flavio_barros/32/45443_2.png) [@Flavio\_Barros](https://discourse.julialang.org/u/Flavio_Barros)\
**Post date:** [April 3, 2024, 6:50pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/1 "2024-04-03T18:50:12Z")

</div>

I came up with a neat Julia solution using sets to solve that simple pangram. However, I’m a bit perplexed… C benchmarks show it running over 200 times faster, and even Python outperforms my Julia code substantially. Curious if anyone has insights into why this might be happening. Here’s my Julia implementation, along with the others:

**In C**

```julia
#include <stdio.h>
#include <stdlib.h>

#define ALPHABET_SIZE 26

int isPangram(char* str) {
    int alphabet[ALPHABET_SIZE] = {0};

    for (int i = 0; str[i] != '\0'; i++) {
        if (str[i] >= 'A' && str[i] <= 'Z') {
            alphabet[str[i] - 'A'] = 1;
        } else if (str[i] >= 'a' && str[i] <= 'z') {
            alphabet[str[i] - 'a'] = 1;
        }
    }

    for (int i = 0; i < ALPHABET_SIZE; i++) {
        if (alphabet[i] == 0) {
            return 0;
        }
    }

    return 1;
}

void main(int argc, char** argv) {   

    if (argc == 0) {
	    
	    printf("Provide at least one letter!");
	    exit(0);
    }

    int i;

    for(i = 1; i < argc; i++) {

       if (argc < 1) break;

       if (isPangram(argv[i])) {
           printf("yes, it's a pangram \n");
       } else {
           printf("no, it's not a pangram \n");
       }
    }

    exit(0);
}

```

**In Python**

```julia
import sys

def verifica_letras(string):
   letras_usadas = set(string.lower())
   letras_alfabeto = set('abcdefghijklmnopqrstuvwxyz')
   return letras_usadas == letras_alfabeto

if len(sys.argv) <= 1: 
    print("Provide at least one letter!")
    sys.exit()

if verifica_letras(sys.argv[1]): 
    print("yes, it's a pangram \n")
else:
    print("no, it's not a pangram \n")

```

**In Julia**

```julia
function ispangram(input)
	'a':'z' ⊆ lowercase(input) ? println("Yes, is a pangram.") : println("Not, it's not a pangram.")
end

length(ARGS) > 0 ? ispangram(ARGS[1]) : println("Provide at least one letter!")

```

**Benchmarks with hyperfine - C**

```julia
> hyperfine --shell=none --warmup 30 "./a.out wjjqevffkkgbcehhiqpvqutmwxawzvjnbvukmlzxyhkgfddzfjhcujnlkjbdfgghjhujkiuytghjioplkjhgfdsaqwertyujioplkjhgfdsaqwertzuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkj"

```

**Result**

Time (mean ± σ): 701.0 µs ± 152.1 µs [User: 525.3 µs, System: 67.9 µs]  
Range (min … max): 523.8 µs … 3765.6 µs 3569 runs

**Benchmarks with hyperfine - Python**

```julia
> hyperfine --shell=none --warmup 30 "python3 pangram.py wjjqevffkkgbcehhiqpvqutmwxawzvjnbvukmlzxyhkgfddzfjhcujnlkjbdfgghjhujkiuytghjioplkjhgfdsaqwertyujioplkjhgfdsaqwertzuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkj"

```

**Result**

Time (mean ± σ): 15.9 ms ± 3.8 ms [User: 11.6 ms, System: 3.8 ms]  
Range (min … max): 12.4 ms … 37.7 ms 210 runs

**Benchmarks with hyperfine - Julia**

```julia
hyperfine --shell=none --warmup 30 "julia --compile=all pangram.jl wjjqevffkkgbcehhiqpvqutmwxawzvjnbvukmlzxyhkgfddzfjhcujnlkjbdfgghjhujkiuytghjioplkjhgfdsaqwertyujioplkjhgfdsaqwertzuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkj"

```

**Result**

Time (mean ± σ): 266.0 ms ± 23.4 ms [User: 228.5 ms, System: 112.0 ms]  
Range (min … max): 240.1 ms … 314.1 ms 12 runs

**Puzzled**

C is expectedly much faster, but I’m surprised at the performance gap between my Python and Julia implementations. Python finished in 15 ms, while Julia took more than 17 times that. Could anyone familiar with both languages help me understand why Julia might be lagging here?

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [April 3, 2024, 6:56pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/2 "2024-04-03T18:56:30Z")

</div>

Hi! I took the liberty to rename your post to make it more specific. Very generic titles like “Julia slower than Python!!!” have the tendency to attract pile-up, and we only need a few expert opinions here 🙂

---

<div class="post-metadata">

**Author:** ![Flavio\_Barros](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/flavio_barros/32/45443_2.png) [@Flavio\_Barros](https://discourse.julialang.org/u/Flavio_Barros)\
**Post date:** [April 3, 2024, 6:57pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/3 "2024-04-03T18:57:10Z")

</div>

Fair…I am new around, sorry for that.

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [April 3, 2024, 6:57pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/4 "2024-04-03T18:57:50Z")

</div>

No worries! And now we get to sit back and watch while everyone you nerdsniped comes together to find a blazingly fast Julia implementation 😉

---

<div class="post-metadata">

**Author:** ![gdalle](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gdalle/32/27854_2.png) [@gdalle](https://discourse.julialang.org/u/gdalle)\
**Post date:** [April 3, 2024, 7:01pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/5 "2024-04-03T19:01:26Z")

</div>

Although the first thing I would say is that you’re including startup and compilation time in your benchmark, which is typically the wrong way to go about this. If you plan to run your function many times (as is typical for most functions), this one-time cost becomes negligible, and so you shouldn’t measure it. In Python it’s not too visible, but Julia has much higher latency

---

<div class="post-metadata">

**Author:** ![goerz](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerz/32/3269_2.png) [@goerz](https://discourse.julialang.org/u/goerz)\
**Post date:** [April 3, 2024, 7:01pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/6 "2024-04-03T19:01:54Z")

</div>

You are measuring startup time, and Julia (still, despite major improvements) is known to have a slow startup time: it compiles your code every time you run a new Julia process. Basically,

> [@Flavio\_Barros](#):
>
> `julia --compile=all pangram.jl `

is not how you should run Julia programs, at least not if the runtime of `pangram.jl` is less than a few minutes. Instead, you start a REPL / IJulia kernel or some other long running process and then run / include your Julia code in that.

In this case, in a new Julia REPL:

```julia
julia> function ispangram(input)
               return ('a':'z' ⊆ lowercase(input))
       end
ispangram (generic function with 1 method)

julia> input = "wjjqevffkkgbcehhiqpvqutmwxawzvjnbvukmlzxyhkgfddzfjhcujnlkjbdfgghjhujkiuytghjioplkjhgfdsaqwertyujioplkjhgfdsaqwertzuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkj"
"wjjqevffkkgbcehhiqpvqutmwxawzvjnbvukmlzxyhkgfddzfjhcujnlkjbdfgghjhujkiuytghjioplkjhgfdsaqwertyujioplkjhgfdsaqwertzuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwer
tyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkj"

julia> ispangram(input)
true

julia> using BenchmarkTools

julia> @benchmark ispangram($input)
BenchmarkTools.Trial: 10000 samples with 8 evaluations.
 Range (min … max): 3.641 μs … 245.906 μs ┊ GC (min … max): 0.00% … 96.62%
 Time (median): 4.031 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 4.366 μs ± 7.129 μs ┊ GC (mean ± σ): 5.55% ± 3.35%

            ▃▆█▄▂
  ▂▅█▆▄▄▆▆▅▆█████▇▇▆▄▄▄▄▄▃▃▃▃▂▃▂▂▂▂▂▂▂▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▃
  3.64 μs Histogram: frequency by time 5.41 μs <

 Memory estimate: 7.01 KiB, allocs estimate: 10.

```

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [April 3, 2024, 7:45pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/7 "2024-04-03T19:45:45Z")

</div>

Using more explicit `for` loop:

```julia
function ispangram3(input)
    appears = falses(26)
    for c in input
        isletter(c) || continue
        appears[lowercase(c)-'a'+1] |= true
    end
    return all(appears)
end

```

is a bit faster.

---

<div class="post-metadata">

**Author:** ![mbauman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mbauman/32/31082_2.png) [@mbauman](https://discourse.julialang.org/u/mbauman)\
**Post date:** [April 3, 2024, 8:00pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/8 "2024-04-03T20:00:59Z")

</div>

Yeah, but this is ~100% startup/compilation time. It’s kinda like timing:

```julia
hyperfine --shell=none --warmup 30 "cc pangram.c && ./a.out wjjqevffkkgbcehhiqpvqutmwxawzvjnbvukmlzxyhkgfddzfjhcujnlkjbdfgghjhujkiuytghjioplkjhgfdsaqwertyujioplkjhgfdsaqwertzuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsazxcvbnmlkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkjhgfdsaqwertyuioplkj"`

```

You might be saying “well don’t do that” — and that’s exactly what Julians will say about timing `julia pangram.jl` like that, too.

The good news is that we’re getting closer to having a real `juliac` that can output compiled stand-alone executables with significantly lower startup times like `cc` does.

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [April 3, 2024, 8:20pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/9 "2024-04-03T20:20:06Z")

</div>

> [@Dan](#):
>
> Using more explicit `for` loop […] is a bit faster.

Since you are only looking for ASCII characters, it’s even faster still to search the raw bytes of the UTF-8 encoding, via `codeunits(input)`, analogous to the C implementation.

```julia
function ispangram4(input::String)
    a,A,z,Z = UInt8('a'),UInt8('A'),UInt8('z'),UInt8('Z')
    appears = fill(false, 26) # Vector{Bool} is faster than BitVector falses(26)
    @inbounds for b in codeunits(input)
        if A ≤ b ≤ Z
            appears[b-(A-0x01)] = true
        elseif a ≤ b ≤ z
            appears[b-(a-0x01)] = true
        end
    end
    return all(appears)
end

```

Note that a `Vector{Bool}` is faster to work with than a `BitVector`, which is why I didn’t use `falses(26)` here. You could save a bit more time by pre-allocating the `appears` array, though hopefully someday Julia will stack-allocate it automatically ([Move small arrays to the stack by pchintalapudi · Pull Request #43573 · JuliaLang/julia · GitHub](https://github.com/JuliaLang/julia/pull/43573)).

But the first-order issue here is how you do the benchmarking, as commented above.

---

<div class="post-metadata">

**Author:** ![Flavio\_Barros](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/flavio_barros/32/45443_2.png) [@Flavio\_Barros](https://discourse.julialang.org/u/Flavio_Barros)\
**Post date:** [April 3, 2024, 8:36pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/10 "2024-04-03T20:36:40Z")

</div>

I appreciate all the optimization suggestions! My surprise was that simple Python and C solutions outperformed my initial Julia code. I realize now that my Julia speed test wasn’t accurate.

---

<div class="post-metadata">

**Author:** ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)\
**Post date:** [April 4, 2024, 3:38am UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/11 "2024-04-04T03:38:55Z")

</div>

Bitsets allow for efficient early stopping. Here, I manually use a `UInt32` as a bitset.

```julia
julia> function ispangram5(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) << (b-A)
               elseif a ≤ b ≤ z
                   appears |= one(UInt32) << (b-a)
               end
               appears == (-1%UInt32) && return true
           end
           false
       end
ispangram5 (generic function with 1 method)

julia> @btime ispangram4($input)
  470.774 ns (1 allocation: 80 bytes)
true

julia> @btime ispangram5($input)
  123.972 ns (0 allocations: 0 bytes)
true

```

Lack of allocations can also make a difference for shorter inputs

```julia
julia> @btime ispangram4("abcdefghijklmnopqrstuvwxyz")
  57.158 ns (1 allocation: 80 bytes)
true

julia> @btime ispangram5("abcdefghijklmnopqrstuvwxyz")
  34.608 ns (0 allocations: 0 bytes)
true

julia> @btime ispangram4("abcdefghijklmnopqrstuvwxy")
  55.704 ns (1 allocation: 80 bytes)
false

julia> @btime ispangram5("abcdefghijklmnopqrstuvwxy")
  33.771 ns (0 allocations: 0 bytes)
false

```

With some more effort, we could SIMD this…

---

<div class="post-metadata">

**Author:** ![mnemnion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mnemnion/32/206596_2.png) [@mnemnion](https://discourse.julialang.org/u/mnemnion)\
**Post date:** [April 4, 2024, 7:31pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/12 "2024-04-04T19:31:28Z")

</div>

> [@Elrod](#):
>
> Bitsets allow for efficient early stopping. Here, I manually use a `UInt32` as a bitset.

We can shave this one a bit with a mask:

```julia
julia> mask = UInt32(2^26 - 1)
0x03ffffff

julia> function ispangram6(input::String)
                  a,A,z,Z = UInt8('a'),UInt8('A'),UInt8('z'),UInt8('Z')
                  appears::UInt32 = 0
                  @inbounds for b in codeunits(input)
                      if A ≤ b ≤ Z
                          appears |= one(UInt32) << (b-A)
                      elseif a ≤ b ≤ z
                          appears |= one(UInt32) << (b-a)
                      end
                      iszero(appears ⊻ mask) && return true
                  end
                  false
              end
ispangram6 (generic function with 1 method)

julia> @btime ispangram5($input)
  82.210 ns (0 allocations: 0 bytes)
true

julia> @btime ispangram6($input)
  3.135 μs (91 allocations: 1.42 KiB)
true

julia> ispangram6("abcdeghijklmnopqrstuvwxyz")
false

```

---

<div class="post-metadata">

**Author:** ![mnemnion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mnemnion/32/206596_2.png) [@mnemnion](https://discourse.julialang.org/u/mnemnion)\
**Post date:** [April 4, 2024, 7:42pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/13 "2024-04-04T19:42:04Z")

</div>

Belatedly realized that `iszero(a ⊻ b)` is just a fancy way of saying `a == b`, that bums a few more cycles.

```julia
julia> function ispangram7(input::String)
                  a,A,z,Z = UInt8('a'),UInt8('A'),UInt8('z'),UInt8('Z')
                  appears::UInt32 = 0
                  @inbounds for b in codeunits(input)
                      if A ≤ b ≤ Z
                          appears |= one(UInt32) << (b-A)
                      elseif a ≤ b ≤ z
                          appears |= one(UInt32) << (b-a)
                      end
                      appears == mask && return true
                  end
                  false
              end
ispangram7 (generic function with 1 method)

julia> @btime ispangram7($input)
  1.300 μs (92 allocations: 1.44 KiB)
true

julia> ispangram7("abcdeghijklmnopqrstuvwxyz")
false

```

The allocations are presumably because the mask is global?

---

<div class="post-metadata">

**Author:** ![mnemnion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mnemnion/32/206596_2.png) [@mnemnion](https://discourse.julialang.org/u/mnemnion)\
**Post date:** [April 4, 2024, 7:44pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/14 "2024-04-04T19:44:55Z")

</div>

> [@mnemnion](#):
>
> The allocations are presumably because the mask is global?

Yes:

```julia
julia> function ispangram8(input::String, mask::UInt32)
                  a,A,z,Z = UInt8('a'),UInt8('A'),UInt8('z'),UInt8('Z')
                  appears::UInt32 = 0
                  @inbounds for b in codeunits(input)
                      if A ≤ b ≤ Z
                          appears |= one(UInt32) << (b-A)
                      elseif a ≤ b ≤ z
                          appears |= one(UInt32) << (b-a)
                      end
                      appears == mask && return true
                  end
                  false
              end
ispangram8 (generic function with 2 methods)

julia> @btime ispangram8($input, $mask)
  97.457 ns (0 allocations: 0 bytes)

```

Edit: so I did, just now, notice that I was comparing nanoseconds with microseconds earlier. Oops.

---

<div class="post-metadata">

**Author:** ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)\
**Post date:** [April 4, 2024, 7:46pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/15 "2024-04-04T19:46:44Z")

</div>

That’s still slower than `ispangram5`?  
But we should just be testing vs `0`, except that’s slower for me, too

```julia
julia> @btime ispangram5($input)
  85.659 ns (0 allocations: 0 bytes)
true

julia> @btime ispangram9($input)
  123.144 ns (0 allocations: 0 bytes)
true

```

Which isn’t surprising

```asm
	cmp	ecx, -1
	jne	.LBB0_10

```

macro-op fuses into one uop, so we shouldn’t really expect some other comparison to be noticeably faster.

---

<div class="post-metadata">

**Author:** ![mnemnion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mnemnion/32/206596_2.png) [@mnemnion](https://discourse.julialang.org/u/mnemnion)\
**Post date:** [April 4, 2024, 7:56pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/16 "2024-04-04T19:56:06Z")

</div>

> [@Elrod](#):
>
> That’s still slower than `ispangram5`?  
> But we should just be testing vs `0`, except that’s slower for me, too

Yeah, wasn’t paying attention there.

This, on the other hand, just barely squeezes past it:

```julia
julia> @inline shift_safe(::Type{T}, s::Integer) where {T} = s & (8 * sizeof(T) - 1)
shift_safe (generic function with 1 method)

julia> function ispangram9(input::String, mask::UInt32)
                  a,A,z,Z = UInt8('a'),UInt8('A'),UInt8('z'),UInt8('Z')
                  appears::UInt32 = 0
                  @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 == mask && return true
                  end
                  false
              end
ispangram9 (generic function with 1 method)

julia> @btime ispangram8($input, $mask)
  97.501 ns (0 allocations: 0 bytes)
true

julia> @btime ispangram9($input, $mask)
  80.317 ns (0 allocations: 0 bytes)
true

julia> @btime ispangram5($input)
  82.212 ns (0 allocations: 0 bytes)

```

Borrowed that from BitPermutations.jl, credit where credit is due.

But of course, combining this with pangram5 is the real win:

```julia
julia> 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
                  false
              end
ispangram10 (generic function with 1 method)

julia> @btime ispangram10($input)
  57.249 ns (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

**Author:** ![mkitti](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mkitti/32/12459_2.png) [@mkitti](https://discourse.julialang.org/u/mkitti)\
**Post date:** [April 4, 2024, 10:40pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/17 "2024-04-04T22:40:59Z")

</div>

I wonder if StaticCompiler.jl could be applied here.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [April 4, 2024, 11:36pm UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/18 "2024-04-04T23:36:37Z")

</div>

> [@mnemnion](#):
>
> ```julia
> 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
> 
> ```

This `if` is very cumbersome in source and perhaps generates branching code. Since we are dealing with UInt8s we might as well do bitwise lowercasing:

```julia
b2 = b | 0x20

```

and use `b2` for `appears`.

---

<div class="post-metadata">

**Author:** ![mnemnion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mnemnion/32/206596_2.png) [@mnemnion](https://discourse.julialang.org/u/mnemnion)\
**Post date:** [April 5, 2024, 12:49am UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/19 "2024-04-05T00:49:37Z")

</div>

> [@Dan](#):
>
> This `if` is very cumbersome in source and perhaps generates branching code.

Good call! There’s no need for a branch at all, because ASCII is very cleverly designed to help us out.

```julia
julia> function ispangram11(input::String)
                         appears::UInt32 = ~((one(UInt32)<<26)-one(UInt32))
                         @inbounds for b in codeunits(input)
                             appears |= one(UInt32) << shift_safe(UInt32, (b & 0x9f) - 1)
                             appears == (-1%UInt32) && return true
                         end
                         println(bitstring(appears))
                         false
                     end
ispangram11 (generic function with 1 method)

julia> @btime ispangram11($input)
  45.202 ns (0 allocations: 0 bytes)
true

julia> @btime ispangram10($input)
  57.266 ns (0 allocations: 0 bytes)
true

```

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [April 5, 2024, 1:33am UTC](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481/20 "2024-04-05T01:33:29Z")

</div>

> [@mnemnion](#):
>
> `appears |= one(UInt32) << shift_safe(UInt32, (b & 0x9f) - 1)`

That’s not correct if there are characters other than ASCII letters. e.g.

```julia
julia> b = UInt8('\r'); (b & 0x9f) - 1
12

```

[Next page](https://discourse.julialang.org/t/julia-slower-than-python-for-finding-pangrams/112481.md?page=2)
