diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,8 @@
+2023-08-07 Ivan Perez <ivan.perez@keera.co.uk>
+    * Version bump (0.14.4) (#274).
+    * Introduce benchmark (#167).
+    * Add version bounds to dependencies (#273).
+
 2023-06-07 Ivan Perez <ivan.perez@keera.co.uk>
     * Version bump (0.14.3) (#269).
     * Improve readability of CHANGELOGs (#261).
diff --git a/Yampa.cabal b/Yampa.cabal
--- a/Yampa.cabal
+++ b/Yampa.cabal
@@ -30,7 +30,7 @@
 build-type:    Simple
 
 name:          Yampa
-version:       0.14.3
+version:       0.14.4
 author:        Henrik Nilsson, Antony Courtney
 maintainer:    Ivan Perez (ivan.perez@keera.co.uk)
 homepage:      https://github.com/ivanperez-keera/Yampa/
@@ -315,3 +315,23 @@
   else
     buildable:
       False
+
+benchmark yampa-bench
+  type:
+    exitcode-stdio-1.0
+
+  main-is:
+    Bench.hs
+
+  build-depends:
+       base      < 5
+     , criterion >= 0.5.0.0 && < 1.7
+     , filepath  >= 1.3.0.1 && < 1.5
+     , time      >= 1.4     && < 1.13
+     , Yampa
+
+  default-language:
+    Haskell2010
+
+  hs-source-dirs:
+    benchmarks
diff --git a/benchmarks/Bench.hs b/benchmarks/Bench.hs
new file mode 100644
--- /dev/null
+++ b/benchmarks/Bench.hs
@@ -0,0 +1,155 @@
+-- |
+-- Description : A benchmark for Yampa.
+-- Copyright   : (c) Ivan Perez, 2023
+-- Authors     : Ivan Perez
+--
+-- A benchmark for Yampa.
+module Main where
+
+import Criterion           (bench, bgroup, nf)
+import Criterion.Main      (defaultConfig, defaultMainWith)
+import Criterion.Types     (Config(csvFile, resamples, verbosity)
+                           , Verbosity(Quiet))
+import Data.Time.LocalTime (getZonedTime)
+import Data.Time.Format    (formatTime, defaultTimeLocale)
+import System.Environment  (getArgs, withArgs)
+import System.FilePath     ((</>))
+
+import FRP.Yampa
+
+-- | Run all benchmarks.
+main :: IO ()
+main = do
+  config <- customConfig
+  withArgs [] $
+    defaultMainWith config
+       [ bgroup "basic"
+                [ bench "identity" $ nf basicIdentity 10000
+                , bench "id"       $ nf basicId       10000
+                ]
+       , bgroup "compositions"
+                [ bench "identity" $ nf composeIdentity 10000
+                , bench "idid"     $ nf composeIdId     10000
+                , bench "plus"     $ nf composePlus     10000
+                , bench "plusplus" $ nf composePlusPlus 10000
+                , bench "plusmult" $ nf composePlusMult 10000
+                , bench "mult"     $ nf composeMult     10000
+                , bench "multmult" $ nf composeMultMult 10000
+                ]
+       , bgroup "counter"
+                [ bench "counter1" $ nf counter1 10000
+                , bench "counter2" $ nf counter2 10000
+                ]
+       ]
+
+-- * Benchmarks
+
+-- ** Basic
+
+-- | Yampa's specialized identity function.
+basicIdentity :: Int -> [Int]
+basicIdentity n = embed sf stream
+  where
+    sf     = identity
+    stream = deltaEncode 1.0 (replicate n 1)
+
+-- | Standard function identity lifted to SFs.
+basicId :: Int -> [Int]
+basicId n = embed sf stream
+  where
+    sf     = arr id
+    stream = deltaEncode 1.0 (replicate n 1)
+
+-- ** Compositions
+
+-- | Composition of Yampa's specialized identity function.
+composeIdentity :: Int -> [Int]
+composeIdentity n = embed sf stream
+  where
+    sf     = identity >>> identity
+    stream = deltaEncode 1.0 (replicate n 1)
+
+-- | Composition of standard function identity lifted to SFs.
+composeIdId :: Int -> [Int]
+composeIdId n = embed sf stream
+  where
+    sf     = arr id >>> arr id
+    stream = deltaEncode 1.0 (replicate n 1)
+
+-- | Plus operation.
+--
+-- This is not a composition; it merely exists to serve as a comparison with
+-- composePlusPlus.
+composePlus :: Int -> [Int]
+composePlus n = embed sf stream
+  where
+    sf     = arr (+3)
+    stream = deltaEncode 1.0 $ take n [1..]
+
+-- | Composition of addition lifted to SFs.
+composePlusPlus :: Int -> [Int]
+composePlusPlus n = embed sf stream
+  where
+    sf     = arr (+1) >>> arr (+2)
+    stream = deltaEncode 1.0 $ take n [1..]
+
+-- | Composition of addition with multiplication, lifted to SFs.
+composePlusMult :: Int -> [Int]
+composePlusMult n = embed sf stream
+  where
+    sf     = arr (+100) >>> arr (*2)
+    stream = deltaEncode 1.0 $ take n [10..]
+
+-- | Multiplication operation.
+--
+-- This is not a composition; it merely exists to serve as a comparison with
+-- composeMultMult.
+composeMult :: Int -> [Int]
+composeMult n = embed sf stream
+  where
+    sf     = arr (*20)
+    stream = deltaEncode 1.0 $ take n [10..]
+
+-- | Composition of multiplication lifted to SFs.
+composeMultMult :: Int -> [Int]
+composeMultMult n = embed sf stream
+  where
+    sf     = arr (*10) >>> arr (*2)
+    stream = deltaEncode 1.0 $ take n [10..]
+
+-- ** Counter
+
+-- | Counter without explicit seq.
+counter1 :: Int -> [Int]
+counter1 n = embed sf stream
+  where
+    sf     = loopPre 0 (arr (dup . uncurry (+)))
+    stream = deltaEncode 1.0 (replicate n 1)
+
+-- | Counter with explicit seq.
+counter2 :: Int -> [Int]
+counter2 n = embed sf stream
+  where
+    sf     = loopPre 0 (arr ((\x -> x `seq` (x, x)). uncurry (+)))
+    stream = deltaEncode 1.0 (replicate n 1)
+
+-- * Auxiliary functions
+
+-- Construct a config with increased number of sampling
+-- and a custom name for the report.
+customConfig :: IO Config
+customConfig = do
+  args <- getArgs
+
+  let dir = case args of
+              []     -> "."
+              (x:xs) -> x
+
+  -- Custom filename using the current time
+  timeString <- (formatTime defaultTimeLocale "%F-%H%M%S") <$> getZonedTime
+  let filename = concat [ timeString, "-", "bench.csv" ]
+
+  return $ defaultConfig { csvFile   = Just $ dir </> filename
+                         , resamples = 100000
+                         , verbosity = Quiet
+                         }
