title: Optimizing numerical array performance in Haskell
body: <p>I'm working on a terrain generation algorithm for a MineCraft-like world. Currently, I'm using simplex noise based on the implementation in the paper <a href="http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf" rel="noreferrer">'Simplex Noise Demystified' [PDF]</a>, since simplex noise is supposed to be faster and to have fewer artifacts than Perlin noise. This looks fairly decent (see image), but so far it's also pretty slow.</p>
  
  <p><img src="https://i.stack.imgur.com/Xte4e.jpg" alt="enter image description here"></p>
  
  <p>Running the noise function 10 times (I need noise with different wavelengths for things like terrain height, temperature, tree location, etc.) with 3 octaves of noise for each block in a chunk (16x16x128 blocks), or about 1 million calls to the noise function in total, takes about 700-800 ms. This is at least an order of magnitude too slow for the purposes of generating terrain with any decent kind of speed, despite the fact that there are no obvious expensive operations in the algorithm (at least to me). Just floor, modulo, some array lookups and basic arithmetic. The algorithm (written in Haskell) is listed below. The SCC comments are for profiling. I've omitted the 2D noise functions, since they work the same way.</p>
  
  <pre><code>g3 :: (Floating a, RealFrac a) =&gt; a
  g3 = 1/6
  
  {-# INLINE int #-}
  int :: (Integral a, Num b) =&gt; a -&gt; b
  int = fromIntegral
  
  grad3 :: (Floating a, RealFrac a) =&gt; V.Vector (a,a,a)
  grad3 = V.fromList $ [(1,1,0),(-1, 1,0),(1,-1, 0),(-1,-1, 0),
                       (1,0,1),(-1, 0,1),(1, 0,-1),(-1, 0,-1),
                       (0,1,1),( 0,-1,1),(0, 1,-1),( 0,-1,-1)]
  
  {-# INLINE dot3 #-}
  dot3 :: Num a =&gt; (a, a, a) -&gt; a -&gt; a -&gt; a -&gt; a
  dot3 (a,b,c) x y z = a * x + b * y + c * z
  
  {-# INLINE fastFloor #-}
  fastFloor :: RealFrac a =&gt; a -&gt; Int
  fastFloor x = truncate (if x &gt; 0 then x else x - 1)
  
  --Generate a random permutation for use in the noise functions
  perm :: Int -&gt; Permutation
  perm seed = V.fromList . concat . replicate 2 . shuffle' [0..255] 256 $ mkStdGen seed
  
  --Generate 3D noise between -0.5 and 0.5
  simplex3D :: (Floating a, RealFrac a) =&gt; Permutation -&gt; a -&gt; a -&gt; a -&gt; a
  simplex3D p x y z = {-# SCC "out" #-} 16 * (n gi0 (x0,y0,z0) + n gi1 xyz1 + n gi2 xyz2 + n gi3 xyz3) where
      (i,j,k) = {-# SCC "ijk" #-} (s x, s y, s z) where s a = fastFloor (a + (x + y + z) / 3)
      (x0,y0,z0) = {-# SCC "x0-z0" #-} (x - int i + t, y - int j + t, z - int k + t) where t = int (i + j + k) * g3
      (i1,j1,k1,i2,j2,k2) = {-# SCC "i1-k2" #-} if x0 &gt;= y0
          then if y0 &gt;= z0 then (1,0,0,1,1,0) else
               if x0 &gt;= z0 then (1,0,0,1,0,1) else (0,0,1,1,0,1)
          else if y0 &lt;  z0 then (0,0,1,0,1,1) else
               if x0 &lt;  z0 then (0,1,0,0,1,1) else (0,1,0,1,1,0)
      xyz1 = {-# SCC "xyz1" #-} (x0 - int i1 +   g3, y0 - int j1 +   g3, z0 - int k1 +   g3)
      xyz2 = {-# SCC "xyz2" #-} (x0 - int i2 + 2*g3, y0 - int j2 + 2*g3, z0 - int k2 + 2*g3)
      xyz3 = {-# SCC "xyz3" #-} (x0 - 1      + 3*g3, y0 - 1      + 3*g3, z0 - 1      + 3*g3)
      (ii,jj,kk) = {-# SCC "iijjkk" #-} (i .&amp;. 255, j .&amp;. 255, k .&amp;. 255)
      gi0 = {-# SCC "gi0" #-} mod (p V.! (ii +      p V.! (jj +      p V.!  kk      ))) 12
      gi1 = {-# SCC "gi1" #-} mod (p V.! (ii + i1 + p V.! (jj + j1 + p V.! (kk + k1)))) 12
      gi2 = {-# SCC "gi2" #-} mod (p V.! (ii + i2 + p V.! (jj + j2 + p V.! (kk + k2)))) 12
      gi3 = {-# SCC "gi3" #-} mod (p V.! (ii + 1  + p V.! (jj + 1  + p V.! (kk + 1 )))) 12
      {-# INLINE n #-}
      n gi (x',y',z') = {-# SCC "n" #-} (\a -&gt; if a &lt; 0 then 0 else
          a*a*a*a*dot3 (grad3 V.! gi) x' y' z') $ 0.6 - x'*x' - y'*y' - z'*z'
  
  harmonic :: (Num a, Fractional a) =&gt; Int -&gt; (a -&gt; a) -&gt; a
  harmonic octaves noise = f octaves / (2 - 1 / int (2 ^ (octaves - 1))) where
      f 0 = 0
      f o = let r = int $ 2 ^ (o - 1) in noise r / r + f (o - 1)
  
  --Generate harmonic 3D noise between -0.5 and 0.5
  harmonicNoise3D :: (RealFrac a, Floating a) =&gt; Permutation -&gt; Int -&gt; a -&gt; a -&gt; a -&gt; a -&gt; a
  harmonicNoise3D p octaves l x y z = harmonic octaves
      (\f -&gt; simplex3D p (x * f / l) (y * f / l) (z * f / l))
  </code></pre>
  
  <p>For profiling, I used the following code,</p>
  
  <pre><code>q _ = let p = perm 0 in
        sum [harmonicNoise3D p 3 l x y z :: Float | l &lt;- [1..10], y &lt;- [0..127], x &lt;- [0..15], z &lt;- [0..15]]
  
  main = do start &lt;- getCurrentTime
            print $ q ()
            end &lt;- getCurrentTime
            print $ diffUTCTime end start
  </code></pre>
  
  <p>which produces the following information:</p>
  
  <pre><code>COST CENTRE                    MODULE               %time %alloc
  
  simplex3D                      Main                  18.8   21.0
  n                              Main                  18.0   19.6
  out                            Main                  10.1    9.2
  harmonicNoise3D                Main                   9.8    4.5
  harmonic                       Main                   6.4    5.8
  int                            Main                   4.0    2.9
  gi3                            Main                   4.0    3.0
  xyz2                           Main                   3.5    5.9
  gi1                            Main                   3.4    3.4
  gi0                            Main                   3.4    2.7
  fastFloor                      Main                   3.2    0.6
  xyz1                           Main                   2.9    5.9
  ijk                            Main                   2.7    3.5
  gi2                            Main                   2.7    3.3
  xyz3                           Main                   2.6    4.1
  iijjkk                         Main                   1.6    2.5
  dot3                           Main                   1.6    0.7
  </code></pre>
  
  <p>To compare, I also ported the algorithm to C#. Performance there was about 3 to 4 times faster, so I imagine I must be doing something wrong. But even then it's not nearly as fast as I would like. So my question is this: can anyone tell me if there are any ways to speed up my implementation and/or the algorithm in general or does anyone know of a different noise algorithm that has better performance characteristics but a similar look?</p>
  
  <p><strong>Update:</strong></p>
  
  <p>After following some of the suggestions offered below, the code now looks as follows:</p>
  
  <pre><code>module Noise ( Permutation, perm
               , noise3D, simplex3D
               ) where
  
  import Data.Bits
  import qualified Data.Vector.Unboxed as UV
  import System.Random
  import System.Random.Shuffle
  
  type Permutation = UV.Vector Int
  
  g3 :: Double
  g3 = 1/6
  
  {-# INLINE int #-}
  int :: Int -&gt; Double
  int = fromIntegral
  
  grad3 :: UV.Vector (Double, Double, Double)
  grad3 = UV.fromList $ [(1,1,0),(-1, 1,0),(1,-1, 0),(-1,-1, 0),
                       (1,0,1),(-1, 0,1),(1, 0,-1),(-1, 0,-1),
                       (0,1,1),( 0,-1,1),(0, 1,-1),( 0,-1,-1)]
  
  {-# INLINE dot3 #-}
  dot3 :: (Double, Double, Double) -&gt; Double -&gt; Double -&gt; Double -&gt; Double
  dot3 (a,b,c) x y z = a * x + b * y + c * z
  
  {-# INLINE fastFloor #-}
  fastFloor :: Double -&gt; Int
  fastFloor x = truncate (if x &gt; 0 then x else x - 1)
  
  --Generate a random permutation for use in the noise functions
  perm :: Int -&gt; Permutation
  perm seed = UV.fromList . concat . replicate 2 . shuffle' [0..255] 256 $ mkStdGen seed
  
  --Generate 3D noise between -0.5 and 0.5
  noise3D :: Permutation -&gt; Double -&gt; Double -&gt; Double -&gt; Double
  noise3D p x y z = 16 * (n gi0 (x0,y0,z0) + n gi1 xyz1 + n gi2 xyz2 + n gi3 xyz3) where
      (i,j,k) = (s x, s y, s z) where s a = fastFloor (a + (x + y + z) / 3)
      (x0,y0,z0) = (x - int i + t, y - int j + t, z - int k + t) where t = int (i + j + k) * g3
      (i1,j1,k1,i2,j2,k2) = if x0 &gt;= y0
          then if y0 &gt;= z0 then (1,0,0,1,1,0) else
               if x0 &gt;= z0 then (1,0,0,1,0,1) else (0,0,1,1,0,1)
          else if y0 &lt;  z0 then (0,0,1,0,1,1) else
               if x0 &lt;  z0 then (0,1,0,0,1,1) else (0,1,0,1,1,0)
      xyz1 = (x0 - int i1 +   g3, y0 - int j1 +   g3, z0 - int k1 +   g3)
      xyz2 = (x0 - int i2 + 2*g3, y0 - int j2 + 2*g3, z0 - int k2 + 2*g3)
      xyz3 = (x0 - 1      + 3*g3, y0 - 1      + 3*g3, z0 - 1      + 3*g3)
      (ii,jj,kk) = (i .&amp;. 255, j .&amp;. 255, k .&amp;. 255)
      gi0 = rem (UV.unsafeIndex p (ii +      UV.unsafeIndex p (jj +      UV.unsafeIndex p  kk      ))) 12
      gi1 = rem (UV.unsafeIndex p (ii + i1 + UV.unsafeIndex p (jj + j1 + UV.unsafeIndex p (kk + k1)))) 12
      gi2 = rem (UV.unsafeIndex p (ii + i2 + UV.unsafeIndex p (jj + j2 + UV.unsafeIndex p (kk + k2)))) 12
      gi3 = rem (UV.unsafeIndex p (ii + 1  + UV.unsafeIndex p (jj + 1  + UV.unsafeIndex p (kk + 1 )))) 12
      {-# INLINE n #-}
      n gi (x',y',z') = (\a -&gt; if a &lt; 0 then 0 else
          a*a*a*a*dot3 (UV.unsafeIndex grad3 gi) x' y' z') $ 0.6 - x'*x' - y'*y' - z'*z'
  
  harmonic :: Int -&gt; (Double -&gt; Double) -&gt; Double
  harmonic octaves noise = f octaves / (2 - 1 / int (2 ^ (octaves - 1))) where
      f 0 = 0
      f o = let r = 2 ^^ (o - 1) in noise r / r + f (o - 1)
  
  --3D simplex noise
  --syntax: simplex3D permutation number_of_octaves wavelength x y z
  simplex3D :: Permutation -&gt; Int -&gt; Double -&gt; Double -&gt; Double -&gt; Double -&gt; Double
  simplex3D p octaves l x y z = harmonic octaves
      (\f -&gt; noise3D p (x * f / l) (y * f / l) (z * f / l))
  </code></pre>
  
  <p>Together with reducing my chunk size to 8x8x128, generating new terrain chunks now occurs at about 10-20 fps, which means moving around is now not nearly as problematic as before. Of course, any other performance improvements are still welcome.</p>