Skip to content

match Map.tryFind k m could lower to a membership test instead of building an Option #88

Description

@simontreanor

Found while writing an ordinary program with Pyfun 0.6.0 — a Scrabble move
generator's cross-check, asked 4.4 million times per position:

let allowsLetter checks rc l =
  match Map.tryFind rc checks:
    case None: true
    case Some s: Set.contains l s

This is the language's way to say "look it up, and here is what to do in
each case". It lowers to a helper that allocates a Some, followed by a
structural match that takes it apart again:

def _pf_map_try_find(k, m):
    if k in m:
        return Some(m.get(k))
    return None_()

def allowsLetter(checks, rc, l):
    match _pf_map_try_find(rc, checks):
        case None_():
            return True
        case Some(s):
            return l in s
        case _:
            raise RuntimeError("non-exhaustive match")

The tell

The Python a person would write is

def allowsLetter(checks, rc, l):
    if rc in checks:
        return l in checks[rc]
    return True

and hand-editing the emitted module to that took the generator's hot case
from 8.21s to 7.96s — on top of the Option representation fixes in #87
with identical output. Nothing about the source changes; the Option was
only ever a way to carry the answer from the helper to the match, and
when the two are adjacent it need not exist.

Why it happens

"Map.tryFind" lowers to the helper unconditionally
(src/lowering/mod.rs:2476), and the match lowering
(src/lowering/mod.rs:1234, 1417, 3170) never looks at what the
scrutinee is. A peephole in the match lowering for a scrutinee that is a
fully applied Map.tryFind k m (likewise List.get i xs, String.get,
List.head) with exactly the None/Some v arms could bind v from
m[k] under if k in m: and put the arms in the two branches. The
Option-returning helper stays for every other use — it only disappears
where the match consumes it on the spot, which is the shape option { }
was added to encourage.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions