Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]
Groups > linux.kernel > #1390708 > unrolled thread
| Started by | "George Spelvin" <linux@horizon.com> |
|---|---|
| First post | 2016-04-29 05:00 +0200 |
| Last post | 2016-04-30 05:10 +0200 |
| Articles | 8 — 2 participants |
Back to article view | Back to linux.kernel
Re: [patch 2/7] lib/hashmod: Add modulo based hash mechanism "George Spelvin" <linux@horizon.com> - 2016-04-29 05:00 +0200
Re: [patch 2/7] lib/hashmod: Add modulo based hash mechanism Linus Torvalds <torvalds@linux-foundation.org> - 2016-04-29 05:20 +0200
Re: [patch 2/7] lib/hashmod: Add modulo based hash mechanism "George Spelvin" <linux@horizon.com> - 2016-04-29 06:20 +0200
Re: [patch 2/7] lib/hashmod: Add modulo based hash mechanism "George Spelvin" <linux@horizon.com> - 2016-04-30 01:40 +0200
Re: [patch 2/7] lib/hashmod: Add modulo based hash mechanism Linus Torvalds <torvalds@linux-foundation.org> - 2016-04-30 02:10 +0200
Re: [patch 2/7] lib/hashmod: Add modulo based hash mechanism "George Spelvin" <linux@horizon.com> - 2016-04-30 02:40 +0200
Re: [patch 2/7] lib/hashmod: Add modulo based hash mechanism Linus Torvalds <torvalds@linux-foundation.org> - 2016-04-30 03:20 +0200
Re: [patch 2/7] lib/hashmod: Add modulo based hash mechanism "George Spelvin" <linux@horizon.com> - 2016-04-30 05:10 +0200
| From | "George Spelvin" <linux@horizon.com> |
|---|---|
| Date | 2016-04-29 05:00 +0200 |
| Subject | Re: [patch 2/7] lib/hashmod: Add modulo based hash mechanism |
| Message-ID | <rt6OR-6U0-1@gated-at.bofh.it> |
Thomas Gleixner wrote: > I'm not a hashing wizard and I completely failed to understand why > hash_long/ptr are so horrible for the various test cases I ran. It's very simple: the constants chosen are bit-sparse, *particularly* in the least significant bits, and only 32/64 bits of the product are kept. Using the high-word of a double-width multiply is even better, but some machines (*cough* SPARCv9 *cough*) don't have hardware support for that. So what you get is: (0x9e370001 * (x << 12)) & 0xffffffff = (0x9e370001 * x & 0xfffff) << 12 = (0x70001 * x & 0xfffff) << 12 *Now* does it make sense? 64 bits is just as bad... 0x9e37fffffffc0001 becomes 0x7fffffffc0001, which is 2^51 - 2^18 + 1. The challenge is the !CONFIG_ARCH_HAS_FAST_MULTIPLIER case, when it has to be done with shifts and adds/subtracts. Now, what's odd is that it's only relevant for 64-bit platforms, and currently only x86 and POWER7+ have it. SPARCv9, MIPS64, ARM64, SH64, PPC64, and IA64 all have it turned off. Is this a bug that should be fixed? In fact, do *any* 64-bit platforms need multiply emulation? How many 32-bit platforms nead a multiplier that's easy for GCC to evaluate via shifts and adds? Generlly, by the time you've got a machine grunty enough to need 64 bits, a multiplier is quite affordable. Anyway, assuming there exists at least one platform that needs the shift-and-add sequence, it's quite easy to get a higher hamming weight, you just have to use a few more registers to save some intermediate results. E.g. u64 x = val, t = val, u; x <<= 2; u = x += t; /* val * 5 */ x <<= 4; /* val * 80 */ x -= u; /* val * 75 = 0b1001011 */ Shall I try to come up with something? Footnote: useful web pages on shift-and-add/subtract mutliplciation http://www.vinc17.org/research/mulbyconst/index.en.html http://www.spiral.net/hardware/multless.html
[toc] | [next] | [standalone]
| From | Linus Torvalds <torvalds@linux-foundation.org> |
|---|---|
| Date | 2016-04-29 05:20 +0200 |
| Message-ID | <rt78d-7lC-3@gated-at.bofh.it> |
| In reply to | #1390708 |
On Thu, Apr 28, 2016 at 7:57 PM, George Spelvin <linux@horizon.com> wrote:
>
> How many 32-bit platforms nead a multiplier that's easy for GCC to
> evaluate via shifts and adds?
>
> Generlly, by the time you've got a machine grunty enough to
> need 64 bits, a multiplier is quite affordable.
Probably true.
That said, the whole "use a multiply to do bit shifts and adds" may be
a false economy too. It's a good trick, but it does limit the end
result in many ways: you are limited to (a) only left-shifts and (b)
only addition and subtraction.
The "only left-shifts" means that you will always be in the situation
that you'll then need to use the high bits (so you'll always need that
shift down). And being limited to just the adder tends to mean that
it's harder to get a nice spread of bits - you're basically always
going to have that same carry chain.
Having looked around at other hashes, I suspect we should look at the
ones that do five or six shifts, and a mix of add/sub and xor. And
because they shift the bits around more freely you don't have the
final shift (that ends up being dependent on the size of the target
set).
It really would be lovely to hear that we can just replace
hash_int/long() with a better hash. And I wouldn't get too hung up on
the multiplication trick. I suspect it's not worth it.
Linus
[toc] | [prev] | [next] | [standalone]
| From | "George Spelvin" <linux@horizon.com> |
|---|---|
| Date | 2016-04-29 06:20 +0200 |
| Message-ID | <rt84i-86H-13@gated-at.bofh.it> |
| In reply to | #1390713 |
Linus wrote:
> Having looked around at other hashes, I suspect we should look at the
> ones that do five or six shifts, and a mix of add/sub and xor. And
> because they shift the bits around more freely you don't have the
> final shift (that ends up being dependent on the size of the target
> set).
I'm not sure that final shift is a problem. You need to mask the result
to the desired final size somehow, and a shift is no more cycles than
an AND.
> It really would be lovely to hear that we can just replace
> hash_int/long() with a better hash. And I wouldn't get too hung up on
> the multiplication trick. I suspect it's not worth it.
My main concern is that the scope of the search grows enormously
if we include such things. I don't want to discourage someone
from looking, but I volunteered to find a better multiplication
constant with an efficient add/subtract chain, not start a thesis
project on more general hash functions.
Two places one could look for ideas, though:
http://www.burtleburtle.net/bob/hash/integer.html
https://gist.github.com/badboy/6267743
Here's Thomas Wang's 64-bit hash, which is reputedly quite
good, in case it helps:
uint64_t hash(uint64_t key)
{
key = ~key + (key << 21); // key = (key << 21) - key - 1;
key ^= key >> 24;
key += (key << 3)) + (key << 8); // key *= 265
key ^= key >> 14;
key += (key << 2)) + (key << 4); // key *= 21
key ^= key >> 28;
key += key << 31;
return key;
}
And his slightly shorter 64-to-32-bit function:
unsigned hash(uint64_t key)
{
key = ~key + (key << 18); // key = (key << 18) - key - 1;
key ^= key >> 31;
key *= 21; // key += (key << 2)) + (key << 4);
key ^= key >> 11;
key += key << 6;
key ^= key >> 22;
return (uint32_t)key;
}
Sticking to multiplication, using the heuristics in the
current comments (prime near golden ratio = 9e3779b9 = 2654435769,)
I can come up with this for multiplying by 2654435599 = 0x9e37790f:
// -----------------------------------------------------------------------------
// This code was generated by Spiral Multiplier Block Generator, www.spiral.net
// Copyright (c) 2006, Carnegie Mellon University
// All rights reserved.
// The generated code is distributed under a BSD style license
// (see http://www.opensource.org/licenses/bsd-license.php)
// -----------------------------------------------
// Cost: 6 adds/subtracts 6 shifts 0 negations
// Depth: 5
// Input:
// int t0
// Outputs:
// int t1 = 2654435599 * t0
// -----------------------------------------------
t3 = shl(t0, 11); /* 2048*/
t2 = sub(t3, t0); /* 2047*/
t5 = shl(t2, 8); /* 524032*/
t4 = sub(t5, t2); /* 521985*/
t7 = shl(t0, 25); /* 33554432*/
t6 = add(t4, t7); /* 34076417*/
t9 = shl(t0, 9); /* 512*/
t8 = sub(t9, t0); /* 511*/
t11 = shl(t6, 4); /* 545222672*/
t10 = sub(t11, t6); /* 511146255*/
t12 = shl(t8, 22); /* 2143289344*/
t1 = add(t10, t12); /* 2654435599*/
Which translates into C as
uint32_t multiply(uint32_t x)
{
unsigned y = (x << 11) - x;
y -= y << 8;
y -= x << 25;
x -= x << 9;
y -= y << 4;
y -= x << 22;
return y;
}
Unfortunately, that utility bogs like hell on 64-bit constants.
[toc] | [prev] | [next] | [standalone]
| From | "George Spelvin" <linux@horizon.com> |
|---|---|
| Date | 2016-04-30 01:40 +0200 |
| Message-ID | <rtqaR-6in-5@gated-at.bofh.it> |
| In reply to | #1390713 |
On Fri, Apr 29, 2016 at 9:32 PM, Linus Torvalds
<torvalds@linux-foundation.org> wrote:
wrote:
> For example, that _long_ range of bits set ("7fffffffc" in the middle)
> is effectively just one bit set with a subtraction. And it's *right*
> in that bit area that is supposed to shuffle bits 14-40 to the high bits
> (which is what we actually *use*. So it effectively shuffles none of those
> bits around at all, and if you have a stride of 4096, your'e pretty much
> done for.
Gee, I recall saying something a lot like that.
> 64 bits is just as bad... 0x9e37fffffffc0001 becomes
> 0x7fffffffc0001, which is 2^51 - 2^18 + 1.
After researching it, I think that the "high bits of a multiply" is
in fact a decent way to do such a hash. Interestingly, for a randomly
chosen odd multiplier A, the high k bits of the w-bit product A*x is a
universal hash function in the cryptographic sense. See section 2.3 of
http://arxiv.org/abs/1504.06804
One thing I note is that the advice in the comments to choose a prime
number is misquoting Knuth! Knuth says (vol. 3 section 6.4) the number
should be *relatively* prime to the word size, which for binary computers
simply means odd.
When we have a hardware multiplier, keeping the Hamming weight low is
a waste of time. When we don't, clever organization can do
better than the very naive addition/subtraction chain in the
current hash_64().
To multiply by the 32-bit constant 1640531527 = 0x61c88647 (which is
the negative of the golden ratio, so has identical distribution
properties) can be done in 6 shifts + adds, with a critical path
length of 7 operations (3 shifts + 4 adds).
#define GOLDEN_RATIO_32 0x61c88647 /* phi^2 = 1-phi */
/* Returns x * GOLDEN_RATIO_32 without a hardware multiplier */
unsigned hash_32(unsigned x)
{
unsigned y, z;
/* Path length */
y = (x << 19) + x; /* 1 shift + 1 add */
z = (x << 9) + y; /* 1 shift + 2 add */
x = (x << 23) + z; /* 1 shift + 3 add */
z = (z << 8) + y; /* 2 shift + 3 add */
x = (x << 6) - x; /* 2 shift + 4 add */
return (z << 3) + x; /* 3 shift + 4 add */
}
Finding a similarly efficient chain for the 64-bit golden ratio
0x9E3779B97F4A7C15 = 11400714819323198485
or
0x61C8864680B583EB = 7046029254386353131
is a bit of a challenge, but algorithms are known.
[toc] | [prev] | [next] | [standalone]
| From | Linus Torvalds <torvalds@linux-foundation.org> |
|---|---|
| Date | 2016-04-30 02:10 +0200 |
| Message-ID | <rtqDU-6Oh-1@gated-at.bofh.it> |
| In reply to | #1391527 |
On Fri, Apr 29, 2016 at 4:31 PM, George Spelvin <linux@horizon.com> wrote:
>
> After researching it, I think that the "high bits of a multiply" is
> in fact a decent way to do such a hash.
Our emails crossed. Yes. My test harness actually likes the
multiplication more than most of the specialized "spread out bits"
versions I've found, but only if the constants are better-chosen than
the ones we have now.
> One thing I note is that the advice in the comments to choose a prime
> number is misquoting Knuth! Knuth says (vol. 3 section 6.4) the number
> should be *relatively* prime to the word size, which for binary computers
> simply means odd.
At least for my tests, even that seems to actually be a total
non-issue. Yes, odd values *might* be better, but as mentioned in my
crossing email, it doesn't actually seem to matter for any case the
kernel cares about, since we tend to want to hash down to 10-20 bits
of data, so the least significant bit (particularly for the 64-bit
case) just doesn't matter all that much.
For the 32-bit case I suspect it's more noticeable, since we might be
using even half or more of the result.
But as mentioned: that least-order bit seems to be a *lot* less
important than the mix of the bits in the middle. Because even if your
input ends up being all zeroes in the low bits (it that worst-case
"page aligned pointers" case that Thomas had), and the hash multiplies
by an even number, you'll still end up just dropping all those zero
bits anyway.
> When we have a hardware multiplier, keeping the Hamming weight low is
> a waste of time. When we don't, clever organization can do
> better than the very naive addition/subtraction chain in the
> current hash_64().
Yeah. gcc will actually do the clever stuff for the 32-bit case, afaik.
Nothing I know of does it for the 64-bit case, which is why our
current hand-written one isn't even the smark one.
> To multiply by the 32-bit constant 1640531527 = 0x61c88647 (which is
> the negative of the golden ratio, so has identical distribution
> properties) can be done in 6 shifts + adds, with a critical path
> length of 7 operations (3 shifts + 4 adds).
So the reason we don't do this for the 32-bit case is exactly that gcc
can already do this.
If you can do the same for the 64-bit case, that might be worth it.
Linus
[toc] | [prev] | [next] | [standalone]
| From | "George Spelvin" <linux@horizon.com> |
|---|---|
| Date | 2016-04-30 02:40 +0200 |
| Message-ID | <rtr6W-76c-9@gated-at.bofh.it> |
| In reply to | #1391534 |
> At least for my tests, even that seems to actually be a total > non-issue. Yes, odd values *might* be better, but as mentioned in my > crossing email, it doesn't actually seem to matter for any case the > kernel cares about, since we tend to want to hash down to 10-20 bits > of data, so the least significant bit (particularly for the 64-bit > case) just doesn't matter all that much. Odd is important. If the multiplier is even, the msbit of the input doesn't affect the hash result at all. x and (x + 0x80000000) hash to the same value, always. That just seems like a crappy hash function. > Yeah. gcc will actually do the clever stuff for the 32-bit case, afaik. It's not as clever as it could be; it just does the same Booth recoding thing, a simple series of shifts with add/subtract. Here's the ARM code that GCC produces (9 instructions, all dependent): mult1: add r3, r0, r0, lsl #1 rsb r3, r0, r3, lsl #5 add r3, r3, r3, lsl #4 rsb r3, r3, r3, lsl #5 add r3, r0, r3, lsl #5 add r3, r0, r3, lsl #1 add r3, r0, r3, lsl #3 add r3, r0, r3, lsl #3 rsb r0, r0, r3, lsl #3 bx lr versus the clever code (6 instructions, #4 and #5 could dual-issue): mult2: add r3, r0, r0, lsl #19 add r2, r3, r0, lsl #9 add r0, r2, r0, lsl #23 add r3, r3, r2, lsl #8 rsb r0, r0, r0, lsl #6 add r0, r0, r3, lsl #3 bx lr
[toc] | [prev] | [next] | [standalone]
| From | Linus Torvalds <torvalds@linux-foundation.org> |
|---|---|
| Date | 2016-04-30 03:20 +0200 |
| Message-ID | <rtrJD-7Fi-1@gated-at.bofh.it> |
| In reply to | #1391543 |
On Fri, Apr 29, 2016 at 5:32 PM, George Spelvin <linux@horizon.com> wrote:
>
> Odd is important. If the multiplier is even, the msbit of the input
> doesn't affect the hash result at all.
Fair enough. My test-set was incomplete.
>> Yeah. gcc will actually do the clever stuff for the 32-bit case, afaik.
>
> It's not as clever as it could be; it just does the same Booth
> recoding thing, a simple series of shifts with add/subtract.
Ahh. I thought gcc did the Bernstein's algorithm thing, which is
exponential in the bit size. That would have explained why it only
does it for 32-bit constants.
Not doing it for 64-bit constants makes no sense if it just uses the
trivial Booth's algorithm version.
So the odd "we don't do it for 64-bit" is apparently just an
oversight, not because gcc does something clever.
Oh well.
Linus
[toc] | [prev] | [next] | [standalone]
| From | "George Spelvin" <linux@horizon.com> |
|---|---|
| Date | 2016-04-30 05:10 +0200 |
| Message-ID | <rtts5-Li-3@gated-at.bofh.it> |
| In reply to | #1391552 |
> Not doing it for 64-bit constants makes no sense if it just uses the
> trivial Booth's algorithm version.
AFAICT, gcc 5 *does* optimize 64-bit multiplies by constants.
Does the belief that it doesn't date back to some really old
version?
There's still a threshold where it just punts to the multiplier.
Some examples, x86-64 (gcc 6.0.1) and aarch64 (gcc 5.3.1).
Note the difference in the multiply-by-12345 routine.
return x*9;
mul9:
leaq (%rdi,%rdi,8), %rax
ret
mul9:
add x0, x0, x0, lsl 3
ret
return x*10;
mul10:
leaq (%rdi,%rdi,4), %rax
addq %rax, %rax
ret
mul10:
lsl x1, x0, 3
add x0, x1, x0, lsl 1
ret
return x*127;
mul127:
movq %rdi, %rax
salq $7, %rax
subq %rdi, %rax
ret
mul127:
lsl x1, x0, 7
sub x0, x1, x0
ret
return x*12345;
mul12345:
imulq $12345, %rdi, %rax
ret
mul12345:
lsl x1, x0, 3
sub x1, x1, x0
lsl x1, x1, 1
sub x1, x1, x0
lsl x1, x1, 3
sub x1, x1, x0
lsl x1, x1, 3
sub x0, x1, x0
lsl x1, x0, 4
sub x0, x1, x0
ret
uint64_t y = (x << 9) - (x << 3) + x;
return x + (x << 14) - (y << 3);
mul12345_manual:
movq %rdi, %rdx
salq $14, %rax
salq $9, %rdx
addq %rdi, %rax
addq %rdi, %rdx
salq $3, %rdi
subq %rdi, %rdx
salq $3, %rdx
subq %rdx, %rax
ret
mul12345_manual:
lsl x2, x0, 9
lsl x1, x0, 14
add x2, x2, x0
add x1, x1, x0
sub x0, x2, x0, lsl 3
sub x0, x1, x0, lsl 3
ret
return x*2654435769:
mul2654435769:
movl $2654435769, %eax
imulq %rdi, %rax
ret
mul2654435769:
mov x1, 31161
movk x1, 0x9e37, lsl 16
mul x0, x0, x1
ret
The problem with variant code paths like mul12345_manual is that the
infrastructure required to determine which to use is many times larger
than the code itself. :-(
[toc] | [prev] | [standalone]
Back to top | Article view | linux.kernel
csiph-web