Метод Logtalk вызывает оптимизацию производительности

Во время игры с Logtalk, кажется, моя программа дольше выполнялась с объектом Logtalk по сравнению с простым Prolog. Я провел тест, сравнивая выполнение простого предиката в простом Prolog с эквивалентом инкапсуляции объекта logtalk ниже:

%%
% plain prolog predicate
plain_prolog_simple :-
  fail.

%%
% object encapsulation
:- object(logtalk_obj).

    :- public([simple/0]).
    simple :-
      fail.

:- end_object. 

Вот что я получаю:

?- benchmark(plain_prolog_simple).

Number of repetitions: 500000
Total time calls: 0.33799099922180176 seconds
Average time per call: 6.759819984436035e-7 seconds
Number of calls per second: 1479329.3346604244
true.

?- benchmark(logtalk_obj::simple).

Number of repetitions: 500000
Total time calls: 2.950408935546875 seconds
Average time per call: 5.90081787109375e-6 seconds
Number of calls per second: 169468.0333888435
true.

Мы можем увидеть logtalk_obj::simple call медленнее, чем plain_prolog_simple вызов. Я использую SWI Prolog в качестве бэкэнда, я попытался установить некоторые флаги разговора журнала, но безуспешно.

Изменить: мы можем найти примеры кода тестов по https://github.com/koryonik/logtalk-experiments/tree/master/benchmarks

В чем дело? Почему эта производительность отличается? Как оптимизировать вызовы метода Logtalk?

3 ответа

Решение

Короче говоря, вы сравниваете компиляцию Logtalk ::/2 цель на высшем уровне. Это классическая ошибка бенчмаркинга. Цели на верхнем уровне, будь то простые цели Пролога, цели с явно заданными предикатами или цели отправки сообщений, всегда будут интерпретироваться, то есть компилироваться на лету.

Вы получаете производительность, близкую к простому Prolog, для целей отправки сообщений в скомпилированных исходных файлах, что является наиболее распространенным сценарием. Увидеть benchmarks пример в дистрибутиве Logtalk для сравнительного решения, которое позволяет избежать вышеуказанной ловушки.

Разрыв в производительности (между простыми целями Prolog и Logtalk) зависит от выбранного базового компилятора Prolog. Разрыв незначителен для зрелых виртуальных машин Prolog (например, SICStus Prolog или ECLiPSe), когда возможно статическое связывание. Некоторые виртуальные машины Prolog (например, SWI-Prolog) испытывают недостаток в некоторых оптимизациях, которые могут увеличить разрыв, особенно в тесных циклах.

PS Logtalk поставляется с настройками конфигурации для разработки, а не для производительности. Смотрите, в частности, документацию по optimize флаг, который должен быть включен для оптимизации статического связывания.

ОБНОВИТЬ

Начиная с кода в вашем репозитории и предполагая, что SWI-Prolog используется в качестве внутреннего компилятора, попробуйте:

----- code.lgt -----
% plain prolog predicate
plain_prolog_simple :-
  fail.

% object encapsulation
:- object(logtalk_obj).

    :- public(simple/0).
    simple :-
      fail.

:- end_object.
--------------------

----- bench.lgt -----
% load the SWI-Prolog "statistics" library
:- use_module(library(statistics)).   

:- object(bench).

    :- public(bench/0).
    bench :-
        write('Plain Prolog goal:'), nl,
        prolog_statistics:time({plain_prolog_simple}).
    bench :-
        write('Logtalk goal:'), nl,
        prolog_statistics:time(logtalk_obj::simple).
    bench.

:- end_object.
---------------------

Сохраните оба файла и запустите Logtalk:

$ swilgt
...
?- set_logtalk_flag(optimize, on).
true.

?- {code, bench}.
% [ /Users/pmoura/Desktop/bench/code.lgt loaded ]
% (0 warnings)
% [ /Users/pmoura/Desktop/bench/bench.lgt loaded ]
% (0 warnings)
true.

?- bench::bench.
Plain Prolog goal:
% 2 inferences, 0.000 CPU in 0.000 seconds (69% CPU, 125000 Lips)
Logtalk goal:
% 2 inferences, 0.000 CPU in 0.000 seconds (70% CPU, 285714 Lips)
true.

time/1 Предикат является мета-предикатом. Компилятор Logtalk использует свойство мета-предиката для компиляции time/1 аргумент. {}/1 Управляющая конструкция - это обход компилятора Logtalk. Это гарантирует, что его аргумент называется как есть в простой базе данных Пролога.

Уловка бенчмаркинга, которая работает с SWI-Prolog и YAP (возможно, с другими), которые обеспечивают time/1 мета-предикат должен использовать этот предикат с Logtalk's <</2 отладочная управляющая конструкция и logtalk встроенный объект. Использование SWI-Prolog в качестве внутреннего компилятора:

?- set_logtalk_flag(optimize, on).
...
?- time(true).  % ensure the library providing time/1 is loaded
...
?- {code}.
...
?- time(plain_prolog_simple).
% 2 inferences, 0.000 CPU in 0.000 seconds (59% CPU, 153846 Lips)
false.
?- logtalk<<(prolog_statistics:time(logtalk_obj::simple)).
% 2 inferences, 0.000 CPU in 0.000 seconds (47% CPU, 250000 Lips)
false.

Быстрое объяснение, <</2 управляющая конструкция компилирует свой аргумент цели перед вызовом. Как optimize флаг включен и time/1 является мета-предикатом, его аргумент полностью скомпилирован, и для отправки сообщения используется статическая привязка. Отсюда и то же количество выводов, которые мы получаем выше. Таким образом, этот трюк позволяет быстро провести сравнительный анализ на высшем уровне для целей отправки сообщений Logtalk.

Использование YAP аналогично, но проще time/1 является встроенным мета-предикатом вместо библиотечного мета-предиката, как в SWI-Prolog.

Вы также можете сделать интерпретаторы для ориентации объекта, которые довольно быстро. Пролог Йекеджеке имеет чисто интерпретированный оператор (::)/2. На данный момент не так много накладных расходов. Это тестовый код:

Jekejeke Prolog 3, Runtime Library 1.3.0
(c) 1985-2018, XLOG Technologies GmbH, Switzerland

?- [user].

plain :- fail.

:- begin_module(obj).

simple(_) :- fail.

:- end_module.

И это некоторые реальные результаты. Между простым вызовом и вызовом, основанным на операторе (::)/2, нет такой существенной разницы. Под капотом оба поиска предикатов встроены в кеш:

?- time((between(1,500000,_), plain, fail; true)).
% Up 76 ms, GC 0 ms, Thread Cpu 78 ms (Current 06/23/18 23:02:41)
Yes

?- time((between(1,500000,_), obj::simple, fail; true)).
% Up 142 ms, GC 11 ms, Thread Cpu 125 ms (Current 06/23/18 23:02:44)
Yes

У нас еще есть накладные расходы, которые могут быть устранены в будущем. Это должно сделать то, что мы все еще делаем миниатюрное переписывание для каждого вызова (::)/2. Но, может быть, это уходит, мы работаем над этим.

Редактировать 23.06.2018: Теперь у нас есть встроенная версия между /3 и уже реализовано несколько оптимизаций. Приведенные выше рисунки показывают предварительный просмотр этого нового прототипа, который еще не выпущен.

Другие вопросы по тегам