The canonical way to get a perceptual hash is using the pHash library. In fact, we can get nearly the same performance in Haskell using hip and repa.

I ran these benchmarks on a six-core Linux machine using GHC's LLVM backend. Here is the criterion output:

benchmarking fileHash/cat.png
time                 19.50 ms   (20.88 ms .. 21.12 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 20.98 ms   (20.92 ms .. 21.09 ms)
std dev              183.1 μs   (98.64 μs .. 280.9 μs)

benchmarking foreignHash/cat.png
time                 17.44 ms   (17.25 ms .. 17.34 ms)
                     1.000 R²   (1.000 R² .. 1.000 R²)
mean                 17.28 ms   (17.25 ms .. 17.30 ms)
std dev              53.08 μs   (43.56 μs .. 74.98 μs)

foreignHash is a wrapper for ph_dct_imagehash from the pHash library; as we can see, fileHash nearly as fast.

You can find instructions for replicating the benchmark here.