Пролог: проверка, установлен ли бит

Я использую целые числа произвольной точности для представления плотных битовых векторов размером от дюжины до нескольких тысяч.

Мой код часто должен проверять, установлены ли определенные биты (или нет), поэтому я провел несколько микротестов, чтобы увидеть, были ли некоторые варианты значительно быстрее, чем другие:

bench_1(0, _, _) :- !.
bench_1(N, V, P) :- V /\ (1 << P) =\= 0, N0 is N-1, bench_1(N0, V, P).

bench_2(0, _, _) :- !.
bench_2(N, V, P) :- (V >> P) /\ 1 =:= 1, N0 is N-1, bench_2(N0, V, P).

bench_3(0, _, _) :- !.
bench_3(N, V, P) :- (V >> P) /\ 1 =\= 0, N0 is N-1, bench_3(N0, V, P).

bench_4(0, _, _) :- !.
bench_4(N, V, P) :- (V >> P) /\ 1  >  0, N0 is N-1, bench_4(N0, V, P).

bench_5(0, _, _) :- !.
bench_5(N, V, P) :- 1 is (V >> P) /\  1, N0 is N-1, bench_5(N0, V, P).

И с SWI, и с SICStus все вышеперечисленные варианты (почти) одинаково быстры.

Затем я наткнулся на следующую интересную часть руководства SWI-Prolog:

getbit(+IntExprV, +IntExprI)

Вычисляет значение бита (0 или 1) IntExprI-го бита IntExprV. Оба аргумента должны быть неотрицательными целыми числами. Результат эквивалентен (IntExprV >> IntExprI)/\1, но более эффективен, поскольку исключается материализация смещенного значения.

В будущих версиях (IntExprV >> IntExprI)/\1 будет оптимизироваться для вызова getbit/2, что обеспечит переносимость и производительность.

Итак, я проверил getbit/2:

bench_6(0, _, _) :- !.
bench_6(N, V, P) :- getbit(V,P) =:= 1, N0 is N-1, bench_6(N0, V, P).

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

call_indi_delta(G, What, Delta) :-
   statistics(What, [V0|_]),
   call(G),
   statistics(What, [V1|_]),
   Delta is V1 - V0.

run(Ind, Reps, Expr, Pos) :-
   Position is Pos,
   Value    is Expr,
   member(P_3, [bench_1,bench_2,bench_3,bench_4,bench_5,bench_6]),
   G =.. [P_3,Reps,Value,Position],
   call_indi_delta(G, Ind, T_ms), 
   write(P_3:Reps=T_ms), nl,
   false.

С run(runtime, 10000000, 1<<1000-1, 200) я наблюдал эти времена выполнения:

        | SWI    | SWI -O | SICStus | SICStus |
        | 7.3.23 | 7.3.23 |   4.3.2 |   4.3.3 |
--------+-----------------+-------------------|
bench_1 | 4547ms | 3704ms |   900ms |   780ms |
bench_2 | 4562ms | 3619ms |   970ms |   850ms |
bench_3 | 4541ms | 3603ms |   970ms |   870ms |
bench_4 | 4541ms | 3633ms |   940ms |   890ms |
bench_5 | 4502ms | 3632ms |   950ms |   840ms |
--------+-----------------+-------------------|
bench_6 | 1424ms |  797ms |    n.a. |    n.a. |

Оказывается, что:

  • getbit/2 дал SWI-Prolog ускорение на 500%.

  • Параметр командной строки -O дал SWI-Prolog значительное ускорение.

Есть ли лучшая формулировка (арифметика, веселье и т. Д.) Для получения аналогичного ускорения с помощью SICStus?

Заранее спасибо!


person repeat    schedule 07.07.2016    source источник
comment
SWI-Prolog использует > из GMP (посмотрите вниз). Вы знаете, как SICStus реализует целые числа с множественной точностью?   -  person    schedule 07.07.2016
comment
@ Борис. Я знаю GMP, но не знаю, как SWI интегрирует его. (Я до сих пор теряюсь в источниках SWI.) Спасибо за ссылки! О SICStus: я этого не знаю, но подозреваю, что ffi влечет за собой значительные накладные расходы на вызовы, но я ' м не уверен ... Я попробую интерфейс C, чтобы проверить это.   -  person repeat    schedule 07.07.2016
comment
SWI использует GMP для слишком больших целых чисел. Если вы введете команду getbit с помощью grep в src / *. C, вы можете найти строку кода, которую я связал.   -  person    schedule 07.07.2016
comment
@ Борис. Я понял. Но как он справляется с выделением памяти? GMP позволяет регистрировать пользовательские распределители gmplib.org/manual/Custom-Allocation.html, но с важное предостережение: GMP может использовать выделенные блоки для хранения указателей на другие выделенные блоки. Это ограничит допущения, которые может сделать консервативная схема сборки мусора.   -  person repeat    schedule 07.07.2016


Ответы (1)


Нет, я не думаю, что есть более быстрые формулировки, чем те, которые вы пробовали. В частности, в SICStus нет ничего похожего на getbit/2 (даже не используется внутренне при компиляции арифметики).

PS. Я бы использовал walltime, как правило, для тестирования. Текущие операционные системы не предоставляют очень надежных runtime.

PPS. Я бы добавил тест, который использует фиктивную версию тестируемой последовательности кода, просто чтобы убедиться, что тестируемый код действительно стоит намного больше, чем жгут тестирования. (Я сделал это, и замена битового теста вызовом dummy/3, который ничего не делает, делает его намного быстрее. Так что тест кажется нормальным.)

person Per Mildner    schedule 07.07.2016
comment
Спасибо! Стоит ли мне попробовать использовать ffi? IIRC накладные расходы на вызовы оказались немного выше с JIT в SICStus 4.3.2 (64-разрядная версия), изменилось ли это? - person repeat; 07.07.2016
comment
Я тоже пробовал walltime. Я ожидал увидеть заметные накладные расходы на сборку мусора с большими битовыми векторами (время сборки мусора IIRC является частью walltime). Я был удивлен, что я этого не сделал. - person repeat; 07.07.2016
comment
@repeat Я предполагаю, что fli будет иметь слишком высокие накладные расходы (и доступ к содержимому большого целого числа из C также не бесплатен), но это трудно понять, не пытаясь. - person Per Mildner; 08.07.2016