Stream: helpdesk (published)

Topic: Optimizing a weird loop with an "output buffer"


view this post on Zulip Alessandro (Mar 30 2021 at 11:50):

Considering this function, which I've just moved from a recursive design to
an iterative paradigm

const Sub = Base.ImmutableDict{Any, Tuple{EClass, Any}}
const SubBuf = Vector{Sub}

# https://www.hpl.hp.com/techreports/2003/HPL-2003-148.pdf
# page 48
"""
From [https://www.hpl.hp.com/techreports/2003/HPL-2003-148.pdf](https://www.hpl.hp.com/techreports/2003/HPL-2003-148.pdf)
The iterator `ematchlist` matches a list of terms `t` to a list of E-nodes by first finding
all substitutions that match the first term to the first E-node, and then extending
each such substitution in all possible ways that match the remaining terms to
the remaining E-nodes. The base case of this recursion is the empty list, which
requires no extension to the substitution; the other case relies on Match to find the
substitutions that match the first term to the first E-node.
"""
function ematchlist(e::EGraph, t::AbstractVector{Pattern}, v::AbstractVector{Int64}, sub::Sub, buf::SubBuf)::SubBuf
    lt = length(t)
    lv = length(v)

    !(lt == lv) && (return buf)

    # currbuf = buf
    currbuf = SubBuf()
    push!(currbuf, sub)

    for i  1:lt
        newbuf = SubBuf()
        while !isempty(currbuf)
            currsub = pop!(currbuf)
            ematchstep(e, t[i], v[i], currsub, newbuf, nothing)
        end
        currbuf = newbuf
    end

    for sub1  currbuf
        push!(buf, sub1)
    end
    return buf
end

Allocating new buffers every time is expensive, and I have basically no performance gain from the recursive version. How can I optimize this loop, removing unnecessary "buffer creations", knowing that i have my output buf already waiting to be "push!-ed" ? A thing worth noting is that the subcall to ematchstep pushes new elements to newbuf.


Last updated: Oct 02 2023 at 04:34 UTC