# Why don't operations on strings constant-fold?

**URL:** <https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597>\
**Category:** Performance\
**Tags:** strings, constant-propagation\
**Created:** [February 14, 2023, 12:42am UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597 "2023-02-14T00:42:47Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 14, 2023, 12:42am UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/1 "2023-02-14T00:42:47Z")

</div>

Why doesn’t `bar` constant-fold, although `foo` does?

```julia
julia> using BenchmarkTools

julia> foo(::Type{T}) where T = parse(T, '3')
       bar(::Type{T}) where T = parse(T, "3");

julia> @btime foo(Int)
       @btime bar(Int);
  1.500 ns (0 allocations: 0 bytes)
  16.934 ns (0 allocations: 0 bytes)

julia> @code_llvm foo(Int)
       @code_llvm bar(Int)

```

Part of me wants to say it shouldn’t, because `!isbits("abc")`. However, part of me also wants to say it should, because `"abc" ≡ "abc"`.

---

<div class="post-metadata">

**Author:** ![aviatesk](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aviatesk/32/7610_2.png) [@aviatesk](https://discourse.julialang.org/u/aviatesk)\
**Post date:** [February 14, 2023, 12:57am UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/2 "2023-02-14T00:57:31Z")

</div>

`String` isn’t constant propagated currently. You can use `Symbol` instead here to constant propagate string-like data.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 14, 2023, 1:16am UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/3 "2023-02-14T01:16:51Z")

</div>

Unfortunately `parse` doesn’t have methods which accept a symbol.

It appears we can do this though:

```julia
julia> Base.@assume_effects foldable bar(::Type{T}) where T = parse(T, "3")
bar (generic function with 1 method)

julia> @btime bar(Int)
  1.000 ns (0 allocations: 0 bytes)
3

```

This works for _this_ function, but when embedding string operations into impure functions it might not be so easy.

I presume the inability to const-prop strings isn’t a fundamental limitation, but is instead just an implementation detail of the compiler as currently implemented?

---

<div class="post-metadata">

**Author:** ![aviatesk](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aviatesk/32/7610_2.png) [@aviatesk](https://discourse.julialang.org/u/aviatesk)\
**Post date:** [February 14, 2023, 1:31am UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/4 "2023-02-14T01:31:10Z")

</div>

Constant propagation is an implementation detail of the compiler. To encourage the compiler to perform constant propagation on a function, regardless of its purity, you can use the syntax `Base.@constprop :aggressive f(...) =` . The `Base.@assume_effects` annotation does a slightly different thing (constant-folding, a.k.a. compiler-time evaluation) and should only be used on impure functions.

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 14, 2023, 2:00am UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/5 "2023-02-14T02:00:36Z")

</div>

I had tried `Base.@constprop` but it didn’t help:

```julia
julia> Base.@constprop :aggressive bar(::Type{T}) where T = parse(T, "3")
bar (generic function with 1 method)

julia> @btime bar(Int)
  16.884 ns (0 allocations: 0 bytes)
3

```

> [@aviatesk](#):
>
> `Base.@assume_effects` … should only be used on impure functions.

You mean `foldable` should only be used on _pure_ functions, yes?

---

<div class="post-metadata">

**Author:** ![aviatesk](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aviatesk/32/7610_2.png) [@aviatesk](https://discourse.julialang.org/u/aviatesk)\
**Post date:** [February 14, 2023, 2:27am UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/6 "2023-02-14T02:27:44Z")

</div>

Constant prop is a different thing from constant folding (from the compiler optimization point of view) so that’s expected. If you really need to optimize away a call to a complex function, assume\_effects based constand folding is the last resort for it.

But yes, it should only be used for “pure” function. Read carefully the documentation of assume\_effects for the details.

---

<div class="post-metadata">

**Author:** ![StefanKarpinski](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stefankarpinski/32/24_2.png) [@StefanKarpinski](https://discourse.julialang.org/u/StefanKarpinski)\
**Post date:** [February 14, 2023, 2:33am UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/7 "2023-02-14T02:33:11Z")

</div>

But also, it would be good to teach the compiler that equal strings are egal and allow it to reason accordingly.

---

<div class="post-metadata">

**Author:** ![heltonmc](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/heltonmc/32/43044_2.png) [@heltonmc](https://discourse.julialang.org/u/heltonmc)\
**Post date:** [February 14, 2023, 4:10pm UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/8 "2023-02-14T16:10:31Z")

</div>

I seem to run into this problem frequently (and should probably learn more about the recent improved effect analysis) when writing higher precision algorithms. But it would be really convenient if a structure like.

```julia
function foo(x::T) where T
    P = ("1.23", "1.24", "1.2345")
    return evalpoly(x, parse.(T, P))
end
# or storing big floats as constants
const P2 = (big"1.23", big"1.24", big"1.2345")
function foo2(x::T) where T
    return evalpoly(x, T.(P2))
end

```

could just work. A recent similar discussion was at [here](https://discourse.julialang.org/t/why-cant-the-compiler-constant-fold-operations-on-bigint-and-bigfloat/94447) and I posted a related question [here](https://discourse.julialang.org/t/storing-high-precision-constants-to-use-with-fixed-precision-libraries-doublefloats-jl-quadmath-jl-decfp-jl-etc/90967).

```julia
julia> using DoubleFloats, Quadmath

julia> @btime foo($Double64(1.2))
  1.046 μs (16 allocations: 673 bytes)
4.49568

julia> @btime foo($Float128(1.2))
  295.539 ns (1 allocation: 32 bytes)
4.49567999999999981335818688421568617e+00

julia> @btime foo2($Double64(1.2))
  373.980 ns (7 allocations: 344 bytes)
4.49568

julia> @btime foo2($Float128(1.2))
  395.318 ns (13 allocations: 608 bytes)
4.49567999999999981335818688421568617e+00

julia> @btime foo($Float64(1.2))
  133.277 ns (1 allocation: 16 bytes)
4.49568

julia> @btime foo2($Float64(1.2))
  93.990 ns (1 allocation: 16 bytes)
4.49568

```

Generally, with explicit coefficients like this the algorithm isn’t arbitrary precision so the precision of `BigFloats` can be fixed. But ideally, we would be able to write algorithms for fixed higher precisions (`Float128`, `Float256`, `Double64`) that users could also potentially use any external package and not experience all these allocations.

It looks like though the external packages could also handle this conversion better. I must admit most of this issue is my general aversion to specifically loading these packages. Or perhaps a general desire for a julia native `Float128` or `Int256` type. 🙂

---

<div class="post-metadata">

**Author:** ![uniment](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/uniment/32/24532_2.png) [@uniment](https://discourse.julialang.org/u/uniment)\
**Post date:** [February 14, 2023, 10:17pm UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/9 "2023-02-14T22:17:53Z")

</div>

Considering that it’s impractical to avoid `BigInt` and `BigFloat`, and it’s impossible (?) to make them constant-fold, probably the better approach for you will be to use a `@generated` function. See definition and performance of `foo6`, which matches the “hand-crafted” `foo5`:

```julia
julia> using DoubleFloats, Quadmath, BenchmarkTools
       Ts = (Float64, Double64, Float128);

julia> function foo(x::T) where T
           P = ("1.23", "1.24", "1.2345")
           return evalpoly(x, parse.(T, P))
       end
       const P2 = (big"1.23", big"1.24", big"1.2345")
       function foo2(x::T) where T
           return evalpoly(x, T.(P2))
       end
       Base.@assume_effects foldable foo3(x::T) where T = let P = ("1.23", "1.24", "1.2345"); evalpoly(x, parse.(T, P)) end
       Base.@assume_effects foldable foo4(x::T) where T = evalpoly(x, T.(P2))
       foreach(Ts) do T; eval(:(let P = map($T, (big"1.23", big"1.24", big"1.2345")); global foo5(x::$T) = evalpoly(x, P) end)) end
       @generated foo6(x::T) where T = let P = map(T, (big"1.23", big"1.24", big"1.2345")); :(evalpoly(x, $P)) end

       foos = (foo, foo2, foo3, foo4, foo5, foo6);

julia> foreach(foos) do f; println(f, ":"); foreach(Ts) do T; eval(:( @btime $f($T($(Expr(:$, 1.2)))) )) end end
foo:
  1.310 μs (0 allocations: 0 bytes)
  1.590 μs (15 allocations: 641 bytes)
  380.788 ns (0 allocations: 0 bytes)
foo2:
  107.309 ns (2 allocations: 80 bytes)
  606.250 ns (8 allocations: 392 bytes)
  575.824 ns (14 allocations: 656 bytes)
foo3:
  1.310 μs (0 allocations: 0 bytes)
  1.580 μs (15 allocations: 641 bytes)
  382.266 ns (0 allocations: 0 bytes)
foo4:
  107.459 ns (2 allocations: 80 bytes)
  609.659 ns (8 allocations: 392 bytes)
  577.473 ns (14 allocations: 656 bytes)
foo5:
  2.000 ns (0 allocations: 0 bytes)
  17.000 ns (0 allocations: 0 bytes)
  79.545 ns (0 allocations: 0 bytes)
foo6:
  2.000 ns (0 allocations: 0 bytes)
  17.034 ns (0 allocations: 0 bytes)
  79.442 ns (0 allocations: 0 bytes)

julia> all(Ts) do T; all(broadcast((f,x)->f(T(x)), foos, 1.2)) do x; x≡first(foos)(T(1.2)) end end # equality check
true

```

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [February 14, 2023, 11:22pm UTC](https://discourse.julialang.org/t/why-dont-operations-on-strings-constant-fold/94597/10 "2023-02-14T23:22:23Z")

</div>

fyi, [@assume\_effects :foldable for all the ccalls by oscardssmith · Pull Request #56 · JuliaMath/Quadmath.jl · GitHub](https://github.com/JuliaMath/Quadmath.jl/pull/56) should make `Float128` operations constant fold pretty well.
