0

I have the following version of isPrime written (and proved) in Coq.

  • It takes around 30 seconds for Compute (isPrime 330) to finish on my machine.
  • The extracted Haskell code takes around 1 second to verify that 9767 is prime.

According to a comment in this post, the timing difference means nothing, but I wonder why is that? and is there any other way to predict performance when extracting Coq code? after all, sometimes performance does matter, and it's quite hard to change Coq source once you labored to prove it's correct. Here is my Coq code:

(***********)
(* IMPORTS *)
(***********)
Require Import Coq.Arith.PeanoNat.

(************)
(* helper'' *)
(************)
Fixpoint helper' (p m n : nat) : bool :=
  match m with
  | 0 => false
  | 1 => false
  | S m' => (orb ((mult m n) =? p) (helper' p m' n))
  end.

(**********)
(* helper *)
(**********)
Fixpoint helper (p m : nat) : bool :=
  match m with
  | 0 => false
  | S m' => (orb ((mult m m) =? p) (orb (helper' p m' m) (helper p m')))
  end.

(***********)
(* isPrime *)
(***********)
Fixpoint isPrime (p : nat) : bool :=
  match p with
  | 0 => false
  | 1 => false
  | S p' => (negb (helper p p'))
  end.

(***********************)
(* Compute isPrime 330 *)
(***********************)
Compute (isPrime 330).

(********************************)
(* Extraction Language: Haskell *)
(********************************)
Extraction Language Haskell.

(***************************)
(* Use Haskell basic types *)
(***************************)
Require Import ExtrHaskellBasic.

(****************************************)
(* Use Haskell support for Nat handling *)
(****************************************)
Require Import ExtrHaskellNatNum.
Extract Inductive Datatypes.nat => "Prelude.Integer" ["0" "succ"]
"(\fO fS n -> if n Prelude.== 0 then fO () else fS (n Prelude.- 1))".

(***************************)
(* Extract to Haskell file *)
(***************************)
Extraction "/home/oren/GIT/CoqIt/FOLDER_2_PRESENTATION/FOLDER_2_EXAMPLES/EXAMPLE_03_PrintPrimes_Performance_Haskell.hs" isPrime.
OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87
  • FYI, Haskell has `Numeric.Natural.Natural`, which is basically exactly `Integer` but unsigned. – HTNW Apr 22 '19 at 15:07

1 Answers1

2

Your Coq code is using a Peano encoding of the naturals. The evaluation of mult 2 2 literally proceeds by the reduction:

mult (S (S 0)) (S (S 0)))
= (S (S 0)) + mult (S 0) (S (S 0)))
= (S (S 0)) + ((S (S 0)) + mult 0 (S (S 0)))
= (S (S 0)) + ((S (S 0)) + 0)
= (S (S 0)) + ((S 0) + (S 0))
= (S (S 0)) + (0 + (S (S 0))
= (S (S 0)) + (S (S 0))
= (S 0) + (S (S (S 0)))
= 0 + (S (S (S (S 0)))
= (S (S (S (S 0))))

and then checking the equality mult 2 2 =? 5 proceeds by the further reduction:

(S (S (S (S 0)))) =? (S (S (S (S (S 0)))))
(S (S (S 0))) =? (S (S (S (S 0))))
(S (S 0)) =? (S (S (S 0)))
(S 0) =? (S (S 0))
0 =? (S 0)
false

Meanwhile, on the Haskell side, the evaluation of 2 * 2 == 5 proceeds by multiplying two Integers and comparing them to another Integer. This is somewhat faster. ;)

What's incredible here is that Coq's evaluation of isPrime 330 only takes 30 seconds instead of, say, 30 years.

I don't know what to say about predicting the speed of extracted code, except to say that primitive operations on Peano numbers will be massively accelerated, and other code will probably be modestly faster, simply because a lot of work has gone into making GHC generate fast code, and performance hasn't been an emphasis in Coq's development.

K. A. Buhr
  • 45,621
  • 3
  • 45
  • 71
  • "What's incredible here is that Coq's evaluation of isPrime 330 only takes 30 seconds instead of, say, 30 years." Arithmetic with this representation of integers is slow, sure, but not _that_ slow! The algorithm still has to work with only a couple of *fairly* short lists in the end. – Cubic Apr 22 '19 at 20:29
  • Yes, I was exaggerating... ;) – K. A. Buhr Apr 22 '19 at 20:34
  • To predict computation times, you need to fix the execution model. Most of the time, you can't describe it formally so that you can only apply rules of thumb. Peano arithmetic is one problem, but order of execution also matters: remember that call-by-value and call-by-name differ greatly, and Haskell is not applying call-by-name either, since lazy evaluation includes some sort of memoisation. – Yves Apr 23 '19 at 06:25