Самый эффективный способ рассчитать расстояние Хемминга в рубине? - PullRequest
12 голосов
/ 18 июня 2011

В ruby, какой самый эффективный способ вычислить битовую разницу между двумя целыми числами без знака (например, расстояние Хэмминга)?

Например, у меня есть целое число a = 2323409845 и b = 1782647144.

Их двоичные представления:

a = 10001010011111000110101110110101
b = 01101010010000010000100101101000

Разница в битах между a & b равна 17 ..

Я могу сделать логическое XOR для них, но это даст мне другое целое число! = 17, тогда мне придется перебирать двоичное представление результата и подсчитывать число из 1.

Какой самый эффективный способ вычисления разницы в битах?

Теперь, изменится ли ответ для вычисления разницы в битах последовательностей многих целых? Например. даны 2 последовательности целых без знака:

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

Каков наиболее эффективный способ вычисления разницы в битах между двумя последовательностями?

Не могли бы вы выполнить итерацию последовательности или есть более быстрый способ рассчитать разницу по всей последовательности сразу?

Ответы [ 4 ]

20 голосов
/ 18 июня 2011

Вы можете использовать оптимизированные функции String в Ruby для подсчета битов вместо чистой арифметики.При быстром бенчмаркинге он оказывается примерно в 6 раз быстрее.

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

h1 - нормальный способ вычисления, в то время как h2 преобразует xor в строку и считает число «1» s

Тест:

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>> 
5 голосов
/ 19 июня 2011

По предложению mu слишком коротко, я написал простое расширение C для использования __builtin_popcount, и с помощью теста производительности проверил, что оно как минимум в 3 раза быстрее, чем оптимизированные строковые функции ruby.

Я посмотрел следующие два урока:

В моей программе:

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

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

Затем в директории, содержащей мою программу, я создаю папку «PopCount» со следующими файлами.

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)));
}

Затем в каталоге popcount запустите:

ruby ​​extconf.rb сделать

Затем запустите программу, и вот она у вас .... самый быстрый способ сделать расстояние Хемминга в ruby.

3 голосов
/ 18 июня 2011

Алгоритм Вегнера:

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 
1 голос
/ 14 августа 2017

Если кто-то собирается следовать пути на основе c, хорошей идеей будет добавить флаг компилятора -msse4.2 в ваш make-файл. Это позволяет компилятору генерировать аппаратные popcnt инструкции вместо использования таблицы для генерации попконта. В моей системе это было примерно в 2,5 раза быстрее.

...