├── .gitignore ├── README.md ├── config └── config.exs ├── lib └── xxhash.ex ├── mix.exs └── test ├── test_helper.exs └── xxhash_test.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | temp 6 | 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | elixir-xxhash 2 | ====== 3 | 4 | This is a pure Elixir implementation of [xxHash](https://github.com/Cyan4973/xxHash) 5 | 6 | ## Usage 7 | Add dependency in your mix.exs file: 8 | ``` 9 | def deps do 10 | [{:xxhash, "~> 0.3.1"}] 11 | end 12 | ``` 13 | Once this is done, execute mix deps.get to fetch and compile elixir-xxhash. 14 | 15 | ## Running in iex 16 | Run with iex -S mix 17 | ``` 18 | iex(4)> XXHash.xxh32("") 19 | 0 20 | iex(5)> XXHash.xxh32("0") 21 | 1212501170 22 | iex(6)> XXHash.xxh32("abcd") 23 | 2741253893 24 | iex(7)> XXHash.xxh32("abcde") 25 | 2537091483 26 | iex(8)> XXHash.xxh32("xxhash") == XXHash.xxh32("xxhash") 27 | true 28 | iex(9)> XXHash.xxh32("0123456789abcde") 29 | 498989583 30 | iex(10)> XXHash.xxh32("0123456789abcdef") 31 | 3267648361 32 | iex(11)> XXHash.xxh32("0123456789abcdefg") 33 | 3430527511 34 | ``` 35 | 36 | ## Limitations 37 | * This is still work in progress. 38 | 39 | ## Notes 40 | * You should consider creating a NIF of [xxHash](https://github.com/Cyan4973/xxHash) if you require a high performance version. 41 | 42 | ## License and copyright 43 | * (c) 2015, Mykola Konyk 44 | * Original [xxHash](https://github.com/Cyan4973/xxHash) (c) 2012-2014, Yann Collet 45 | * Distributed under the [MS-RL License.](http://opensource.org/licenses/MS-RL) 46 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | # import_config "#{Mix.env}.exs" 25 | -------------------------------------------------------------------------------- /lib/xxhash.ex: -------------------------------------------------------------------------------- 1 | defmodule XXHash do 2 | @moduledoc """ 3 | Elixir implementation of XXHash. 4 | Includes both 32 bit and 64 bit versions both outlined here: 5 | https://github.com/Cyan4973/xxHash/blob/dev/doc/xxhash_spec.md 6 | """ 7 | use Bitwise 8 | 9 | @prime_32_1 2_654_435_761 10 | @prime_32_2 2_246_822_519 11 | @prime_32_3 3_266_489_917 12 | @prime_32_4 668_265_263 13 | @prime_32_5 374_761_393 14 | 15 | defmodule Int32 do 16 | def add(a, b), do: (a + b) |> mask 17 | def sub(a, b), do: (a - b) |> mask 18 | def mul(a, b), do: (a * b) |> mask 19 | def lshift(a, b), do: a <<< b |> mask 20 | def rshift(a, b), do: a >>> b 21 | def xor(a, b), do: (a ^^^ b) |> mask 22 | def rotl(a, b), do: lshift(a, b) ||| rshift(a, 32 - b) 23 | def rshift_xor(a, b), do: a |> xor(rshift(a, b)) 24 | def read(<>) when <<1::32-little>> != <<1::32-native>>, do: a 25 | def read(<>), do: byteswap(a) 26 | defp mask(a), do: a &&& 0xFFFFFFFF 27 | 28 | defp byteswap(a) do 29 | <> = <> 30 | b 31 | end 32 | end 33 | 34 | @prime_64_1 11_400_714_785_074_694_791 35 | @prime_64_2 14_029_467_366_897_019_727 36 | @prime_64_3 1_609_587_929_392_839_161 37 | @prime_64_4 9_650_029_242_287_828_579 38 | @prime_64_5 2_870_177_450_012_600_261 39 | 40 | defmodule Int64 do 41 | def add(a, b), do: (a + b) |> mask 42 | def sub(a, b), do: (a - b) |> mask 43 | def mul(a, b), do: (a * b) |> mask 44 | def lshift(a, b), do: a <<< b |> mask 45 | def rshift(a, b), do: a >>> b 46 | def xor(a, b), do: (a ^^^ b) |> mask 47 | def rotl(a, b), do: lshift(a, b) ||| rshift(a, 64 - b) 48 | def rshift_xor(a, b), do: a |> xor(rshift(a, b)) 49 | def read(<>) when <<1::64-little>> != <<1::64-native>>, do: a 50 | def read(<>), do: byteswap(a) 51 | def mask(a), do: a &&& 0xFFFFFFFFFFFFFFFF 52 | 53 | defp byteswap(a) do 54 | <> = <> 55 | b 56 | end 57 | end 58 | 59 | @spec xxh32(binary | term, non_neg_integer, non_neg_integer) :: non_neg_integer 60 | def xxh32(input), do: xxh32(input, String.length(input), 0) 61 | 62 | @spec xxh32(binary | term, non_neg_integer) :: non_neg_integer 63 | def xxh32(input, seed), do: xxh32(input, String.length(input), seed) 64 | 65 | # 32 bit empty binary hardcoded hash 66 | @spec xxh32(binary | term, non_neg_integer, non_neg_integer) :: non_neg_integer 67 | def xxh32(<<>>, _length, _seed), do: 46_947_589 68 | 69 | @spec xxh32(binary | term, non_neg_integer, non_neg_integer) :: non_neg_integer 70 | def xxh32(input, length, seed) do 71 | {h32, buffer} = 72 | if length >= 16 do 73 | do_xxh32(0, seed, input) 74 | else 75 | {Int32.add(seed, @prime_32_5), input} 76 | end 77 | 78 | h32 79 | |> Int32.add(length) 80 | |> do_xxh32(seed, buffer) 81 | |> Int32.rshift_xor(15) 82 | |> Int32.mul(@prime_32_2) 83 | |> Int32.rshift_xor(13) 84 | |> Int32.mul(@prime_32_3) 85 | |> Int32.rshift_xor(16) 86 | end 87 | 88 | # Seed accumulators 89 | @spec do_xxh32(non_neg_integer, non_neg_integer, binary | term) :: non_neg_integer 90 | defp do_xxh32(h, seed, <<_a::32, _b::32, _c::32, _d::32, _rest::binary>> = all) do 91 | v1 = Int32.add(seed, @prime_32_1) |> Int32.add(@prime_32_2) 92 | v2 = Int32.add(seed, @prime_32_2) 93 | v3 = Int32.add(seed, 0) 94 | v4 = Int32.sub(seed, @prime_32_1) 95 | do_xxh32(h, seed, all, {v1, v2, v3, v4}) 96 | end 97 | 98 | @spec do_xxh32(non_neg_integer, non_neg_integer, binary | term) :: non_neg_integer 99 | defp do_xxh32(h, _seed, <<>>), do: h 100 | 101 | # Consume remaining input in 32 bit chunks 102 | @spec do_xxh32(non_neg_integer, non_neg_integer, binary | term) :: non_neg_integer 103 | defp do_xxh32(h, seed, <>) do 104 | Int32.read(<>) 105 | |> Int32.mul(@prime_32_3) 106 | |> Int32.add(h) 107 | |> Int32.rotl(17) 108 | |> Int32.mul(@prime_32_4) 109 | |> do_xxh32(seed, rest) 110 | end 111 | 112 | # Consume remaining input in 8 bit chunks 113 | @spec do_xxh32(non_neg_integer, non_neg_integer, binary | term) :: non_neg_integer 114 | defp do_xxh32(h, seed, <>) do 115 | Int32.mul(p, @prime_32_5) 116 | |> Int32.add(h) 117 | |> Int32.rotl(11) 118 | |> Int32.mul(@prime_32_1) 119 | |> do_xxh32(seed, rest) 120 | end 121 | 122 | # Process stripes 123 | @spec do_xxh32(non_neg_integer, non_neg_integer, binary | term, tuple) :: non_neg_integer 124 | defp do_xxh32(h, seed, <>, {v1, v2, v3, v4}) do 125 | do_xxh32( 126 | h, 127 | seed, 128 | rest, 129 | {round32(v1, <>), round32(v2, <>), round32(v3, <>), 130 | round32(v4, <>)} 131 | ) 132 | end 133 | 134 | # Convergence 135 | @spec do_xxh32(non_neg_integer, non_neg_integer, binary | term, tuple) :: non_neg_integer 136 | defp do_xxh32(_h, _seed, rest, {v1, v2, v3, v4}) do 137 | {Int32.rotl(v1, 1) + Int32.rotl(v2, 7) + Int32.rotl(v3, 12) + Int32.rotl(v4, 18), rest} 138 | end 139 | 140 | defp round32(acc_n, lane_n) do 141 | lane_n 142 | |> Int32.read() 143 | |> Int32.mul(@prime_32_2) 144 | |> Int32.add(acc_n) 145 | |> Int32.rotl(13) 146 | |> Int32.mul(@prime_32_1) 147 | end 148 | 149 | ## 64 bit implementation 150 | 151 | @spec xxh64(binary | term, non_neg_integer, non_neg_integer) :: non_neg_integer 152 | def xxh64(input), do: xxh64(input, String.length(input), 0) 153 | 154 | @spec xxh64(binary | term, non_neg_integer) :: non_neg_integer 155 | def xxh64(input, seed), do: xxh64(input, String.length(input), seed) 156 | 157 | # 64 bit empty binary hardcoded hash 158 | @spec xxh64(binary | term, non_neg_integer, non_neg_integer) :: non_neg_integer 159 | def xxh64(<<>>, _length, _seed), do: 17_241_709_254_077_376_921 160 | 161 | @spec xxh64(binary | term, non_neg_integer, non_neg_integer) :: non_neg_integer 162 | def xxh64(input, length, seed) do 163 | {h64, buffer} = 164 | if length >= 32 do 165 | do_xxh64(0, seed, input) 166 | else 167 | {Int64.add(seed, @prime_64_5), input} 168 | end 169 | 170 | h64 171 | |> Int64.add(length) 172 | |> do_xxh64(seed, buffer) 173 | |> Int64.rshift_xor(33) 174 | |> Int64.mul(@prime_64_2) 175 | |> Int64.rshift_xor(29) 176 | |> Int64.mul(@prime_64_3) 177 | |> Int64.rshift_xor(32) 178 | end 179 | 180 | # Seed accumulators 181 | @spec do_xxh64(non_neg_integer, non_neg_integer, binary | term) :: non_neg_integer 182 | defp do_xxh64(h, seed, <<_a::64, _b::64, _c::64, _d::64, _rest::binary>> = all) do 183 | v1 = Int64.add(seed, @prime_64_1) |> Int64.add(@prime_64_2) 184 | v2 = Int64.add(seed, @prime_64_2) 185 | v3 = Int64.add(seed, 0) 186 | v4 = Int64.sub(seed, @prime_64_1) 187 | do_xxh64(h, seed, all, {v1, v2, v3, v4}) 188 | end 189 | 190 | @spec do_xxh64(non_neg_integer, non_neg_integer, binary | term) :: non_neg_integer 191 | defp do_xxh64(h, _seed, <<>>), do: h 192 | 193 | # Consume remaining input in 64 bit chunks 194 | @spec do_xxh64(non_neg_integer, non_neg_integer, binary | term) :: non_neg_integer 195 | defp do_xxh64(h, seed, <>) do 196 | round64(0, Int64.read(<>)) 197 | |> Int64.xor(h) 198 | |> Int64.rotl(27) 199 | |> Int64.mul(@prime_64_1) 200 | |> Int64.add(@prime_64_4) 201 | |> do_xxh64(seed, rest) 202 | end 203 | 204 | # Consume remaining input in 32 bit chunks 205 | 206 | @spec do_xxh64(non_neg_integer, non_neg_integer, binary | term) :: non_neg_integer 207 | defp do_xxh64(h, seed, <>) do 208 | Int32.read(<>) 209 | |> Int64.mul(@prime_64_1) 210 | |> Int64.xor(h) 211 | |> Int64.rotl(23) 212 | |> Int64.mul(@prime_64_2) 213 | |> Int64.add(@prime_64_3) 214 | |> do_xxh64(seed, rest) 215 | end 216 | 217 | # Consume remaining input in 8 bit chunks 218 | @spec do_xxh64(non_neg_integer, non_neg_integer, binary | term) :: non_neg_integer 219 | defp do_xxh64(h, seed, <>) do 220 | p 221 | |> Int64.mul(@prime_64_5) 222 | |> Int64.xor(h) 223 | |> Int64.rotl(11) 224 | |> Int64.mul(@prime_64_1) 225 | |> do_xxh64(seed, rest) 226 | end 227 | 228 | @spec do_xxh64(non_neg_integer, non_neg_integer, binary | term, tuple) :: non_neg_integer 229 | defp do_xxh64(h, seed, <>, {v1, v2, v3, v4}) do 230 | do_xxh64( 231 | h, 232 | seed, 233 | rest, 234 | {round64(v1, Int64.read(<>)), round64(v2, Int64.read(<>)), 235 | round64(v3, Int64.read(<>)), round64(v4, Int64.read(<>))} 236 | ) 237 | end 238 | 239 | @spec do_xxh64(non_neg_integer, non_neg_integer, binary | term, tuple) :: non_neg_integer 240 | defp do_xxh64(_h, _seed, rest, {v1, v2, v3, v4}) do 241 | acc = 242 | (Int64.rotl(v1, 1) + Int64.rotl(v2, 7) + Int64.rotl(v3, 12) + Int64.rotl(v4, 18)) 243 | |> merge64(v1) 244 | |> merge64(v2) 245 | |> merge64(v3) 246 | |> merge64(v4) 247 | 248 | {acc, rest} 249 | end 250 | 251 | defp round64(acc_n, lane_n) do 252 | lane_n 253 | |> Int64.mul(@prime_64_2) 254 | |> Int64.add(acc_n) 255 | |> Int64.rotl(31) 256 | |> Int64.mul(@prime_64_1) 257 | end 258 | 259 | defp merge64(acc, acc_n) do 260 | 0 261 | |> round64(acc_n) 262 | |> Int64.xor(acc) 263 | |> Int64.mul(@prime_64_1) 264 | |> Int64.add(@prime_64_4) 265 | end 266 | end 267 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule XXHash.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :xxhash, 7 | version: "0.3.1", 8 | elixir: "~> 1.0", 9 | description: description(), 10 | package: package(), 11 | deps: deps() 12 | ] 13 | end 14 | 15 | def application do 16 | [applications: []] 17 | end 18 | 19 | defp deps do 20 | [{:ex_doc, ">= 0.0.0", only: :dev}] 21 | end 22 | 23 | defp description do 24 | """ 25 | Native Elixir xxHash port. 26 | """ 27 | end 28 | 29 | def package do 30 | [ 31 | files: ["lib", "mix.exs", "README.md"], 32 | contributors: ["Mykola Konyk", "Derek Kraan", "Christian Green"], 33 | maintainers: ["Mykola Konyk", "Derek Kraan"], 34 | licenses: ["MS-RL"], 35 | links: %{"GitHub" => "https://github.com/ttvd/elixir-xxhash"} 36 | ] 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /test/xxhash_test.exs: -------------------------------------------------------------------------------- 1 | defmodule XXHashTest do 2 | use ExUnit.Case 3 | 4 | describe "32 bit implementation" do 5 | test "with empty string and default seed" do 6 | assert XXHash.xxh32("") == 46_947_589 7 | end 8 | 9 | test "with a string < 16 characters" do 10 | assert XXHash.xxh32("howdy") == 3_656_460_142 11 | end 12 | 13 | test "with a string < 16 characters using 8bit and 32bit buffers" do 14 | assert XXHash.xxh32("howdyhowdy") == 2_955_902_467 15 | end 16 | 17 | test "with a string > 16 characters" do 18 | assert XXHash.xxh32("howdyhowdymoomoohowdyhowdymoomoo") == 3_515_583_491 19 | end 20 | 21 | test "with a seed of '10'" do 22 | assert XXHash.xxh32("howdyhowdymoomoohowdyhowdymoomoo", 10) == 1_124_996_114 23 | end 24 | end 25 | 26 | describe "64 bit implementation" do 27 | test "with empty string and default seed" do 28 | assert XXHash.xxh64("") == 17_241_709_254_077_376_921 29 | end 30 | 31 | test "with a string < 16 characters" do 32 | assert XXHash.xxh64("howdy") == 10_631_137_994_255_926_386 33 | end 34 | 35 | test "with a string < 16 characters using 8bit, 32bit, and 64bit buffers" do 36 | assert XXHash.xxh64("howdyhowdy") == 2_144_175_473_417_988_058 37 | end 38 | 39 | test "with a string > 16 characters" do 40 | assert XXHash.xxh64("howdyhowdymoomoohowdyhowdymoomoo") == 13_228_332_242_145_796_837 41 | end 42 | 43 | test "with a seed of '10'" do 44 | assert XXHash.xxh64("howdyhowdymoomoohowdyhowdymoomoo", 10) == 8_226_117_543_209_784_396 45 | end 46 | end 47 | end 48 | --------------------------------------------------------------------------------