Home > Documentation > Performance
Logtalk performance

This page contains benchmark results for some Prolog compilers. The main goal of this page it to give you some data for comparing predicate performance in plain Prolog and using Logtalk objects. Benchmark results are provided for both static code and dynamic code.

Benchmark goals

All the tests have been performed using the benchmarks example distributed with Logtalk version 2.42.4 (r6128), using either static binding or dynamic binding but with optional features (including events support) disabled. This provides two relevant scenarios for comparing Logtalk performance with plain Prolog performance. The worst case scenario corresponds to only using dynamic binding with event support enabled. The benchmarks example contains loader files for easily setting up different test scenarios.

Static code test goals

The benchmarks example provides list length and naive list reverse predicates defined in plain Prolog, in a Prolog module, and in a Logtalk object (predicate definitions are the same in all cases). The following goals are used for the first two benchmark tests:

s11: generate_list(30, List), my_length(List, _)
s12: generate_list(30, List), module:mod_length(List, _)
s13: generate_list(30, List), object::length(List, _)
s21: generate_list(30, List), my_nrev(List, _)
s22: generate_list(30, List), module:mod_nrev(List, _)
s23: generate_list(30, List), object::nrev(List, _)

These benchmark tests use a list of 30 elements as an argument to the list predicates. Increasing the list length may lead to decreasing performance differences between plain Prolog and Logtalk as the list length computation time starts to outweigh the overhead of the message sending mechanism. Likewise, decreasing the list length may lead to increasing performance differences between plain Prolog and Logtalk (up to the point you will be closing on the Logtalk message sending mechanism overhead when compared to plain Prolog predicate calls). However, these tests make use of common library predicates where static binding is easily enabled, eliminating the message sending mechanism overheads. The next two examples deal with graph search:

s31: maze_solve(1, 7, _)
s32: module:mod_maze_solve(1, 7, _)
s33: maze::solve(1, 7, _)
s41: graph_path(0, 4, _)
s42: module:mod_graph_path(0, 4, _)
s43: graph::path(0, 4, _)

When static binding is used, the performance of each set of goals is expected to be similar. The performance of Logtalk can be worse due to the overhead of the extra argument added to each compiled object predicate for carrying execution context information. This overhead depends on the Prolog abstract machine and on the optimizations used to pass unchanged arguments between predicate calls.

Category test goals

Category predicates can be called using either the ::/1 or the :/1 control constructs. When using the :/1 control construct, the lookup for both the predicate declaration and the predicate definition begins in this and is restricted to the imported categories. Depending on how the category is compiled, Logtalk may use static binding for :/1 calls, providing the same performance level as calls to local object predicates. The following goals are used for the benchmark tests:

c1: leaf::obj_local
c2: leaf::ctg_direct
c3: leaf::ctg_self

The obj_local method calls a local object predicate; the performance of such calls is equal or close to plain Prolog. The ctg_direct method uses the :/1 control construct to call an imported category predicate. The ctg_self method uses the ::/1 message sending control construct to call an imported category predicate. While the :/1 calls may use static binding, the ::/1 calls always use dynamic binding and a lookup caching mechanism. Note that the choice between either control construct is not simply a question of performance as the control constructs provide different semantics for calling imported category predicates. All three predicates perform the same computation (generating a list of twenty elements and calculating its length) using local predicates.

Dynamic code test goals

Dynamic code tests include both object database updates and creating and abolishing dynamic objects. The benchmarks example provides an object named database, which defines a set of predicates for testing the Logtalk built-in database methods as described below. The following goals are used for the benchmark tests:

d1: create_object(xpto, [], [], []), abolish_object(xpto)
d2: plain_dyndb(_)
d3: database::this_dyndb(_)
d4: database::self_dyndb(_)
d5: database::obj_dyndb(_)

The first test simply creates and abolishes a (dynamic) object. The remaining tests are used for benchmarking object database updates, comparing with plain Prolog database updates. The *_dyndb tests simply assert (using assertz/1) and retract a clause (using retract/1) of a dynamic predicate with arity one. The plain_dyndb(_) test uses the Prolog built-in database predicates. The other three tests use the Logtalk built-in database methods, using a direct method call (this_dyndb(_)), a call using ::/1 (self_dyndb(_)), and a call using ::/2 (obj_dyndb(_)).

Static code benchmark results

Apple MacBook Pro 15.4" Core 2 Duo 2.8GHz, 4GB RAM, MacOS X Intel 10.6.6. All results are given in number of calls per second. By default, the benchmark code repeats each goal up to 100000 times in order to get more accurate results. The last columns show the trade-off between plain Prolog and Logtalk. Dynamic binding is never used in the Prolog module tests.

Static binding (no events support)
Prolog compiler     s11         s12         s13       s13/s11       s21         s22         s23       s23/s21       s31         s32         s33       s33/s31       s41         s42         s43       s43/s41  
B-Prolog 7.4#3 5555556 - 4464286 80.3 % 171674 - 159337 92.8 % 190331 - 163720 86.0 % 71459 - 53996 75.6 %
CxProlog 0.97.5 (devel) 143221 - 187624 131.0 % 36430 - 35185 96.6 % 59700 - 57068 95.6 % 14311 - 13613 95.1 %
ECLiPSe 6.0#168 2500000 1369863 2439024 97.6 % 97847 90744 97182 99.3 % 202429 194553 196078 96.7 % 45290 41511 43975 97.1 %
GNU Prolog 1.4.0 147929 - 138889 93.9 % 21299 - 20951 98.4 % 37175 - 35676 96.0 % 9858 - 9463 96.0 %
Qu-Prolog 8.12 434783 - 400000 92.0 % 25773 - 25253 98.0 % 51282 - 48544 94.7 % 8489 - 8285 97.6 %
SICStus Prolog 4.1.0beta1 1666667 1666667 1666667 100.0 % 142857 140845 138889 97.2 % 208333 208333 192308 92.3 % 65359 64103 54645 83.6 %
SWI-Prolog 5.11.17 (64 bits) 434783 434783 400000 92.0 % 18519 18519 17241 93.1 % 83333 83333 76923 92.3 % 21277 21277 19608 92.2 %
XSB 3.3+ (CVS, 64 bits) 909091 862069 884956 97.3 % 52994 50429 54496 102.8 % 132450 129702 126582 95.6 % 36258 36010 34072 94.0 %
YAP 6.2.0 (git, 64 bits) 2777778 2702703 2564103 92.3 % 131062 130208 128370 97.9 % 352113 355872 325733 92.5 % 75358 72464 71942 95.5 %
Dynamic binding (with predicate lookup caching but no events support)
Prolog compiler     s11         s12         s13       s13/s11       s21         s22         s23       s23/s21       s31         s32         s33       s33/s31       s41         s42         s43       s43/s41  
B-Prolog 7.4#3 5555556 - 1219512 22.0 % 167785 - 144092 85.9 % 190476 - 144092 75.6 % 71839 - 51867 72.2 %
CxProlog 0.97.5 (devel) 145860 - 174633 119.7 % 36255 - 34784 96.0 % 59812 - 55854 93.4 % 14297 - 13579 95.0 %
ECLiPSe 6.0#168 2500000 1369863 847458 33.9 % 98135 92421 86133 87.8 % 206186 195695 159744 77.4 % 45558 40502 43459 95.4 %
GNU Prolog 1.4.0 144928 - 121803 84.0 % 21110 - 20687 98.0 % 36955 - 34602 93.6 % 9863 - 9446 95.8 %
Qu-Prolog 8.12 416667 - 333333 80.0 % 25773 - 24938 96.8 % 50761 - 45872 90.4 % 8460 - 8026 94.9 %
SICStus Prolog 4.1.0beta1 1666667 1666667 1111111 66.7 % 144928 135135 135135 93.2 % 212766 208333 172414 81.0 % 62112 63694 48780 78.5 %
SWI-Prolog 5.11.17 (64 bits) 434783 434783 357143 82.1 % 18519 18519 16949 91.5 % 83333 83333 71429 85.7 % 21277 21277 19608 92.2 %
XSB 3.3+ (CVS, 64 bits) 909091 884956 694444 76.4 % 53107 50531 53533 100.8 % 133511 129366 120192 90.0 % 35971 35249 33058 91.9 %
YAP 6.2.0 (git, 64 bits) 2777778 2631579 2500000 90.0 % 127714 129534 129702 101.6 % 350877 347222 324675 92.5 % 72569 75586 72939 100.5 %
Category benchmark results

All results are given in number of calls per second. By default, the benchmark code repeats each goal up to 100000 times in order to get more accurate results. The last column shows the trade-off between static binding (c2) and dynamic binding (c3) when calling category predicates.

Apple MacBook Pro 15.4" Core 2 Duo 2.8GHz, 4GB RAM, MacOS X Intel 10.6.6
Prolog compiler      c1           c2           c3         c3/c2   
B-Prolog 7.4#3 476190 476190 344828 72.4 %
CxProlog 0.97.5 (devel) 79153 79381 74660 94.1 %
ECLiPSe 6.0#168 578035 571429 403226 70.6 %
GNU Prolog 1.4.0 80972 79554 74460 93.6 %
Qu-Prolog 8.12 128205 126582 116279 91.9 %
SICStus Prolog 4.1.0beta1 769231 714286 555556 77.8 %
SWI-Prolog 5.11.17 (64 bits) 181818 166667 158730 95.2 %
XSB 3.3+ (CVS, 64 bits) 374532 373134 328947 88.2 %
YAP 6.2.0 (git, 64 bits) 952381 952381 892857 93.7 %
Dynamic code benchmark results

All results are given in number of calls per second. By default, the benchmark code repeats each goal up to 100000 times in order to get more accurate results. The last column shows the trade-off between plain Prolog (d2) and Logtalk using static binding (d3).

Apple MacBook Pro 15.4" Core 2 Duo 2.8GHz, 4GB RAM, MacOS X Intel 10.6.6
Prolog compiler      d1           d2           d3           d4           d5         d3/d2   
B-Prolog 7.4#3 4082 1162791 961538 411862 364964 82.7 %
CxProlog 0.97.5 (devel) 976 123570 117534 93810 91586 95.1 %
ECLiPSe 6.0#168 4387 775194 746269 302115 303951 96.3 %
GNU Prolog 1.4.0 5681 28177 30618 28918 28466 108.7 %
Qu-Prolog 8.12 678 48780 46296 40000 39841 94.9 %
SICStus Prolog 4.1.0beta1 3700 454545 434783 312500 303030 95.7 %
SWI-Prolog 5.11.17 (64 bits) 5432 769231 625000 384615 357143 81.2 %
XSB 3.3+ (CVS, 64 bits) 1118 81037 82305 72886 70872 101.6 %
YAP 6.2.0 (git, 64 bits) 4095 452489 438596 350877 341297 96.9 %
Some unsorted comments