sumbur 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ext/sumbur/extconf.rb +8 -0
- data/ext/sumbur/sumbur.c +68 -0
- data/lib/sumbur/pure_ruby.rb +31 -0
- data/lib/sumbur/version.rb +3 -0
- data/lib/sumbur.rb +13 -0
- data/test/test_sumbur.rb +67 -0
- metadata +56 -0
data/ext/sumbur/sumbur.c
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
3
|
+
#define L 0xFFFFFFFF
|
4
|
+
static VALUE
|
5
|
+
rb_sumbur(VALUE self, VALUE hashed_int, VALUE capacity)
|
6
|
+
{
|
7
|
+
unsigned int h = NUM2UINT(hashed_int);
|
8
|
+
unsigned int capa = NUM2UINT(capacity);
|
9
|
+
unsigned int part, n, i, c;
|
10
|
+
|
11
|
+
if (capacity == 0) {
|
12
|
+
rb_raise(rb_eArgError, "Sumbur is not applicable to empty cluster");
|
13
|
+
}
|
14
|
+
|
15
|
+
part = L / capa;
|
16
|
+
|
17
|
+
if (L - h <= part) return INT2FIX(0);
|
18
|
+
|
19
|
+
n = 1;
|
20
|
+
|
21
|
+
do {
|
22
|
+
if (h >= L / 2) h -= L / 2;
|
23
|
+
else {
|
24
|
+
n = 2;
|
25
|
+
if (L / 2 - h < part) break;
|
26
|
+
}
|
27
|
+
if (capa == 2) break;
|
28
|
+
|
29
|
+
#define curslice(i) (L / (i * (i - 1)))
|
30
|
+
#define unroll(i) \
|
31
|
+
if (curslice(i) <= h) h -= curslice(i); \
|
32
|
+
else { \
|
33
|
+
h += curslice(i) * (i - n - 1); \
|
34
|
+
n = i; \
|
35
|
+
if (L / i - h < part) break; \
|
36
|
+
} \
|
37
|
+
if (capa == i) break
|
38
|
+
|
39
|
+
unroll(3); unroll(4); unroll(5); unroll(6); unroll(7);
|
40
|
+
unroll(8); unroll(9); unroll(10); unroll(11); unroll(12);
|
41
|
+
unroll(13); unroll(14); unroll(15); unroll(16); unroll(17);
|
42
|
+
unroll(18); unroll(19); unroll(20); unroll(21); unroll(22);
|
43
|
+
unroll(23); unroll(24);
|
44
|
+
|
45
|
+
for(i = 25; i <= capa; i++) {
|
46
|
+
c = L / (i * (i - 1));
|
47
|
+
if (c <= h) {
|
48
|
+
h -= c;
|
49
|
+
}
|
50
|
+
else {
|
51
|
+
h += c * (i - n - 1);
|
52
|
+
n = i;
|
53
|
+
if (L / i - h < part) break;
|
54
|
+
}
|
55
|
+
}
|
56
|
+
} while(0);
|
57
|
+
return INT2FIX(n - 1);
|
58
|
+
}
|
59
|
+
|
60
|
+
void
|
61
|
+
Init_native_sumbur()
|
62
|
+
{
|
63
|
+
VALUE mod_sumbur = rb_define_module("Sumbur");
|
64
|
+
VALUE mod_native = rb_define_module_under(mod_sumbur, "Native");
|
65
|
+
|
66
|
+
rb_define_method(mod_native, "sumbur", rb_sumbur, 2);
|
67
|
+
rb_extend_object(mod_native, mod_native);
|
68
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Sumbur
|
2
|
+
module PureRuby
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def sumbur(hashed_integer, cluster_capacity)
|
6
|
+
raise ArgumentError, "Sumbur is not applicable to empty cluster" if cluster_capacity == 0
|
7
|
+
raise ArgumentError, "Sumbur accepts only positive 32bit integers" if hashed_integer < 0 || hashed_integer > 0xFFFFFFFF
|
8
|
+
|
9
|
+
l = 0xFFFFFFFF
|
10
|
+
part = l / cluster_capacity
|
11
|
+
|
12
|
+
return 0 if l - hashed_integer < part
|
13
|
+
|
14
|
+
h = hashed_integer
|
15
|
+
n = 1
|
16
|
+
i = 2
|
17
|
+
while i <= cluster_capacity
|
18
|
+
c = l / (i * (i-1))
|
19
|
+
if c <= h
|
20
|
+
h -= c
|
21
|
+
else
|
22
|
+
h += c * (i-n-1)
|
23
|
+
n = i
|
24
|
+
break if l / n - h < part
|
25
|
+
end
|
26
|
+
i += 1
|
27
|
+
end
|
28
|
+
n - 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/sumbur.rb
ADDED
data/test/test_sumbur.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
|
4
|
+
$spread_cache = {}
|
5
|
+
|
6
|
+
HASHED = (1..1_000_000).map{|i| [i.hash & 0xFFFFFFFF, i]}
|
7
|
+
|
8
|
+
def spread(num, capa, sumbur)
|
9
|
+
start = Time.now
|
10
|
+
v = $spread_cache[ [num, capa, sumbur] ] ||= HASHED[0, num].
|
11
|
+
map{|hash, int| [sumbur.sumbur(hash, capa), int]}.
|
12
|
+
group_by{|serv, int| serv}
|
13
|
+
d = Time.now - start
|
14
|
+
puts format("%s %.4f", [num, capa, sumbur].inspect, d) if d > 0.01
|
15
|
+
v
|
16
|
+
end
|
17
|
+
|
18
|
+
shared_example = proc do
|
19
|
+
it "should spread capacity" do
|
20
|
+
for num in [100_000, 1_000_000]
|
21
|
+
for capa in [2,3,7,8,17,18]
|
22
|
+
spread(num, capa, sumbur).each{|serv, ints|
|
23
|
+
ints.size.must_be_within_epsilon num/capa, 7000.0/num
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should reshard values cleanly" do
|
30
|
+
for num in [100_000, 1_000_000]
|
31
|
+
for capa in [2,3,7,8,17,18]
|
32
|
+
cur = spread(num, capa, sumbur)
|
33
|
+
nxt = spread(num, capa+1, sumbur)
|
34
|
+
moved = 0
|
35
|
+
for i in 0...capa
|
36
|
+
(nxt[i] - cur[i]).must_be_empty
|
37
|
+
moved += mvd = (cur[i] - nxt[i]).size
|
38
|
+
mvd.must_be_within_epsilon (num/capa - num/(capa+1)), 40000.0/num
|
39
|
+
end
|
40
|
+
moved.must_be_within_epsilon num/(capa+1), 7000.0/num
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'sumbur/pure_ruby'
|
47
|
+
describe "Pure Ruby" do
|
48
|
+
let(:sumbur){ Sumbur::PureRuby }
|
49
|
+
class_exec &shared_example
|
50
|
+
end
|
51
|
+
|
52
|
+
begin
|
53
|
+
require 'sumbur/native_sumbur'
|
54
|
+
|
55
|
+
describe "Native" do
|
56
|
+
let(:sumbur) { Sumbur::Native }
|
57
|
+
class_exec &shared_example
|
58
|
+
|
59
|
+
it "should produce same spread as pure ruby version" do
|
60
|
+
for capa in [2,3,4,7,8,9,17,18,19]
|
61
|
+
spread(1_000_000, capa, Sumbur::Native).must_equal spread(1_000_000, capa, Sumbur::PureRuby)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
rescue LoadError
|
66
|
+
puts "Native version is not tested"
|
67
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sumbur
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sokolov Yura 'funny-falcon'
|
9
|
+
- Maksim Kalinchenko
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-08-02 00:00:00.000000000 Z
|
14
|
+
dependencies: []
|
15
|
+
description: Sumbur - consistent spreading
|
16
|
+
email:
|
17
|
+
- funny.falcon@gmail.com
|
18
|
+
- uint32@mail.ru
|
19
|
+
executables: []
|
20
|
+
extensions:
|
21
|
+
- ext/sumbur/extconf.rb
|
22
|
+
extra_rdoc_files: []
|
23
|
+
files:
|
24
|
+
- ext/sumbur/extconf.rb
|
25
|
+
- ext/sumbur/sumbur.c
|
26
|
+
- lib/sumbur/pure_ruby.rb
|
27
|
+
- lib/sumbur/version.rb
|
28
|
+
- lib/sumbur.rb
|
29
|
+
- test/test_sumbur.rb
|
30
|
+
homepage: https://github.com/mailru/sumbur-ruby
|
31
|
+
licenses: []
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
- ext
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ! '>='
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
none: false
|
45
|
+
requirements:
|
46
|
+
- - ! '>='
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '0'
|
49
|
+
requirements: []
|
50
|
+
rubyforge_project:
|
51
|
+
rubygems_version: 1.8.24
|
52
|
+
signing_key:
|
53
|
+
specification_version: 3
|
54
|
+
summary: Sumbur - consistent spreading
|
55
|
+
test_files:
|
56
|
+
- test/test_sumbur.rb
|