jwagner / simplex-noise.js

A fast simplex noise implementation in Javascript / Typescript.
MIT License
1.61k stars 130 forks source link

upd to even faster floor method #53

Closed foretoo closed 2 years ago

foretoo commented 2 years ago

x | 0 does exactly the same as Math.floor(x) but much faster. Don't know why you got ~10% speedup by combining both approaches, but definitely it would be even faster without calling Math.floor at all.

jwagner commented 2 years ago

@foretoo

x | 0 does exactly the same as Math.floor(x) but much faster.

That's not true. Math.floor(-0.1) is -1. -0.1|0 is 0. In practice using |0 leads to a discontinuity around 0 because of that. That's also why the tests fail. :)

The in simplex noise demystified Stefan Gustavson uses the following

// This method is a *lot* faster than using (int)Math.floor(x)
 private static int fastfloor(double x) {
 return x>0 ? (int)x : (int)x-1;
 }

That mostly works except for the fact that negative integers now also get rounded down so fastfloor(-1) is -2. That shouldn't be a problem for the smoothness of the output but it also changes the results. In the end it also wasn't much faster. By giving up a bit we could do something like ((x + pad)|0) - pad; Where pad = 1<<30. That could be faster since it doesn't require any branching. I might give that a try later.

jwagner commented 2 years ago
➜ /workspaces/simplex-noise.js/perf (main ✗) $  ./benchmark.sh
33932097.10748968
noise2D: 66,808,990 ops/sec ±0%
noise3D: 44,500,140 ops/sec ±0%
noise4D: 32,128,885 ops/sec ±0%

➜ /workspaces/simplex-noise.js/perf (main ✗) $  ./benchmark.sh

34228639.807571135
noise2D: 66,613,371 ops/sec ±0%
noise3D: 44,476,135 ops/sec ±0%
noise4D: 32,093,893 ops/sec ±0%

Gave it a try. First is the weird Math.floor(x)|0, second is ((x + (1 << 30)) | 0) - (1 << 30). There isn't any noticeable performance difference. I don't think there is much to be gained here that would justify a change to the output.

foretoo commented 2 years ago

huh didn't know about that difference 😅 but still, i believe it depends on environment, because when i run

for (let t = 0; t < 5; t++) {
  console.time(`${t}`)
  for (let i = 0; i < 1000000; i++) {
    noise2D(i, 0)
  }
  console.timeEnd(`${t}`)
}

with Math.floor(x)|0, i get ~

0: 87.362ms
1: 83.46ms
2: 76.005ms
3: 75.335ms
4: 77.59ms

and with ((x + 1<<30)|0) - 1<<30, i get ~

0: 73.613ms
1: 47.618ms
2: 42.436ms
3: 42.588ms
4: 43.477ms

Macbook Pro 13" (2020), Node v18.7.0 same difference in Chrome 104.0.5112.79

foretoo commented 2 years ago

oh, i see, i messed up, instead of ((x + (1 << 30)) | 0) - (1 << 30) i used ((x + 1<<30)|0) - 1<<30, well then there is no noticeable difference indeed. =)

jwagner commented 2 years ago

Operator precedence isn't always intuitive. :) I tend to run the tests before I run benchmarks so I catch issues like that.

Even if this one didn't pan out, thanks for looking for an improvement. If you find something else you think could be improved just create another MR. :)