Skip to content

Commit 701804f

Browse files
committed
post: Multiplicative Hashing Functions -- Notes on Primes, Golden Ratio, and Evil
1 parent 44408e5 commit 701804f

36 files changed

Lines changed: 11830 additions & 129 deletions

404.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,8 @@ <h2>Latest</h2>
576576

577577
<li><a href="/post/">Posts</a></li>
578578

579+
<li><a href="/post/multiplicative-hash/">Multiplicative Hashing Functions -- Notes on Primes, Golden Ratio, and Evil</a></li>
580+
579581
<li><a href="/post/perfect-distribution/">Perfect Distribution: GCD in Disguise</a></li>
580582

581583
<li><a href="/post/binomial-modulo-prime/">Binomial Coefficients Modulo a Prime: Fermat&#39;s Theorem and the Non-Adjacent Selection Problem</a></li>
@@ -590,8 +592,6 @@ <h2>Latest</h2>
590592

591593
<li><a href="/authors/admin/">Slava Chernoy</a></li>
592594

593-
<li><a href="/authors/">Authors</a></li>
594-
595595
</ul>
596596

597597

content/post/multiplicative-hash.md

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ math = true
44
highlight = true
55
tags = ["multiplicative hashing", "hash functions", "prime", "goldan ratio", "gcd", "coprimes", "prime"]
66
title = "Multiplicative Hashing Functions -- Notes on Primes, Golden Ratio, and Evil"
7-
draft = true
7+
draft = false
88

99
# Optional featured image (relative to `static/img/` folder).
1010
[header]
@@ -15,14 +15,12 @@ caption = ""
1515

1616
## Introduction
1717

18-
Distribution of partitions amongst slices has a huge impact on the performance of XIV systems.
19-
Different solutions for this problem were discussed and implemented in Gen2 and Gen3.
20-
These approaches are not perfect; in fact, any solution for this problem has some drawbacks.
21-
Our goal is to find a "good" solution having not too much impact on customers and having acceptable cost in development and QA and have acceptable performance (as less collisions as possible).
22-
Due to great importance of this problem, we would like present a new approach not yet discussed.
18+
Mapping partitions (or keys) to slices (or buckets) in a distributed or sharded system has a large impact on performance.
19+
Different hash-based solutions for this problem exist; each has drawbacks.
20+
The goal is to choose a hash function that is simple to implement and gives acceptable performance with as few collisions as possible.
2321

2422
The problem is defined as follows. Given a logical partition number $P$, compute the corresponding slice number $S = s(P)$.
25-
Where $0 ≤ P < 2^{32}$, $0 ≤ S < M$, and $M$ denotes the table size, e.g. in Gen2, $M = 2^{14}$.
23+
Where $0 ≤ P < 2^{32}$, $0 ≤ S < M$, and $M$ denotes the table size (e.g. $M = 2^{14}$).
2624
In our "binary" world, assumptions that input data (partition numbers) have uniform distribution are not always correct.
2725
Therefore, the hash function $s: P \to S$ must be designed very carefully.
2826
In addition to providing uniform distribution of hash values (slice numbers), it has to add some randomness.
@@ -32,13 +30,15 @@ usually explaining in two paragraphs what the book is saying just in one sentenc
3230

3331
## Basic Ideas
3432

35-
In Gen2 we used the following hash function:
33+
A first approach is the division-remainder method with $M$ a power of 2:
34+
3635
$s(P) = P \bmod M$,
3736

38-
where $M = 2^{14}$ -- the table size or the number of slices. In Gen3 it was proposed to change the number of slices from a power of 2 to a prime number:
37+
where $M = 2^{14}$ is the table size (number of slices). A variant is to take $M$ prime instead:
38+
3939
$s(P) = P \bmod M$,
4040

41-
where $M = 16411$ is a prime number $> 2^{14}$. Such form of hashing is called "Division Remainder Method". The main idea of this Notes is to demonstrate another hashing method called "Multiplicative Method":
41+
where $M = 16411$ is a prime $> 2^{14}$. Both forms are called "Division Remainder Method". These notes focus on another method, the "Multiplicative Method":
4242

4343
$$f(P) = A \cdot P \bmod W$$
4444

@@ -107,7 +107,7 @@ uint32_t slice(uint32_t P) {
107107
}
108108
```
109109
110-
It really works well for any input data (partitions) and allows to use the same number of slices as in Gen2: $2^{14}$. As a read can see, the last function has only two operation, one is multiplication and other is logical shift. On some architectures this function may be faster then the second one finding a modulo!
110+
It works well for arbitrary input data and allows using the same number of slices $M = 2^{14}$. As the reader can see, the multiplicative version uses only a multiplication and a logical shift; on some architectures it can be faster than computing a modulo.
111111
112112
Details for Math Fans
113113
@@ -137,7 +137,11 @@ Actually, this condition on $M$ is too strong.
137137
For satisfying this property, it is sufficient that $M ≠ 2^i$ will hold.
138138
For example, $M = 15 · 12 · 97 = 17460$ (15 modules, 12 disks, 97 is a prime) is also "good".
139139
140-
Since $M$ is prime, it seems that the following pattern is not common: $P = S + M · i$. But actually, primes that are close to a power of 2 are also not good. Knuth recommends to choose such $M$ that the following condition will not hold for any small integers $a$ and $j$: $r^j ≡ ± a \pmod M$. Where $r$ denotes the base of computation. From explanation of Knuth, the meaning of $r$ for our case is not too clear: whether $r=2$, $r=16$, or $r=256$? It seems the answer very depends on the type of input data. By Knuth, if $r=2$, the chosen $M$ is not so good, since $M = 16411 = 2^{14} + 27$, and hence $2^{14} ≡ -27 \pmod M$. For $r=16$, we get that $16^ ≡ -108 \pmod M$. For $r=256$, $16^2 ≡ -108 \pmod M$. Knuth explains that such $M$ may produce a hash code that is a simple composition of key digits (in $r$ base system). Instead of trying to understand this explanation, we will give some intuition. Working with numbers, a programmer usually chooses powers of 2 for sizes of structures and buffers (e.g., $2^{10}$ bytes). Then he defines the format of such data and introduces headers (e.g. the header size = 20 bytes). Hence, the size of data without the header becomes very close to the power of 2 (in our example, $2^{10} - 20 = 1004$). On the other hand, embedding this structure to an outer packet (assume the size of this outer header is 30 bytes) leads to the total size being also close to the power of 2 ($2^{10} + 30 = 1054$). As result, most of numbers in our "binary" world are either powers of 2 or close to them. Therefore, such choice of $M$ increases collisions. In other words, not only powers of 2 are *evil*, but primes closing to them are *evil* too.
140+
Since $M$ is prime, it seems that the following pattern is not common: $P = S + M · i$. But actually, primes that are close to a power of 2 are also not good. Knuth recommends to choose such $M$ that the following condition will not hold for any small integers $a$ and $j$: $r^j ≡ ± a \pmod M$. Where $r$ denotes the base of computation. From explanation of Knuth, the meaning of $r$ for our case is not too clear: whether $r=2$, $r=16$, or $r=256$? It seems the answer very depends on the type of input data.
141+
142+
By Knuth, if $r=2$, the chosen $M$ is not so good, since $M = 16411 = 2^{14} + 27$, and hence $2^{14} ≡ -27 \pmod M$. For $r=16$, we get that $16^4 ≡ -108 \pmod M$. For $r=256$, $256^2 ≡ -108 \pmod M$.
143+
144+
Knuth explains that such $M$ may produce a hash code that is a simple composition of key digits (in $r$ base system). Instead of trying to understand this explanation, we will give some intuition. Working with numbers, a programmer usually chooses powers of 2 for sizes of structures and buffers (e.g., $2^{10}$ bytes). Then he defines the format of such data and introduces headers (e.g. the header size = 20 bytes). Hence, the size of data without the header becomes very close to the power of 2 (in our example, $2^{10} - 20 = 1004$). On the other hand, embedding this structure to an outer packet (assume the size of this outer header is 30 bytes) leads to the total size being also close to the power of 2 ($2^{10} + 30 = 1054$). As result, most of numbers in our "binary" world are either powers of 2 or close to them. Therefore, such choice of $M$ increases collisions. In other words, not only powers of 2 are *evil*, but primes closing to them are *evil* too.
141145
142146
As an example of a "good" prime, let's consider $M = 24571$. It is a bit smaller then the middle of $2^{14}$ and $2^{15}$.
143147
@@ -172,7 +176,7 @@ We show the implementation of $p()$ in C code for the multiplicative hashing onl
172176
const uint32_t M = 2 << 14;
173177
const uint32_t B = 244002641;
174178
175-
uint32$t partition(uint32_t S, uint32_t Id) {
179+
uint32_t partition(uint32_t S, uint32_t Id) {
176180
return (S << 18 + Id) * B;
177181
}
178182
```

index.html

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1077,14 +1077,24 @@ <h2 id="the-problem">The Problem</h2>
10771077
<div class="media-body">
10781078

10791079
<div class="section-subheading article-title mb-0 mt-0">
1080-
<a href="/post/perfect-distribution/" >Perfect Distribution: GCD in Disguise</a>
1080+
<a href="/post/multiplicative-hash/" >Multiplicative Hashing Functions -- Notes on Primes, Golden Ratio, and Evil</a>
10811081
</div>
10821082

10831083

1084-
<a href="/post/perfect-distribution/" class="summary-link">
1084+
<a href="/post/multiplicative-hash/" class="summary-link">
10851085
<div class="article-style">
1086-
<h2 id="perfect-distribution-gcd-in-disguise">Perfect Distribution: GCD in Disguise</h2>
1087-
<p>We discuss an algorithm that distributes $a$ ones among $n$ positions so that the gaps between consecutive ones differ by at most one—a <strong>perfect distribution</strong>. I developed it while working on profiling, stress, and negative testing of a system that needed exactly this kind of uniform spread. I am not aware of prior art; if you know of related work, I would be interested to hear.</p>
1086+
<h2 id="introduction">Introduction</h2>
1087+
<p>Mapping partitions (or keys) to slices (or buckets) in a distributed or sharded system has a large impact on performance.
1088+
Different hash-based solutions for this problem exist; each has drawbacks.
1089+
The goal is to choose a hash function that is simple to implement and gives acceptable performance with as few collisions as possible.</p>
1090+
<p>The problem is defined as follows. Given a logical partition number $P$, compute the corresponding slice number $S = s(P)$.
1091+
Where $0 ≤ P &lt; 2^{32}$, $0 ≤ S &lt; M$, and $M$ denotes the table size (e.g. $M = 2^{14}$).
1092+
In our &ldquo;binary&rdquo; world, assumptions that input data (partition numbers) have uniform distribution are not always correct.
1093+
Therefore, the hash function $s: P \to S$ must be designed very carefully.
1094+
In addition to providing uniform distribution of hash values (slice numbers), it has to add some randomness.
1095+
Luckily, this field is well studied: well-known textbooks of Corman&rsquo;s and Knuth&rsquo;s have a good introduction to this field.
1096+
The last one has more detail explanation; therefore, without hesitation, we make use of Knuth&rsquo;s book (Section 6.4.):
1097+
usually explaining in two paragraphs what the book is saying just in one sentence.</p>
10881098
</div>
10891099
</a>
10901100

@@ -1108,7 +1118,7 @@ <h2 id="perfect-distribution-gcd-in-disguise">Perfect Distribution: GCD in Disgu
11081118

11091119

11101120

1111-
Aug 1, 2017
1121+
Aug 2, 2017
11121122
</span>
11131123

11141124

@@ -1117,7 +1127,7 @@ <h2 id="perfect-distribution-gcd-in-disguise">Perfect Distribution: GCD in Disgu
11171127

11181128
<span class="middot-divider"></span>
11191129
<span class="article-reading-time">
1120-
7 min read
1130+
12 min read
11211131
</span>
11221132

11231133

@@ -1165,15 +1175,14 @@ <h2 id="perfect-distribution-gcd-in-disguise">Perfect Distribution: GCD in Disgu
11651175
<div class="media-body">
11661176

11671177
<div class="section-subheading article-title mb-0 mt-0">
1168-
<a href="/post/binomial-modulo-prime/" >Binomial Coefficients Modulo a Prime: Fermat&#39;s Theorem and the Non-Adjacent Selection Problem</a>
1178+
<a href="/post/perfect-distribution/" >Perfect Distribution: GCD in Disguise</a>
11691179
</div>
11701180

11711181

1172-
<a href="/post/binomial-modulo-prime/" class="summary-link">
1182+
<a href="/post/perfect-distribution/" class="summary-link">
11731183
<div class="article-style">
1174-
<p>In the <a href="/post/efficient-implementation-non-adjacent-selection/">previous post</a>, we implemented the closed form $F_{n,m} = \binom{n-m+1}{m}$ using Python&rsquo;s <code>math.factorial</code>, and with <code>scipy</code> and <code>sympy</code>. Here we cover the common competitive-programming case: computing the answer <strong>modulo a large prime</strong> $M$ (e.g. $M = 10^9+7$).</p>
1175-
<h2 id="why-modulo">Why modulo?</h2>
1176-
<p>In counting problems, the result can be huge even for moderate input. Often the problem asks for the answer modulo a big prime so that it fits in a standard integer type. We could compute the full number and then take the remainder, but that forces expensive long-integer arithmetic. Computing <strong>everything</strong> modulo $M$ from the start is much faster.</p>
1184+
<h2 id="perfect-distribution-gcd-in-disguise">Perfect Distribution: GCD in Disguise</h2>
1185+
<p>We discuss an algorithm that distributes $a$ ones among $n$ positions so that the gaps between consecutive ones differ by at most one—a <strong>perfect distribution</strong>. I developed it while working on profiling, stress, and negative testing of a system that needed exactly this kind of uniform spread. I am not aware of prior art; if you know of related work, I would be interested to hear.</p>
11771186
</div>
11781187
</a>
11791188

@@ -1197,7 +1206,7 @@ <h2 id="why-modulo">Why modulo?</h2>
11971206

11981207

11991208

1200-
Jul 8, 2017
1209+
Aug 1, 2017
12011210
</span>
12021211

12031212

@@ -1206,7 +1215,7 @@ <h2 id="why-modulo">Why modulo?</h2>
12061215

12071216
<span class="middot-divider"></span>
12081217
<span class="article-reading-time">
1209-
2 min read
1218+
7 min read
12101219
</span>
12111220

12121221

@@ -1254,17 +1263,15 @@ <h2 id="why-modulo">Why modulo?</h2>
12541263
<div class="media-body">
12551264

12561265
<div class="section-subheading article-title mb-0 mt-0">
1257-
<a href="/post/efficient-implementation-non-adjacent-selection/" >Efficient Implementation of the Non-Adjacent Selection Formula</a>
1266+
<a href="/post/binomial-modulo-prime/" >Binomial Coefficients Modulo a Prime: Fermat&#39;s Theorem and the Non-Adjacent Selection Problem</a>
12581267
</div>
12591268

12601269

1261-
<a href="/post/efficient-implementation-non-adjacent-selection/" class="summary-link">
1270+
<a href="/post/binomial-modulo-prime/" class="summary-link">
12621271
<div class="article-style">
1263-
<p>In the <a href="/post/two-var-recursive-func/">previous post</a>, we derived the closed form for the non-adjacent selection problem:</p>
1264-
<p>$$ F_{n, m} = {n - m + 1 \choose m} $$</p>
1265-
<p>Now we discuss how to implement this efficiently in Python—from a simple factorial-based solution to library implementations. For the common case of computing the answer <strong>modulo a large prime</strong> (e.g. in competitive programming), see the <a href="/post/binomial-modulo-prime/">next post</a>.</p>
1266-
<h2 id="fast-solutions-based-on-binomials">Fast Solutions Based on Binomials</h2>
1267-
<p>We can reflect the closed form in very trivial Python code:</p>
1272+
<p>In the <a href="/post/efficient-implementation-non-adjacent-selection/">previous post</a>, we implemented the closed form $F_{n,m} = \binom{n-m+1}{m}$ using Python&rsquo;s <code>math.factorial</code>, and with <code>scipy</code> and <code>sympy</code>. Here we cover the common competitive-programming case: computing the answer <strong>modulo a large prime</strong> $M$ (e.g. $M = 10^9+7$).</p>
1273+
<h2 id="why-modulo">Why modulo?</h2>
1274+
<p>In counting problems, the result can be huge even for moderate input. Often the problem asks for the answer modulo a big prime so that it fits in a standard integer type. We could compute the full number and then take the remainder, but that forces expensive long-integer arithmetic. Computing <strong>everything</strong> modulo $M$ from the start is much faster.</p>
12681275
</div>
12691276
</a>
12701277

@@ -1288,7 +1295,7 @@ <h2 id="fast-solutions-based-on-binomials">Fast Solutions Based on Binomials</h2
12881295

12891296

12901297

1291-
Jul 7, 2017
1298+
Jul 8, 2017
12921299
</span>
12931300

12941301

@@ -1297,7 +1304,7 @@ <h2 id="fast-solutions-based-on-binomials">Fast Solutions Based on Binomials</h2
12971304

12981305
<span class="middot-divider"></span>
12991306
<span class="article-reading-time">
1300-
4 min read
1307+
2 min read
13011308
</span>
13021309

13031310

@@ -1345,19 +1352,17 @@ <h2 id="fast-solutions-based-on-binomials">Fast Solutions Based on Binomials</h2
13451352
<div class="media-body">
13461353

13471354
<div class="section-subheading article-title mb-0 mt-0">
1348-
<a href="/post/two-var-recursive-func/" >Cracking Multivariate Recursive Equations Using Generating Functions</a>
1355+
<a href="/post/efficient-implementation-non-adjacent-selection/" >Efficient Implementation of the Non-Adjacent Selection Formula</a>
13491356
</div>
13501357

13511358

1352-
<a href="/post/two-var-recursive-func/" class="summary-link">
1359+
<a href="/post/efficient-implementation-non-adjacent-selection/" class="summary-link">
13531360
<div class="article-style">
1354-
<p>In this post, we return back to the combinatorial problem discussed in <a href="/post/intro-to-dp/">Introduction to Dynamic Programming and Memoization</a> post.
1355-
We will show that generating functions may work great not only for single variable case (see <a href="/post/gen-func-art/">The Art of Generating Functions</a>),
1356-
but also could be very useful for hacking two-variable relations (and of course, in general for multivariate case too).</p>
1357-
<p>For making the post self-contained, we repeat the problem definition here.</p>
1358-
<h2 id="the-problem">The Problem</h2>
1359-
<blockquote>
1360-
<p>Compute the number of ways to choose $m$ elements from $n$ elements such that selected elements in one combination are not adjacent.</p>
1361+
<p>In the <a href="/post/two-var-recursive-func/">previous post</a>, we derived the closed form for the non-adjacent selection problem:</p>
1362+
<p>$$ F_{n, m} = {n - m + 1 \choose m} $$</p>
1363+
<p>Now we discuss how to implement this efficiently in Python—from a simple factorial-based solution to library implementations. For the common case of computing the answer <strong>modulo a large prime</strong> (e.g. in competitive programming), see the <a href="/post/binomial-modulo-prime/">next post</a>.</p>
1364+
<h2 id="fast-solutions-based-on-binomials">Fast Solutions Based on Binomials</h2>
1365+
<p>We can reflect the closed form in very trivial Python code:</p>
13611366
</div>
13621367
</a>
13631368

@@ -1381,7 +1386,7 @@ <h2 id="the-problem">The Problem</h2>
13811386

13821387

13831388

1384-
Jul 6, 2017
1389+
Jul 7, 2017
13851390
</span>
13861391

13871392

@@ -1390,7 +1395,7 @@ <h2 id="the-problem">The Problem</h2>
13901395

13911396
<span class="middot-divider"></span>
13921397
<span class="article-reading-time">
1393-
3 min read
1398+
4 min read
13941399
</span>
13951400

13961401

0 commit comments

Comments
 (0)