2011-06-18 16 views
11

W rubinowym, jaki jest najskuteczniejszy sposób obliczenia różnicy bitowej między dwiema liczbami całkowitymi bez znaku (np. Odległość Hamminga)?Najbardziej skuteczny sposób obliczania odległości Hamminga w Ruby?

przykład mam całkowitą 2323409845 A = B = 1782647144.

ich reprezentacje binarne:

a = 10001010011111000110101110110101 
b = 01101010010000010000100101101000 

Różnica nieco pomiędzy A & b wynosi 17 ..

I potrafię zrobić logiczny XOR na nich, ale to da mi inną liczbę całkowitą! = 17, musiałbym wtedy iterować przez binarną reprezentację wyniku i porównać # z 1s.

Jaki jest najskuteczniejszy sposób obliczenia różnicy bitowej?

Teraz, czy odpowiedź zmienia się w celu obliczenia różnicy bitów sekwencji wielu int? Na przykład. podano 2 sekwencje liczb całkowitych bez znaku:

x = {2323409845,641760420,509499086....} 
y = {uint,uint,uint...} 

Jaki jest najbardziej efektywny sposób obliczenia różnicy bitów między dwiema sekwencjami?

Czy dokonałbyś iteracji w sekwencji, czy też istnieje szybszy sposób obliczenia różnicy w całej sekwencji naraz?

+0

Dzięki! Właśnie to zrobiłem i wydaje mi się, że jest 3 razy szybsze niż poniższa metoda (używając zoptymalizowanych funkcji ciągów Ruby). – ch3rryc0ke

+0

Spóźniłem się na tę imprezę, ale możesz wziąć [ten benchmark popkonta] (http: // dalkescientific. com/writings/diary/popcnt.cpp) do spinowania. '__builtin_popcount' jest jedną z najwolniejszych metod, jeśli nie [używasz flagi kompilacji] (http://www.dalkescientific.com/writings/diary/archive/2011/11/02/faster_popcount_update.html) – x1a4

Odpowiedz

19

Można skorzystać z funkcji zoptymalizowanych ciąg w Ruby zrobić liczenia bitowej, zamiast czystej arytmetyki. Okazuje się, że jest około 6 razy szybszy przy szybkiej analizie porównawczej.

def h2(a, b) 
    (a^b).to_s(2).count("1") 
end 

h1 jest normalny sposób obliczyć, natomiast h2 konwertuje xor na ciąg znaków, a liczy się liczbę "1" s

Benchmark:

ruby-1.9.2-p180:001:0>> def h1(a, b) 
ruby-1.9.2-p180:002:1*> ret = 0 
ruby-1.9.2-p180:003:1*> xor = a^b 
ruby-1.9.2-p180:004:1*> until xor == 0 
ruby-1.9.2-p180:005:2*> ret += 1 
ruby-1.9.2-p180:006:2*> xor &= xor - 1 
ruby-1.9.2-p180:007:2*> end 
ruby-1.9.2-p180:008:1*> ret 
ruby-1.9.2-p180:009:1*> end 
# => nil 
ruby-1.9.2-p180:010:0>> def h2(a, b) 
ruby-1.9.2-p180:011:1*> (a^b).to_s(2).count("1") 
ruby-1.9.2-p180:012:1*> end 
# => nil 
ruby-1.9.2-p180:013:0>> h1(2323409845, 1782647144) 
# => 17 
ruby-1.9.2-p180:014:0>> h2(2323409845, 1782647144) 
# => 17 
ruby-1.9.2-p180:015:0>> quickbench(10**5) { h1(2323409845, 1782647144) } 
Rehearsal ------------------------------------ 
    2.060000 0.000000 2.060000 ( 1.944690) 
--------------------------- total: 2.060000sec 

     user  system  total  real 
    1.990000 0.000000 1.990000 ( 1.958056) 
# => nil 
ruby-1.9.2-p180:016:0>> quickbench(10**5) { h2(2323409845, 1782647144) } 
Rehearsal ------------------------------------ 
    0.340000 0.000000 0.340000 ( 0.333673) 
--------------------------- total: 0.340000sec 

     user  system  total  real 
    0.320000 0.000000 0.320000 ( 0.326854) 
# => nil 
ruby-1.9.2-p180:017:0>> 
+0

Dzięki za tonę , Zauważyłem, że to też było dużo szybsze. Wykonanie przybliżonych 21K porównań za pomocą wbudowanej funkcji ciągów, jak sugerujesz, trwało około 3 sekund, gdzie w tradycyjny sposób trwało dwa razy dłużej – ch3rryc0ke

3

Algorytm Wegner:

def hamm_dist(a, b) 
    dist = 0 
    val = a^b 

    while not val.zero? 
    dist += 1 
    val &= val - 1 
    end 
    dist 
end 

p hamm_dist(2323409845, 1782647144) # => 17 
5

za sugestię mu jest za krótki, napisałem proste rozszerzenie C, aby użyć __builtin_popcount, i przy użyciu testu porównawczego zweryfikowano, że jest co najmniej 3 razy szybszy niż funkcje zoptymalizowane przez Ruby.

Spojrzałem na Poniższe dwa tutoriale:

W moim programie:

require './FastPopcount/fastpopcount.so' 
include FastPopcount 

def hamming(a,b) 
    popcount(a^b) 
end 

Następnie w reż zawierające mój program, utworzyć folder "PopCount" z następujących akta.

extconf.rb:

# Loads mkmf which is used to make makefiles for Ruby extensions 
require 'mkmf' 

# Give it a name 
extension_name = 'fastpopcount' 

# The destination 
dir_config(extension_name) 

# Do the work 
create_makefile(extension_name) 

popcount.c:

// Include the Ruby headers and goodies 
#include "ruby.h" 

// Defining a space for information and references about the module to be stored internally 
VALUE FastPopcount = Qnil; 

// Prototype for the initialization method - Ruby calls this, not you 
void Init_fastpopcount(); 

// Prototype for our method 'popcount' - methods are prefixed by 'method_' here 
VALUE method_popcount(int argc, VALUE *argv, VALUE self); 

// The initialization method for this module 
void Init_fastpopcount() { 
    FastPopcount = rb_define_module("FastPopcount"); 
    rb_define_method(FastPopcount, "popcount", method_popcount, 1); 
} 

// Our 'popcount' method.. it uses the builtin popcount 
VALUE method_popcount(int argc, VALUE *argv, VALUE self) { 
    return INT2NUM(__builtin_popcount(NUM2UINT(argv))); 
} 

Następnie w biegu katalogu popcount:

rubin extconf.rb zrobić

Następnie uruchom program, a to wszystko .... najszybszym sposobem na odległość Hamminga w rubin.

0

Jeśli zamierzasz podążać ścieżką opartą na literach C, dobrym pomysłem jest dodanie flagi kompilatora -msse4.2 do pliku makefile. Pozwala to kompilatorowi generować instrukcje sprzętowe popcnt zamiast korzystania z tabeli w celu generowania popcount. W moim systemie było to około 2,5x szybciej.

Powiązane problemy