Skip to content

Instantly share code, notes, and snippets.

@TruncatedDinoSour
Created September 25, 2024 19:44
Show Gist options
  • Save TruncatedDinoSour/fd8bd540523f5454fed4d61007250c04 to your computer and use it in GitHub Desktop.
Save TruncatedDinoSour/fd8bd540523f5454fed4d61007250c04 to your computer and use it in GitHub Desktop.
Simple SipHash16, SipHash32, and SipHash64 implementation in C (public domain)
/*
* UNLICENSE
*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*
* Note: I'm saving this mainly for myself as I've implemented this before in
* https://git.ari.lt/ari/vessel and I do NOT want to repeat this
* again :sob:
*/
#include <stdint.h>
#ifndef HMAP_C1
#define HMAP_C1 5
#endif /* HMAP_C1 */
#ifndef HMAP_C2
#define HMAP_C2 7
#endif /* HMAP_C2 */
#ifndef HMAP_VARIANT
#define HMAP_VARIANT 32 /* 32 bits should be enough */
#endif /* HMAP_VARIANT */
/* Begin HMAP_VARIANT helper type */
#if HMAP_VARIANT == 16
/* Use this if you really really really want small memory footprint in expense
* of usability */
#define HMAP_TYPE uint16_t
#define HMAP_TYPE_FMT "%u"
#elif HMAP_VARIANT == 32
/* The sane limit for most applications */
#define HMAP_TYPE uint32_t
#define HMAP_TYPE_FMT "%u"
#elif HMAP_VARIANT == 64
/* If you know you'll be sending big responses - use this */
#define HMAP_TYPE uint64_t
#define HMAP_TYPE_FMT "%lu"
#else
#error "Unsupported HMAP_VARIANT. Supported values are: 16, 32, 64"
#endif /* HMAP_TYPE */
#ifndef HMAP_SIPHASH_C_ROUNDS
#define HMAP_SIPHASH_C_ROUNDS 2
#endif /* HMAP_SIPHASH_C_ROUNDS */
#ifndef HMAP_SIPHASH_D_ROUNDS
#define HMAP_SIPHASH_D_ROUNDS 4
#endif /* HMAP_SIPHASH_D_ROUNDS */
/* Forcefully inlined for optimization purposes */
#define SIPHASH_ROTL(x, b) (x << b) | (x >> (64 - b))
#define SIPHASH_DOUBLE_ROUND(v0, v1, v2, v3) \
do { \
v0 += v1; \
v2 += v3; \
v1 = SIPHASH_ROTL(v1, 13); \
v3 = SIPHASH_ROTL(v3, 16); \
v1 ^= v0; \
v3 ^= v2; \
v0 = SIPHASH_ROTL(v0, 32); \
v2 += v1; \
v0 += v3; \
v1 = SIPHASH_ROTL(v1, 17); \
v3 = SIPHASH_ROTL(v3, 21); \
v1 ^= v2; \
v3 ^= v0; \
v2 = SIPHASH_ROTL(v2, 32); \
} while (0)
#define SIPHASH_READ_LE64(p) \
(uint64_t) p[0] | ((uint64_t)p[1] << 8) | ((uint64_t)p[2] << 16) | \
((uint64_t)p[3] << 24) | ((uint64_t)p[4] << 32) | \
((uint64_t)p[5] << 40) | ((uint64_t)p[6] << 48) | \
((uint64_t)p[7] << 56)
void SipHash(const uint8_t *input,
const uint64_t length,
const uint64_t seeds[2],
HMAP_TYPE *output);
/* Dynamic SipHash implementation */
void SipHash(const uint8_t *input,
const uint64_t length,
const uint64_t seeds[2],
HMAP_TYPE *output) {
uint64_t idx;
uint64_t v0 = 0x736f6d6570736575ULL ^ seeds[0];
uint64_t v1 = 0x646f72616e646f6dULL ^ seeds[1];
uint64_t v2 = 0x6c7967656e657261ULL ^ seeds[0];
uint64_t v3 = 0x7465646279746573ULL ^ seeds[1];
const uint64_t *in_end =
(const uint64_t *)(input + (length & ~(uint64_t)7));
const uint8_t *in_tail = (const uint8_t *)in_end;
uint64_t m;
uint64_t b = ((uint64_t)length) << 56;
while (input != (const uint8_t *)in_end) {
m = SIPHASH_READ_LE64(input);
input += sizeof(m);
v3 ^= m;
for (idx = 0; idx < HMAP_SIPHASH_C_ROUNDS; ++idx)
SIPHASH_DOUBLE_ROUND(v0, v1, v2, v3);
v0 ^= m;
}
for (idx = 0; idx < (length & 7); ++idx)
b |= ((uint64_t)in_tail[idx]) << (8 * idx);
v3 ^= b;
for (idx = 0; idx < HMAP_SIPHASH_C_ROUNDS; ++idx)
SIPHASH_DOUBLE_ROUND(v0, v1, v2, v3);
v0 ^= b;
v2 ^= 0xff;
for (idx = 0; idx < HMAP_SIPHASH_D_ROUNDS; ++idx)
SIPHASH_DOUBLE_ROUND(v0, v1, v2, v3);
#if HMAP_VARIANT == 16
*(uint16_t *)output =
(uint16_t)((v0 ^ v1 ^ v2 ^ v3) ^ ((v0 ^ v1 ^ v2 ^ v3) >> 16));
#elif HMAP_VARIANT == 32
*(uint32_t *)output =
(uint32_t)((v0 ^ v1 ^ v2 ^ v3) ^ ((v0 ^ v1 ^ v2 ^ v3) >> 32));
#elif HMAP_VARIANT == 64
*(uint64_t *)output = (uint64_t)(v0 ^ v1 ^ v2 ^ v3);
#endif /* HMAP_VARIANT */
}
@TruncatedDinoSour
Copy link
Author

Benchmarks:

  • O0: 4.79s/1mil calls
  • O1: 3.69s/1mil calls
  • O2: 3.60s/1mil calls
  • O3: 3.27s/1mil calls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment