Zulip Chat Archive
Stream: Is there code for X?
Topic: Parallel Execution of `List.map`
Asei Inoue (Jul 16 2025 at 03:51):
How can I execute List.map in parallel?
Asei Inoue (Jul 16 2025 at 03:58):
-- Define the syntax for a tactic named `my_exact?` (make `my_exact?` recognized as a syntax)
syntax (name := myExact?) "my_exact?" : tactic
open Lean Elab Tactic in
-- Define the implementation of the `my_exact?` tactic
@[tactic myExact?]
def evalMyExact? : Tactic := fun _stx => do
-- Retrieve the current environment (which stores theorems and other constants)
let env ← getEnv
-- Get the constants in the environment and filter them under the following conditions:
-- 1. They are not marked as unsafe
-- 2. Their kind is either `axiom` or `thm` (theorem)
let theorems : List Name := SMap.toList (Environment.constants env)
|>.filter (fun (_name, info) => ! ConstantInfo.isUnsafe info)
|>.filterMap (fun (name, _info) => do
let kind ← getOriginalConstKind? env name
match kind with
| .axiom | .thm => name
| _ => none
)
-- Try each of the theorems that meet the conditions
for name in theorems do
try
-- Convert the name into a syntax node
let nameStx := mkIdent name
-- Expand and execute `apply name <;> assumption` as a tactic expression
evalTactic <| ← `(tactic| apply $nameStx <;> assumption)
-- If successful, log a message and exit the tactic
logInfo m!"Applied {name} successfully."
return
catch _ =>
-- On failure, continue to the next theorem
continue
-- If no theorem could be applied, fail as a tactic
failure
set_option maxHeartbeats 500000 in
-- Example usage
example (x y : Nat) (h : x = y) : y = x := by
my_exact?
I wrote a tactic that imitates exact? as shown above, but it is extremely slow, which is a problem.
I want to speed it up by using parallel execution.
Damiano Testa (Jul 16 2025 at 04:09):
This is not an answer to your question, but I think that exact? is relatively fast because it preprocesses lemmas, so that it does not even try to apply the ones that are clearly not applicable. For that, I think that it uses a discrimination tree. I suspect that using some quicker test to pre-filter the lemmas that you apply has a larger impact on performance than just dividing by the number of cores.
Markus Himmel (Jul 16 2025 at 08:03):
Using the new Async monad in Lean 4.22 (it's still Internal in Lean 4.22, so use at your own risk. It will be officially released later this year):
import Std.Internal.Async.Basic
open Std.Internal.IO.Async
def expensiveOperation (n : Nat) : Async Nat := do
IO.sleep 1000
return 2 * n
def manyInParallel (n : Nat) : Async Nat := do
let tasks := (Array.range n).map expensiveOperation
let results ← concurrentlyAll tasks
return results.sum
-- Only runs about a second
#eval (manyInParallel 10).wait
Niklas Halonen (Jul 18 2025 at 15:28):
Thanks @Markus Himmel , this is really cool!
To more or less answer the question, I have devised the following definition for the Async map function for Array:
import Std.Internal.Async.Basic
open Std.Internal.IO.Async
def process (x : Nat) : Async Nat := do
-- IO.println x
IO.sleep 1000
return 2 * x
def concMap (f : α → (Async β)) (xs : Array α) : Async (Array β) := concurrentlyAll (xs.map f)
#eval (concMap process (Array.range 10)).wait
List version can be gotten by pre and post-composition with Array.toList and List.toArray.
Also I have a question of my own: Why does the code with IO.println get stuck at the #eval?
In the process of experimenting with this I noticed that the Lean server at live.lean-lang.org keeps crashing after almost any code change (usually when there's a type error). I wonder if this is a known bug...
Joe Hendrix (Jul 18 2025 at 19:42):
It's been a while since I worked on it, but exact? is using a few things to run quickly.
It uses a discrimination tree so that one can quickly look up a pattern that could potentially match using apply in the tree. Furthermore, the tree data type isn't the standard Lean discrimination tree used by simp, but is a lazy discrimination tree that is tuned to allow patterns to be inserted at the root, but not fully expanded until needed in a lookup.
For concurrency, multiple lazy discrimination trees on subsets of available definitions are constructed in parallel and then merged.
Concurrency is likely fine as long as you have a relatively small number of theorems. The laziness tricks become important if you want to do things like quickly search all theorems in Mathlib.
Markus Himmel (Jul 25 2025 at 07:27):
Niklas Halonen said:
Also I have a question of my own: Why does the code with
IO.printlnget stuck at the#eval?In the process of experimenting with this I noticed that the Lean server at live.lean-lang.org keeps crashing after almost any code change (usually when there's a type error). I wonder if this is a known bug...
Yes, this is a known issue: lean4#738.
Last updated: Dec 20 2025 at 21:32 UTC