The nofib benchmark suite contains a number of "realistic" Haskell programs used for benchmarking. See this paper (ps.gz) for more information. Brief notes on all of the programs can be found in NOFIB.md.
This repository contains versions of some of these programs as Plutus programs. The nofib programs are generally quite old (mostly written in the early 1990s) and don't bear much resemblance to typical modern Haskell programs (which may be an advantage, since they should be more acceptable to the Plutus compiler). I've resisted the temptation to modernise the programs, except that most of them have no type signatures for functions and I've added signatures (sometimes less general than they could be).
The programs that I've ported so far are
- clausify
- constraints (renamed to
constraints-plutus
to avoid a package name clash) - knights
- lastpiece
- primetest
These are all from the spectral
subdirectory. Apart from knights
these all
consist of a single source file. The primetest
program originally consisted
of several Literate Haskell source files, but I converted it into a single file
and removed most of the literate stuff. All of the source files are contained
in the relevant app
subdirectory.
The programs have all been converted into standalone programs which it should be
possible to run by typing stack run
in the relevant directory: this produces
textual PLC output which I've been running via the plc
command, but it would
be straightforward to have the CEK machine execute the code directly. Paths to
various plutus-*
libraries will need to be updated in stack.yaml
.
WARNING The lastpiece
program solves a fixed-size puzzle. The Haskell
version runs quite quickly, but on the CEK machine the Plutus Core version
quickly runs out of memory. It may be possible to fix this, but in the meantime
lastpiece
shouldn't be used for benchmarking.
With the exception of lastpiece
, each program is supplied with some arguments
in Main.hs
which makes the program run in a few seconds and use 2 or 3 GB of
memory. These inputs could be changed if required. Most programs also
can also be run directly as Haskell programs with suitable modifications (often
already contained in the source file, possibly commented out).
Most of the programs contain notes about the time/memory usage of the Plutus
vesrion with various inputs. These are usually near the Main
function.
A number of problems arose while I was porting the programs.
-
Compilation flags. I had quite a lot of trouble getting the compilation flags right, particularly in programs involving multiple files. You generally seem to be able to get away with
- -fno-strictness
- -fno-specialise
- -fno-spec-constr
-
... but you may also need some or all of
- -fexpose-all-unfoldings
- -fno-ignore-interface-pragmas
- -fno-omit-interface-pragmas
- -fno-worker-wrapper
especially if there are multiple source files (in that case I found it best to put the flags in the cabal file since it was impossible to get them right in several different files, but it was OK (and possibly easier) to put them in pragmas at the top of the file if there was only one source file).
-
Ints. The
Int
type is used extensively. Plutus Core doesn't support this type, so I've converted all of these toInteger
. This has involved re-implementing some functions (see below). -
Floats. Many of the programs use the
Float
andDouble
types (see NOFIB.md). Plutus Core doesn't support these types at all, so I've ignored all of these programs. -
Characters. Quite a few of the programs use the
Char
type quite extensively. Plutus Core currently provides only limited support for characters, so these programs also mostly have had to be ignored (except for theclausify
program, which used characters for variable IDs; I've replaced these withInteger
IDs). Some more support for characters might be useful (see SCP-1163), in particular- Instances of
Eq
andOrd
forChar
. - Conversions to and from
Integer
(ie,chr
andord
). - Perhaps easier access to individual characters within strings.
- Instances of
-
Laziness. A number of the programs use laziness, in particular infinite lists. This has necessitated some rewriting.
-
Operator precedence. Implementations of
==
,<=
and so on are provided forInteger
inPlutusTx.Prelude
. These don't have any precedence declarations, so sometimes they're not parsed as you'd expect. For instance, it was necessary to insert some brackets in guards in function definitions. We should fix this in Prelude.hs: see SCP-1159. -
Literal patterns. The compiler can't handle matches on (at least) literal integers (
f x 1 = ... | f y 3 = ...
), so these have had to be replaced with chains ofif...then...else
expressions. -
Lazy let-bindings. Some let-bindings are evaluated lazily, in particular ones of the form
let (a, b) = f(...)
. The problem with this is thatf
is evaluated when you usea
and again when you useb
. This led to a slowdown by a factor of four in theprimality
program (which has the state for a random number generator threaded through it, requiring several functions to return pairs). This was quite difficult to diagnose, but could be solved by usingcase
to deconstruct the pairs. -
Library functions. The
PlutusTx
prelude exports new definitions for many standard library functions, but not all (for instancetake
,init
,divMod
). Several programs provide their own replacements for these: we should probably collect all of these in one place where they can be shared amongst benchmarks. -
Unexpected error messages. Several of the programs use library functions such as
div
anddivMod
which are not redefined by the PlutusTx prelude. trying to compile these gives the unexpected error messageGHC Core to PLC plugin: Error: Unsupported feature: Use of Haskell Integer equality, possibly via the Haskell Eq typeclass
. It turns out that this is because the functions are inlined and use matches on literal values internally. The program does contain uses ofEq
, but they don't appear in the source. -
Miscellaneous
- Uses of
[a..b]
fail with an "no unfolding" error forGHC.Integer.Type.gtInteger#
while compilingGHC.Enum.enumDeltaToInteger1FB
. - Trying to use unary negation gives
Reference to a name which is not a local, a builtin, or an external INLINABLE function: Variable GHC.Integer.Type.negateInteger
. Use0-n
instead of-n
to fix this. Maybe we need this in the PlutusTx prelude. - The PlutusTx prelude redefines
elem
but notnotElem
, which gets re-exported from the standard prelude. That was a bit confusing.
- Uses of