Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

ANN: Dogelog Runtime, Prolog to the Moon (2021)

325 views
Skip to first unread message

Mostowski Collapse

unread,
Aug 15, 2021, 8:43:42 AM8/15/21
to
Yesterday we went into a little programming binge, despite there
was a free parade in Zurich. We could now already implement a transpiler
that targets Python. We simply took the transpiler main.p that targets

JavaScript and moded it into a new transpiler mainpy.p that targets
Python. The code is already on GitHub and we present it also here
as the Python code mainpy.p. We were also busy

on machine.py and special.py. The progress is now:

+------------+ cross +-------------+
| loader.p | compile | loader.py | 100%
| compiler.p | -----------> | compiler.py | 100%
+------------+ +-------------+
| machine.py | 66%
| special.py | 33%
+-------------+

See also:

Python Version of Dogelog Runtime special
https://twitter.com/dogelogch/status/1426884473988292617

Python Version of Dogelog Runtime special
https://www.facebook.com/groups/dogelog

Mostowski Collapse

unread,
Aug 19, 2021, 5:36:54 PM8/19/21
to
Woa! The JavaScript JIT compiler is quite impressive. I now
ported Dogelog runtime to Python as well, so that I can compare
JavaScript and Python, and tested without clause indexing:

between(L,H,L) :- L =< H.
between(L,H,X) :- L < H, Y is L+1, between(Y,H,X).

setup :- between(1,255,N), M is N//2, assertz(edge(M,N)), fail.
setup :- edge(M,N), assertz(edge2(N,M)), fail.
setup.

anc(X,Y) :- edge(X, Y).
anc(X,Y) :- edge(X, Z), anc(Z, Y).

anc2(X,Y) :- edge2(Y, X).
anc2(X,Y) :- edge2(Y, Z), anc2(X, Z).

:- setup.
:- time((between(1,10,_), anc2(0,255), fail; true)).
:- time((between(1,10,_), anc(0,255), fail; true)).

The results are:

/* Python 3.10.0rc1 */
% Wall 188 ms, trim 0 ms
% Wall 5537 ms, trim 0 ms

/* JavaScript Chrome 92.0.4515.159 */
% Wall 5 ms, trim 0 ms
% Wall 147 ms, trim 0 ms

Mostowski Collapse

unread,
Aug 19, 2021, 5:37:21 PM8/19/21
to
Thats a factor 37.8 faster! I tested the a variant of
the Albufeira instructions Prolog VM aka ZIP, which
was also the inspiration for SWI-Prolog.

Open Source:

The Python Version of the Dogelog Runtime
https://github.com/jburse/dogelog-moon/tree/main/devel/runtimepy

The Python Test Harness
https://gist.github.com/jburse/bf6c01c7524f2611d606cb88983da9d6#file-test-py


Mostowski Collapse schrieb:

Julio Di Egidio

unread,
Aug 20, 2021, 6:58:29 AM8/20/21
to
On Sunday, 15 August 2021 at 14:43:42 UTC+2, Mostowski Collapse wrote:

> Yesterday we went into a little programming binge

Who is this "we"?

> See also:
>
> Python Version of Dogelog Runtime special
> https://twitter.com/dogelogch/status/1426884473988292617
>
> Python Version of Dogelog Runtime special
> https://www.facebook.com/groups/dogelog

I haven't tried either but, assuming the code works ;), great stuff man.

You might be the one(s) who save Prolog from oblivion...

Julio

Mostowski Collapse

unread,
Aug 20, 2021, 7:13:48 PM8/20/21
to
We = Me and my cat named socrates
The cat is a very good programmer:

def meow():
print("meow meow, Prolog is not only SWI-Prolog")

Julio Di Egidio schrieb:

Julio Di Egidio

unread,
Aug 21, 2021, 3:00:10 AM8/21/21
to
On Saturday, 21 August 2021 at 01:13:48 UTC+2, Mostowski Collapse wrote:

> We = Me and my cat named socrates
> The cat is a very good programmer:

LOL, certainly better than many...

Have fun,

Julio

Mostowski Collapse

unread,
Aug 23, 2021, 1:53:49 PM8/23/21
to
The world is getting ridiculous. termux seems to
not anymore be supported by Google Play because
of some Android 10 issues?

On the other hand there is this gem:

TI 84 Plus CE Python Edition Unboxing
https://www.youtube.com/watch?v=LVxP_Fki8Fc

LoL

Mostowski Collapse schrieb:

Mostowski Collapse

unread,
Aug 26, 2021, 3:02:00 PM8/26/21
to
Having fun with a new attended query answerer for
the Dogelog runtime. It can be bottled into a single Python
file, no server roundtrip, just ISO core Prolog in one

Python file, requires Python 3.10:

>python.exe toplevel.py
Dogelog Runtime, Prolog to the Moon, 0.9.3
(c) 1985-2021, XLOG Technologies AG, Switzerland
?- X=1; X=2.
X = 1;
X = 2.
?- X= ... ; X = ... .
X = ...;
X = ... .
?-

So we adopted displaying a dot when there are no more
choice points. This is seen in SWI-Prolog for example, but
was also adopted by Scryer Prolog recently.

Note the space between '...' and '.' in the last answer of
the last query. This is needed so that what is shown
is copyable. The query answerer is described here:

Dogelog Runtime attended Prolog query answers. (Jekejeke)
https://twitter.com/dogelogch/status/1430647215928877065

Dogelog Runtime attended Prolog query answers. (Jekejeke)
https://www.facebook.com/groups/dogelog

Mostowski Collapse

unread,
Sep 2, 2021, 12:39:58 PM9/2/21
to
More best kept secrets of Prolog: Pattern Matching

Everybody loves pattern matching. Languages
like Python, release 3.10, even provide it
now. There is now a match/case statement

in Python. But Prolog users will scratch their
head. Will my if-then-else be as fast as a
imperative switch jump table lookup?

Dogelog runtime has stepped up its game
concerning pattern matching. It now provides
ECLiPSe Prolog disjunction and if-then-else

indexing. Take this example:

?- [user].
foo(X,Y) :- X=baz, Y=2; X=bar -> Y=1.

SWI-Prolog leaves a choice point, so no
clause indexing used:

/* SWI-Prolog 8.3.26 */
?- foo(baz,Z).
Z = 2 ; %%% Spurious Choice Point
false.

Dogelog doesn't leave a choice point, since
it can index the disjunction and if-then-else:

/* Dogelog Runtime 0.9.3 */
?- foo(baz,Z).
Z = 2. %%% No Choice Point

See also:

Preview: Dogelog disjunction and if-then-else indexing. (Jekejeke)
https://twitter.com/dogelogch/status/1433446729974796293

Preview: Dogelog disjunction and if-then-else indexing. (Jekejeke)
https://www.facebook.com/groups/dogelog

Mostowski Collapse

unread,
Sep 13, 2021, 8:46:58 AM9/13/21
to
The Standard Python version of Dogelog runtime
is annoyingly slow. So we gave it a try with
andother Python, and it was 6x times faster.

We could test GraalVM. We worked around the missing
match in Python 3.8 by replacing it with if-then-else.
Performance is a little better, we find:

/* Standard Python Version, Warm Run */
?- time(fibo(23,X)).
% Wall 3865 ms, gc 94 ms, 71991 lips
X = 46368.

/* GraalVM Python Version, Warm Warm Run */
?- time(fibo(23,X)).
% Wall 695 ms, gc 14 ms, 400356 lips
X = 46368.

See also:

JDK 1.8 GraalVM Python is 6x faster than Standard Python
https://twitter.com/dogelogch/status/1437395917167112193

JDK 1.8 GraalVM Python is 6x faster than Standard Python
https://www.facebook.com/groups/dogelog

Mostowski Collapse schrieb:

Terry Reedy

unread,
Sep 13, 2021, 5:27:43 PM9/13/21
to
On 9/13/2021 8:46 AM, Mostowski Collapse wrote:
> The Standard Python version of Dogelog runtime
> is annoyingly slow. So we gave it a try with
> andother Python, and it was 6x times faster.
>
> We could test GraalVM. We worked around the missing
> match in Python 3.8 by replacing it with if-then-else.
> Performance is a little better, we find:
>
> /* Standard Python Version, Warm Run */
> ?- time(fibo(23,X)).
> % Wall 3865 ms, gc 94 ms, 71991 lips
> X = 46368.
>
> /* GraalVM Python Version, Warm Warm Run */
> ?- time(fibo(23,X)).
> % Wall 695 ms, gc 14 ms, 400356 lips
> X = 46368.
>
> See also:
>
> JDK 1.8 GraalVM Python is 6x faster than Standard Python
> https://twitter.com/dogelogch/status/1437395917167112193
>
> JDK 1.8 GraalVM Python is 6x faster than Standard Python
> https://www.facebook.com/groups/dogelog

You need to test more than fibonacci to make that claim. There is a
benchmark test that times around 40 different similarly small benchmarks.


--
Terry Jan Reedy

Mostowski Collapse

unread,
Sep 14, 2021, 8:56:35 AM9/14/21
to
I am testing a Prolog interpreter written
in Python. So fibonacci number routine is
written in Prolog and I am running the

fibonnaci number routine inside the
Prolog interpreter that is written in
Python. The factor 6x times faster of

GraalVM can be reproduced also for other
Prolog programs running inside the Prolog
interpreter that is written in Python.

I have a benchmark suite, where I get,
the figures are milliseconds:

Test Standard GraalVM
Total 170'996 28'523

This means the factor is:

170'996 / 28'523 = 5.9950

The test harness, test cases and individual
results for all test cases are found here:

And we could test GraalVM Python, results are from 14.09.2021,
tested with Dogelog Runtime 0.9.5, Python Version:
https://gist.github.com/jburse/f4e774ebb15cac722238b26b1a620f84#gistcomment-3892587

Mostowski Collapse

unread,
Sep 14, 2021, 9:02:16 AM9/14/21
to
But even when using GraalVM Python, the
execution is still slow. Much slower
then the same Prolog interpreter written

in JavaScript. For JavaScript node.exe
I get much better results. I get these
results comparing to a few other new

Prolog systems as well:

Test Dogelog Scryer Trealla
Total 4427 7325 1824

The test harness, test cases and individual
results for all test cases are found here:

Meanwhile could also test Trealla, results are from 12.09.2021,
tested with Dogelog Runtime 0.9.5, JavaScript Version:
https://gist.github.com/jburse/f4e774ebb15cac722238b26b1a620f84#gistcomment-3890013

But this is all only a moment in time.
The Prolog interpreter itself is evolving,
and the programming languages also,

so this is a moving target. Benchmark
results will look different tomorrow.

Mostowski Collapse

unread,
Sep 15, 2021, 9:37:19 AM9/15/21
to
I am not testing this use-case. But a related
use-case might highlight why speed did never
hurt anybody.

Lets say you program a flying drone with Python,
and the measurement is from the drone sensor
and communication systems.

Lets say you are using the idle time between
measurements for some complex planning. It
is then not true that you have anyway

to wait for the measurement.

Hope this helps!

BTW: If somebody knows another Python implementation
I am happy to test this implementation as well.
I am assuming that the standard Python python.exe

I tested amounts to CPython? Not sure. And the
GraalVM is practically the same as JPython? Not
sure either.

> Opinion: Anyone who is counting on Python
> for truly fast compute speed is probably using
> Python for the wrong purpose.
> Here, we use Python to control Test Equipment,
> to set up the equipment and ask for a measurement,
> get it, and proceed to the next measurement; and
> at the end produce a nice formatted report.
> If we wrote the test script in C or Rust or
> whatever it could not run substantially faster
> because it is communicating with the test equipment,
> setting it up and waiting for responses, and
> that is where the vast majority of the time goes.
> Especially if the measurement result requires
> averaging it can take a while. In my opinion
> this is an ideal use for Python, not just because
> the speed of Python is not important, but also
> because we can easily find people who know Python,
> who like coding in Python, and will join the
> company to program in Python ... and stay with us.
>
> --- Joseph S.

Mostowski Collapse

unread,
Sep 15, 2021, 10:02:58 AM9/15/21
to
Oops "speed did never hurt anybody". Don't be
evil, I am talking about unarmed drones.

See also:

Drone Programming With Python Course
https://www.youtube.com/watch?v=LmEcyQnfpDA

Mostowski Collapse schrieb:

Julio Di Egidio

unread,
Sep 15, 2021, 11:51:34 AM9/15/21
to
On Wednesday, 15 September 2021 at 15:37:19 UTC+2, Mostowski Collapse wrote:

> > Opinion: Anyone who is counting on Python
> > for truly fast compute speed is probably using
> > Python for the wrong purpose.

You just don't know anything about this environment: those who need fast computation rather use *libraries* where all the performance critical parts are written in native code... and that's pretty customary in Python.

By that I don't mean Python is flawless, indeed (IMO) it isn't in so many ways: to the point that, for more professional solutions in the maths/statistics realms in particular, people rather use R: yet, the primary reason is not so much performance but really the solidity/structure of the language per se...

Julio

Mostowski Collapse

unread,
Sep 15, 2021, 12:23:30 PM9/15/21
to
I really wonder why my Python implementation
is a factor 40 slower than my JavaScript implementation.
Structurally its the same code.

You can check yourself:

Python Version:
https://github.com/jburse/dogelog-moon/blob/main/devel/runtimepy/machine.py

JavaScript Version:
https://github.com/jburse/dogelog-moon/blob/main/devel/runtime/machine.js

Its the same while, if-then-else, etc.. its the same
classes Variable, Compound etc.. Maybe I could speed
it up by some details. For example to create an array
of length n, I use in Python:

temp = [NotImplemented] * code[pos]
pos += 1

Whereas in JavaScript I use, also
in exec_build2():

temp = new Array(code[pos++]);

So I hear Guido doesn't like ++. So in Python I use +=
and a separate statement as a workaround. But otherwise,
what about the creation of an array,

is the the idiom [_] * _ slow? I am assuming its
compiled away. Or does it really first create an
array of size 1 and then enlarge it?

Chris Angelico

unread,
Sep 15, 2021, 1:27:13 PM9/15/21
to
On Thu, Sep 16, 2021 at 3:17 AM Mostowski Collapse <janb...@fastmail.fm> wrote:
>
> I really wonder why my Python implementation
> is a factor 40 slower than my JavaScript implementation.
> Structurally its the same code.
>

Very hard to know. Your code is detailed and complicated. Do they
produce identical results? Are you using the same sort of
floating-point data everywhere, or is one integer and the other float?
What's going on with all the globals, the continuations, etc? My
suspicion is that you're trying to write weird, wonky Python code, and
then are surprised that it doesn't perform well.

ChrisA

Mostowski Collapse

unread,
Sep 15, 2021, 2:13:36 PM9/15/21
to
If you find a "wonky" spot, I can replace it by "non-wonky"
code. I noticed some differences between Python Dicts
and JavaScript objects. Python tends to throw more exceptions.

So in Python I now do the following:

peek = kb.get(functor, NotImplemented)
if peek is not NotImplemented:

In JavaScript I can directly do:

peek = kb[functor];
if (peek !== undefined)

But if get() in Python is implemented under the hood with
exception handling. i.e. using the exception prone [] and
then in case an exception is thrown, returning the

default value, then Python get() will probably be quite slow.
Since usually exceptions are slow.

alister

unread,
Sep 15, 2021, 2:17:23 PM9/15/21
to
On Wed, 15 Sep 2021 18:23:10 +0200, Mostowski Collapse wrote:

> I really wonder why my Python implementation is a factor 40 slower than
> my JavaScript implementation.
> Structurally its the same code.
>
> You can check yourself:
>
> Python Version:
> https://github.com/jburse/dogelog-moon/blob/main/devel/runtimepy/
machine.py
>
> JavaScript Version:
> https://github.com/jburse/dogelog-moon/blob/main/devel/runtime/
machine.js
>
> Its the same while, if-then-else, etc.. its the same classes Variable,
> Compound etc.. Maybe I could speed it up by some details. For example to
> create an array of length n, I use in Python:
>
> temp = [NotImplemented] * code[pos]
> pos += 1
>
> Whereas in JavaScript I use, also in exec_build2():
>
> temp = new Array(code[pos++]);
>
> So I hear Guido doesn't like ++. So in Python I use +=
> and a separate statement as a workaround. But otherwise,
> what about the creation of an array,
>
> is the the idiom [_] * _ slow? I am assuming its compiled away. Or does
> it really first create an array of size 1 and then enlarge it?
>
> Julio Di Egidio wrote:
<sniped due to top posting>

this is probably a string contender

i = 0
while i < len(term.args) - 1:
mark_term(term.args[i])
i += 1
term = term.args[i]

try replacing with something more pythonic

for index,term in enumerate(term.args):
mark_term(term.args[i])


& possibly go all the way to changing it into a comprehension

there are other similar anti patterns throughout this code.

any time you are manually keeping a counter as an index into a list,tupple
other iterable YOU ARE DOING IT WRONG!

Do not write javascript in python, write python



--
Two percent of zero is almost nothing.




--
Whoever dies with the most toys wins.

Mostowski Collapse

unread,
Sep 15, 2021, 2:22:50 PM9/15/21
to
Do you mean, replace this:

i = 0
while i < len(term.args) - 1:
____mark_term(term.args[i])
____i += 1
term = term.args[i]

By this:

for i,term in enumerate(term.args):
____mark_term(term.args[i])

This wouldn't be correct anymore. The
recursive call is only for the arguments
except for the last one one.

alister

unread,
Sep 15, 2021, 2:22:56 PM9/15/21
to
And this demonstrates how an experienced Python programmer can make an
almost spot on diagnosis without even checking the source code.

@ this stage I would recommend watching some presentations on you tube

this one https://www.youtube.com/watch?v=wf-BqAjZb8M by Raymond Hettinger
is brilliant as it highlights there is more to checking code than just
making sure it looks nice & runs correctly.



--
Lemmings don't grow older, they just die.

Mostowski Collapse

unread,
Sep 15, 2021, 2:27:07 PM9/15/21
to
Well I would be more than happy if an experienced
programmer can fine tune my code. My programming
experience in Python is only 4 weeks.

Mostowski Collapse schrieb am Dienstag, 14. September 2021 um 14:56:35 UTC+2:
> The test harness, test cases and individual
> results for all test cases are found here:
>
> And we could test GraalVM Python, results are from 14.09.2021,
> tested with Dogelog Runtime 0.9.5, Python Version:
> https://gist.github.com/jburse/f4e774ebb15cac722238b26b1a620f84#gistcomment-3892587

If you follow the above link, you also find:

Test Harness
https://gist.github.com/jburse/f4e774ebb15cac722238b26b1a620f84#file-suite2-pl

Test Cases
https://github.com/jburse/jekejeke-samples/tree/master/jekrun_bench/core/tests

CPU / RAM: Intel(R) Core(TM) i7-6700HQ, 32 GB
Standard Python: python.exe Python 3.10.0rc1
GraalVM Python: WSL 2, Python 3.8.5 (GraalVM CE Native 21.2.0)

Mostowski Collapse

unread,
Sep 15, 2021, 2:31:59 PM9/15/21
to
There is a further problem with this:

> for i,term in enumerate(term.args):
> ____mark_term(term.args[i])

It should read:

for i,help in enumerate(term.args):
____mark_term(help)

But then i isn't need.

alister

unread,
Sep 15, 2021, 2:41:12 PM9/15/21
to
On Wed, 15 Sep 2021 11:31:48 -0700, Mostowski Collapse wrote:

> There is a further problem with this:
>
>> for i,term in enumerate(term.args):
>> ____mark_term(term.args[i])
>
> It should read:
>
> for i,help in enumerate(term.args):
> ____mark_term(help)
>
> But then i isn't need.
even Better (i had only skimmed the code as I was certain I would find
this, it is probably the No. 1 thing new python programmers get wrong
if your example is correct the it can be simplified even further to

for help in term.args:
mark_term(help)

& if help does not get used after this loop then a comprehension is even
better
_ == [mark_term(help) for help in term.args]


the underscore character is python convention for an unneeded place-
holder variable.
--
Pie are not square. Pie are round. Cornbread are square.

alister

unread,
Sep 15, 2021, 2:45:57 PM9/15/21
to
On Wed, 15 Sep 2021 18:40:52 +0000, alister wrote:

> On Wed, 15 Sep 2021 11:31:48 -0700, Mostowski Collapse wrote:
>
>> There is a further problem with this:
>>
>>> for i,term in enumerate(term.args):
>>> ____mark_term(term.args[i])
>>
>> It should read:
>>
>> for i,help in enumerate(term.args):
>> ____mark_term(help)
>>
>> But then i isn't need.
> even Better (i had only skimmed the code as I was certain I would find
> this, it is probably the No. 1 thing new python programmers get wrong if
> your example is correct the it can be simplified even further to
>
> for help in term.args:
> mark_term(help)
>
> & if help does not get used after this loop then a comprehension is even
> better _ == [mark_term(help) for help in term.args]
>
>
> the underscore character is python convention for an unneeded place-
> holder variable.
>
>
I also notice you are using match case - this was only introduced in
python 10 so it is very new (i had not seen it before)
the break statements are probably not necessary as if ii understand this
feature correctly python does not fall-through & execute every subsequent
case after a successful match.

& finally the netiquette in this forum is to interleave of bottom post
rather than top post, it makes it easier to follow the thread.





--
Don't quit now, we might just as well lock the door and throw away the
key.

Mostowski Collapse

unread,
Sep 15, 2021, 2:48:31 PM9/15/21
to
And how do you iterate over the first n-1 elements
of a list with n elements? This is what my code does:

i = 0
while i < len(term.args) - 1:
____mark_term(term.args[i])
____i += 1
term = term.args[i]

You can try yourself:

% python3
>>> foo = ["a", "b", "c"]
>>> i = 0
>>> while i < len(foo) - 1:
... print("mark_term", foo[i])
... i += 1
...
mark_term a
mark_term b
>>> foo = foo[i]
>>> foo
'c'

Mostowski Collapse

unread,
Sep 15, 2021, 2:51:59 PM9/15/21
to
There is a Python 3.8 compatible version here:

https://github.com/jburse/dogelog-moon/blob/main/devel/runtimepy/machine2.py

I have replaced match by if-then-else. So as to
be able to test with GraalVM. GraalVM is still faster
despite using if-then-else.

But GraalVM needs some time to JIT the code.
You need to make some cold runs before you
see kicking it being fast.

Mostowski Collapse

unread,
Sep 15, 2021, 2:57:01 PM9/15/21
to
What could be slow, repeatedly requesting the "args"
field. Maybe I should do:

help = term.args
i = 0
while i < len(help) - 1:
____mark_term(help[i])
____i += 1
term = help[i]

alister

unread,
Sep 15, 2021, 3:16:09 PM9/15/21
to
On Wed, 15 Sep 2021 11:48:18 -0700, Mostowski Collapse wrote:

> And how do you iterate over the first n-1 elements of a list with n
> elements? This is what my code does:
>
> i = 0 while i < len(term.args) - 1:
> ____mark_term(term.args[i])
> ____i += 1 term = term.args[i]
>
> You can try yourself:

use a slice
as a generic example you can try in the interactive interpreter (aka
REPL):

>>>items = [1,2,3,4,5,6,7,8,9,0]
>>>for item in items[:-1]:
>>> print(item)

1
2
3
4
5
6
7
8
9

I should state for the record that I am no Python expert, I mainly visit
this news group to learn.
sometimes from reading solutions & somtimes by trying to work out what
the solution might be.

most productively by working out a solution & seeing if other posters
agree (& working out why they don't)



--
The universe seems neither benign nor hostile, merely indifferent.
-- Sagan

Chris Angelico

unread,
Sep 15, 2021, 3:30:10 PM9/15/21
to
On Thu, Sep 16, 2021 at 5:15 AM Mostowski Collapse <burs...@gmail.com> wrote:
>
> If you find a "wonky" spot, I can replace it by "non-wonky"
> code. I noticed some differences between Python Dicts
> and JavaScript objects. Python tends to throw more exceptions.
>
> So in Python I now do the following:
>
> peek = kb.get(functor, NotImplemented)
> if peek is not NotImplemented:
>
> In JavaScript I can directly do:
>
> peek = kb[functor];
> if (peek !== undefined)
>
> But if get() in Python is implemented under the hood with
> exception handling. i.e. using the exception prone [] and
> then in case an exception is thrown, returning the
>
> default value, then Python get() will probably be quite slow.
> Since usually exceptions are slow.
>

No, you're thinking in terms of microoptimizations. Exception handling
isn't THAT slow. I'm talking more about how everything's getting
packaged up and then unpackaged (the repeated use of the "Compound"
class looks highly suboptimal), rather than reworking your algorithm
to behave more cleanly.

ChrisA

Julio Di Egidio

unread,
Sep 15, 2021, 3:36:51 PM9/15/21
to
On Wednesday, 15 September 2021 at 20:27:07 UTC+2, Mostowski Collapse wrote:

> Well I would be more than happy if an experienced
> programmer can fine tune my code.

I wouldn't do it: because the JS code is just as "untuned", and because good old loops are in fact at least as fast as iterator or generator expressions, contrary to popular folklore (I have even posted some study about it: just ignored, to say it charitably). Rather, for one thing, Python object creation *is* particularly slow, and that may not be the only problem: but you'd better get a good profiler to find and compare the critical points between the two languages..

Moreover, IMO, there is not much tuning needed anyway, in fact I find your code quite at the right level, and the fact that the two versions compare almost one-to-one for something of that sort is rather sure sign that the code is... at the right level and meant to be read without a microscope: as it should be. Indeed, I'd rather compliment you for a pretty good benchmark case here: with the caveat, as I say below, that maybe (you not being an expert at Python or JS)) something altogether different could be done, and more idiomatic/efficient, re both languages.
Is there an article or a spec for your "machine"? Need to see the spec to think about possible alternative approaches...

Julio

Julio Di Egidio

unread,
Sep 15, 2021, 3:40:24 PM9/15/21
to
Eh, sorry, I have replied to the wrong post. Of course these are the links I meant to quote:
Julio

alister

unread,
Sep 15, 2021, 4:00:30 PM9/15/21
to
On Wed, 15 Sep 2021 11:56:47 -0700, Mostowski Collapse wrote:

> What could be slow, repeatedly requesting the "args"
> field. Maybe I should do:
>
> help = term.args i = 0 while i < len(help) - 1:
> ____mark_term(help[i])
> ____i += 1 term = help[i]
>
No this construct is a common error in new python programmers
the next progression they make is when they discover the range function
items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
for x in range(len(list)):
print (items[x])

but that is equally inefficient

the pythonic way is as previously suggested

for item in items:
print(item)

then the management of the index is being handled by the python
interpreter internally & is effectively machine code.
every time you increment your own pointer the interpreter has to process
reading the next line, reading the variable , incrementing it & then
using it. this is what makes your current code slow.


if you ever find your self creating a variable purely to use as a pointer
into a list then you are almost certainly taking the wrong approach.

more usefull links
https://www.youtube.com/watch?v=zdJEYhA2AZQ






--
"Show me a good loser, and I'll show you a loser."
-- Vince Lombardi, football coach

Mostowski Collapse

unread,
Sep 15, 2021, 5:10:50 PM9/15/21
to
And how do you only iterate over n-1 elements?
I don't need a loop over all elements.

With array slicing?

Someting like:

for item in items[0:len(items)-2]:
___print(item)

Or with negative slicing indexes? Problem
is my length can be equal to one.

And when I have length equal to one, the
slice might not do the right thing?

LoL

Mostowski Collapse

unread,
Sep 15, 2021, 5:13:12 PM9/15/21
to
Ok you suggested:

>>>items = [1,2,3,4,5,6,7,8,9,0]
>>>for item in items[:-1]:
>>> print(item)

1
2
3
4
5
6
7
8
9

Does this also work for length = 1? Ok let me try:

>>> foo = ["a","b","c"]
>>> for x in foo[:-1]:
... print(x)
...
a
b
>>> foo = ["a"]
>>> for x in foo[:-1]:
... print(x)
...

Oki Doki

DFS

unread,
Sep 15, 2021, 5:15:07 PM9/15/21
to
On 9/15/2021 12:23 PM, Mostowski Collapse wrote:
> I really wonder why my Python implementation
> is a factor 40 slower than my JavaScript implementation.
> Structurally its the same code.
>
> You can check yourself:
>
> Python Version:
> https://github.com/jburse/dogelog-moon/blob/main/devel/runtimepy/machine.py
>
> JavaScript Version:
> https://github.com/jburse/dogelog-moon/blob/main/devel/runtime/machine.js
>
> Its the same while, if-then-else, etc.. its the same
> classes Variable, Compound etc.. Maybe I could speed
> it up by some details. For example to create an array
> of length n, I use in Python:
>
>   temp = [NotImplemented] * code[pos]
>   pos += 1
>
> Whereas in JavaScript I use, also
> in exec_build2():
>
>   temp = new Array(code[pos++]);
>
> So I hear Guido doesn't like ++. So in Python I use +=
> and a separate statement as a workaround. But otherwise,
> what about the creation of an array,
>
> is the the idiom [_] * _ slow? I am assuming its
> compiled away. Or does it really first create an
> array of size 1 and then enlarge it?



I'm sure you know you can put in timing statements to find bottlenecks.

import time
startTime = time.perf_counter()
[code block]
print("%.2f" % (time.perf_counter() - startTime))



Mostowski Collapse

unread,
Sep 15, 2021, 5:19:13 PM9/15/21
to
BTW: I could already make it faster, by not repeatedly
accessing .arg anymore. It went down from ca.:

171'000 ms

To this here:

140'000 ms

But only in the cold run. In the warm run it went back
to 171'000 ms. Possibly when my code is faster,
it will create objects more faster, and kill the Python GC.

Or it was because my Laptop went into screen black?
And throttled the CPU. Not sure.

DFS

unread,
Sep 15, 2021, 5:26:36 PM9/15/21
to
On 9/15/2021 5:10 PM, Mostowski Collapse wrote:
> And how do you only iterate over n-1 elements?
> I don't need a loop over all elements.
>
> With array slicing?
>
> Someting like:
>
> for item in items[0:len(items)-2]:
> ___print(item)
>
> Or with negative slicing indexes? Problem
> is my length can be equal to one.
>
> And when I have length equal to one, the
> slice might not do the right thing?
>
> LoL


From the python command prompt:

items = [1,2,3,4]

for itm in items:
print(itm)
1
2
3
4

for itm in items[:-2]:
print(itm)
1
2


for itm in items[:-3]:
print(itm)
1


for itm in items[:-4]:
print(itm)
(no result, no error thrown)


for itm in items[:-5]:
print(itm)
(no result, no error thrown)

Mostowski Collapse

unread,
Sep 15, 2021, 5:29:48 PM9/15/21
to
Thank you for the suggestion. The test harness
is invoked as follows. So it does already do time/1,
thats also how I did the comparison Standard Python

and GraalVM Python, a file dogelog.py:

import sys
# sys.path.append("<path>\jekrun_bench\core\harness2\libpy")
sys.path.append("/mnt/c/<path>/jekrun_bench/core/harness2/libpy")
from index import init, consult

init()
consult(":- ['suite2.p']. "
":- time(suite). "
":- nl. "
":- time(suite). ")

Here you see a GraalVM cold and warm run.The warm run is faster.
If you do a warm warm run, it even gets more faster, because of
JIT-ing, Just-in-Time machine compilation,

via the GraalVM Truffles framework:

$ export PATH=<path>/graalvm-ce-java8-21.2.0/bin:$PATH
$ cd /mnt/c/<path>/jekrun_bench/core/harness2
$ graalpython /mnt/c/<path>/jekrun_bench/core/harness2/dogelog.py
nrev % Wall 6175 ms, gc 212 ms, 154473 lips
crypt % Wall 9327 ms, gc 63 ms, 112838 lips
deriv % Wall 4101 ms, gc 90 ms, 321890 lips
poly % Wall 3594 ms, gc 415 ms, 216299 lips
sortq % Wall 3427 ms, gc 67 ms, 290070 lips
tictac % Wall 2770 ms, gc 51 ms, 136580 lips
queens % Wall 3287 ms, gc 64 ms, 325617 lips
query % Wall 1432 ms, gc 77 ms, 382969 lips
mtak % Wall 2532 ms, gc 95 ms, 533881 lips
perfect % Wall 3980 ms, gc 76 ms, 290382 lips
% Wall 40745 ms, gc 1212 ms, 235751 lips

nrev % Wall 4508 ms, gc 112 ms, 211595 lips
crypt % Wall 6063 ms, gc 61 ms, 173584 lips
deriv % Wall 3150 ms, gc 42 ms, 419070 lips
poly % Wall 3549 ms, gc 432 ms, 219042 lips
sortq % Wall 3196 ms, gc 63 ms, 311036 lips
tictac % Wall 2670 ms, gc 52 ms, 141695 lips
queens % Wall 3087 ms, gc 60 ms, 346713 lips
query % Wall 1434 ms, gc 25 ms, 382435 lips
mtak % Wall 2596 ms, gc 90 ms, 520719 lips
perfect % Wall 3521 ms, gc 43 ms, 328236 lips
% Wall 33810 ms, gc 980 ms, 284108 lips

Mostowski Collapse

unread,
Sep 15, 2021, 5:33:13 PM9/15/21
to
But the end-result is still very weak:
% Wall 33810 ms, gc 980 ms, 284108 lips

This is below 1 million LIPS.
The JavaScript version of Dogelog does currently around 2 million LIPS.
And SWI-Prolog can do around 20 million LIPS.

Chris Angelico

unread,
Sep 15, 2021, 6:02:54 PM9/15/21
to
On Thu, Sep 16, 2021 at 7:59 AM Mostowski Collapse <burs...@gmail.com> wrote:
>
> BTW: I could already make it faster, by not repeatedly
> accessing .arg anymore. It went down from ca.:
>
> 171'000 ms
>
> To this here:
>
> 140'000 ms
>
> But only in the cold run. In the warm run it went back
> to 171'000 ms. Possibly when my code is faster,
> it will create objects more faster, and kill the Python GC.
>
> Or it was because my Laptop went into screen black?
> And throttled the CPU. Not sure.
>

Instead of worrying about all these details, start by simplifying your
code. Focus on clean, simple, readable code, and don't microoptimize.
Specifically, focus on the core arithmetic that you're trying to do,
and get rid of all the bookkeeping overhead; most of that is a waste
of time. I mentioned earlier the repeated boxing and unboxing in
"Compound" objects - have you changed anything with those?

ChrisA

Mostowski Collapse

unread,
Sep 15, 2021, 10:56:50 PM9/15/21
to
Compound is not used for boxing. Integers and floats
are represented directly. Also integers are not mapped to
floats. But maybe compound could be a little flattened,

like using directly an array. But then you cannot assure
anymore "clean, simple, readable code". For example now
I have clean, simple and readable code, since I can

access the functor of a compound via:

obj.functor

but when I flatten Compound into arrays, it would become:

obj[0]

Should I declare a constant FUNCTOR = 0? Some of your
requirements have a trade-off, not all of them can be
sustained simultaneously so easy.

I am rather expecting languages like Python and JavaScript
to offer the same comfort as C or Java, except that
I don't need to write types for all the varianbles and fields

all the time. But this requires smart language compiler
and language runtime. V8 Chrome has interesting articles
how they optimize access like .functor.

Chris Angelico

unread,
Sep 16, 2021, 1:29:44 PM9/16/21
to
On Fri, Sep 17, 2021 at 3:20 AM Mostowski Collapse <burs...@gmail.com> wrote:
>
> Compound is not used for boxing. Integers and floats
> are represented directly. Also integers are not mapped to
> floats. But maybe compound could be a little flattened,
>

"Boxing" in this case isn't about ints and floats, since Java-like
bizarrenesses simply don't happen in Python; I'm talking about the way
that you frequently build up a Compound object for various situations
(even for throwing an error - you have a function that constructs a
generic Exception, and then buries a Compound inside it), and then
you're frequently checking if something is an instance of Compound.
All these constant back-and-forths are extremely expensive, since
they're not part of your algorithm at all.

At very least, use tuples instead of Compounds, but it would be far
better to ask less questions about your data and do more things by
tidying up your algorithm. Unfortunately, I can't really advise with
any detail, because you have code like this:

###
# Mark a term.
#
# @param term The term.
##
def mark_term(term):

What does that even mean?! I get it, you have a term, and you're
marking it. Whatever that mark means. The comments add absolutely
nothing that the function header didn't tell me. Are you implementing
your own garbage collection on top of Python's? Or something else?
It's extremely hard to give any sort of recommendations when your code
is hard to read, and nearly all of the comments are nothing more than
restating what can be seen in the next line of code. Also, with the
number of globals you're using, tracing the purpose of your functions
is not easy.

ChrisA

Mostowski Collapse

unread,
Sep 16, 2021, 3:57:10 PM9/16/21
to
About Exceptions: Thats just building ISO core
standard Prolog error terms.

About Garbage Collection: Thats just Prolog
garbage collection, which does shrink some
single linked lists, which ordinary
programmig language GC cannot do,

or maybe some Weak Pointer magic can do it?
The use case is very simple. A Prolog system
has a so called trail. The trail in Dogelog
Runtime is a single linked list:

-->[ A ]-->[ B ]-->[ C ]-->

Now if B becomes unused, you need to rewire
the trail, it should then look like:

-->[ A ]---------->[ C ]-->


If a programming language has a means to
communicate this to the Garbage Collector,
I happy to apply it. The challenge is many
fold, the pointer from A to B for example

needs not to be accounted to determine
whether B is reachable. So all the links
in the trail are weak pointers. But they
are weak pointers that need to

be able to adapt.

Mostowski Collapse

unread,
Sep 16, 2021, 3:59:05 PM9/16/21
to
Here is a challenge for Python.
Can Python solve Sudoku?

Mostowski Collapse wrote:
> I am not testing this use-case. But a related
> use-case might highlight why speed did never
> hurt anybody.
>
> Lets say you program a flying drone with Python,
> and the measurement is from the drone sensor
> and communication systems.
>
> Lets say you are using the idle time between
> measurements for some complex planning. It
> is then not true that you have anyway
>
> to wait for the measurement.
>
> Hope this helps!
>
> BTW: If somebody knows another Python implementation
> I am happy to test this implementation as well.
> I am assuming that the standard Python python.exe
>
> I tested amounts to CPython? Not sure. And the
> GraalVM is practically the same as JPython? Not
> sure either.
>
>> Opinion:   Anyone who is counting on Python for truly fast compute
>> speed is probably using Python for the wrong purpose. Here, we use
>> Python to control Test Equipment, to set up the equipment and ask for
>> a measurement, get it, and proceed to the next measurement; and at the
>> end produce a nice formatted report. If we wrote the test script in C
>> or Rust or whatever it could not run substantially faster because it
>> is communicating with the test equipment, setting it up and waiting
>> for responses, and that is where the vast majority of the time goes.
>> Especially if the measurement result requires averaging it can take a
>> while.  In my opinion this is an ideal use for Python, not just
>> because the speed of Python is not important, but also because we can
>> easily find people who know Python, who like coding in Python, and
>> will join the company to program in Python ... and stay with us.
>> --- Joseph S.
>

Mostowski Collapse

unread,
Sep 16, 2021, 4:27:29 PM9/16/21
to
A friend just sent me a Web Sudoku made with Dogelog Runtime
https://gist.github.com/jburse/c85297e97091caf22d306dd8c8be12fe#gistcomment-3895696

LoL

Avi Gross

unread,
Sep 16, 2021, 5:43:10 PM9/16/21
to
Some questions make no sense to me.

Can a kind of snake solve Sudoku? Do you mean a specific puzzle, or any puzzle or even a puzzle with no solution?

Can a programming language do it? Well, in my experience, programming languages are tools to be used by humans, or sometimes by other programming languages. They are not sentient and cannot be asked to solve much of anything.

So is the question whether someone can program using only Python to solve an arbitrary sudoku problem? Short answer is you can do that in just about ANY language. I mean by brute force, if you have a 9 by 9 matrix with some of the 81 locations already filled in, then you can try every darn combination of the other spots using digits 1 to 9 and then ignore any where the rows and columns and the 9 3x3 submatrices do not follow the rules. At least one solution is guaranteed to pop out if there is one. Sure, such methods may run out of memory or take a while, but many can use little memory and some can speed things up by not going down blind alleys such as placing a number in a position where there already is the same number on the same row or column or sub-matrix.

So is the real question whether a human has already made a decent implementation in Python available? Sure, do a little searching and there are plenty of such things including some that use interesting features of python and some that are just translations from a more primitive language.
--
https://mail.python.org/mailman/listinfo/python-list

Mostowski Collapse

unread,
Sep 16, 2021, 5:47:00 PM9/16/21
to
The new release 0.9.6 is quite speedy:

"Maailman vaikein"
850002400720000009004000000000107002305000900040000000000080070017000000000036040
time(solve(Puzzle))
% Wall 41354 ms, gc 520 ms, 3143029 lips
in Browser

See also:

Preview: New para/1 instruction for Dogelog runtime. (Jekejeke)
https://twitter.com/dogelogch/status/1438586282502983682

Preview: New para/1 instruction for Dogelog runtime. (Jekejeke)
https://www.facebook.com/groups/dogelog

Greg Ewing

unread,
Sep 16, 2021, 9:24:20 PM9/16/21
to
On 16/09/21 4:23 am, Mostowski Collapse wrote:
> I really wonder why my Python implementation
> is a factor 40 slower than my JavaScript implementation.

There are Javascript implementations around nowadays that are
blazingly fast. Partly that's because a lot of effort has been
put into them, but it's also because Javascript is a different
language. There are many dynamic aspects to Python that make
fast implementations difficult.

> I use in Python:
>
>   temp = [NotImplemented] * code[pos]
>   pos += 1
>
> is the the idiom [_] * _ slow?

No, on the contrary it's probably the fastest way to do it
in Python. You could improve it a bit by precomputing
[NotImplemented]:

# once at the module level
NotImplementedList = [NotImplemented]

# whenever you want a new list
temp = NotImplementedList * code[pos]

That's probably at least as fast as built-in function for
creating lists would be.

> does it really first create an
> array of size 1 and then enlarge it?

It does:

>>> def f(code, pos):
... return [NotImplemented] * code[pos]
...
>>> from dis import dis
>>> dis(f)
2 0 LOAD_GLOBAL 0 (NotImplemented)
2 BUILD_LIST 1
4 LOAD_FAST 0 (code)
6 LOAD_FAST 1 (pos)
8 BINARY_SUBSCR
10 BINARY_MULTIPLY
12 RETURN_VALUE

BTW, the Python terminology is "list", not "array".
(There *is* something in the stdlib called an array, but
it's rarely used or needed.)

--
Greg

Chris Angelico

unread,
Sep 16, 2021, 9:30:03 PM9/16/21
to
On Fri, Sep 17, 2021 at 7:17 AM Mostowski Collapse <janb...@fastmail.fm> wrote:
>
> About Exceptions: Thats just building ISO core
> standard Prolog error terms.
>
> About Garbage Collection: Thats just Prolog
> garbage collection, which does shrink some
> single linked lists, which ordinary
> programmig language GC cannot do,
>

Okay, so.... you're building your own garbage collection on top of
Python's, and you're wondering why it's slow?

Change your code to not try to implement one language inside another,
and you'll see a massive performance improvement.

ChrisA

Greg Ewing

unread,
Sep 16, 2021, 9:34:53 PM9/16/21
to
On 16/09/21 2:56 pm, Mostowski Collapse wrote:
> I can access the functor of a compound via:
>
> obj.functor
>
> but when I flatten Compound into arrays, it would become:
>
> obj[0]
>
> Should I declare a constant FUNCTOR = 0?

You could, but keep in mind that access to a global in Python
is somewhat expensive, since it requires a dictionary lookup.

There's always a tradeoff between clarity and efficiency.
In this case, I think obj[0] is clear enough -- the reader
will be well aware that the first item of a term is the
functor. Especially if you use a name that makes it clear
what kind of object it is, rather than a generic name such
as "obj".

--
Greg

Greg Ewing

unread,
Sep 16, 2021, 9:44:36 PM9/16/21
to
On 16/09/21 6:56 am, Mostowski Collapse wrote:
> What could be slow, repeatedly requesting the "args"
> field. Maybe I should do:
>
> help = term.args
> i = 0
> while i < len(help) - 1:
> ____mark_term(help[i])
> ____i += 1
> term = help[i]

Yes, that will certainly help.

But you're still evaluating len(help) - 1 every time around
the loop, so this is even better:

help = term.args
n = len(help) - 1
i = 0
while i < n:
mark_term(help[i])
i += 1
term = help[i]

Some general principles to be aware of:

* Almost nothing is free -- Python very literally does what
you tell it to do, even if it looks dumb.

* Access to attributes and global variables is expensive
(requires at least one dict lookup, often more in the case
of attributes).

* Access to *local* variables, on the other hand, is very
cheap (essentially an array lookup).

* Function calls are expensive -- both to look up the name, if
it's global, which it usually is, and the machinery of the
call itself.

* Creating objects is expensive. Creating instances of
user-defined objects is more expensive than built-in ones.

--
Greg

Mostowski Collapse

unread,
Sep 17, 2021, 4:41:48 AM9/17/21
to
Thanks for your response, will have a look.
Ok, dis() is all that is need to disassemble.

Very cool!

A long term goal could be indeed to have
a Prolog interpreter produce 20MLips, like
SWI-Prolog, but tightly integrated into

Python. So that it directly makes use of
the Python objects and the Python garbage
collection like Dogelog Runtime.

Although Dogelog Runtime has its own
garbage collection, its only used to help
the native Python garbage collection.

The result is that you can enjoy bi-directly
calling Python. For example the Prolog
adding of two numbers is realized as:

###
# +(A, B, C): [ISO 9.1.7]
# The predicate succeeds in C with the sum of A and B.
##
def eval_add(alpha, beta):
check_number(alpha)
check_number(beta)
try:
return alpha + beta
except OverflowError:
raise make_error(Compound("evaluation_error", ["float_overflow"]))

And then register it:

add("+", 3, make_dispatch(eval_add, MASK_MACH_FUNC))

Could also map the exception to a Prolog term later.
Thats not so much an issue for speed. The sunshine
case is straight forward.

But I might try dis() on eval_add(). Are exceptions
blocks in Python cheap or expensive? Are they like
in Java, some code annotation, or like in Go

programming language pushing some panic handler?

Greg Ewing schrieb:

Mostowski Collapse

unread,
Sep 17, 2021, 4:51:49 AM9/17/21
to
No its cooperative. Usually objects do get
garbage collected by the native garbage collector
of the host language in Dogelog runtime.

The Prolog garbage collection is only to help
the host language garbage collector when you have
a deep recursion in Prolog.

You can then reclaim intermediate variables.
A simple example to test the slightly idio-
syncratic Prolog garbage collection is:

fibo(0, 1) :- !.
fibo(1, 1) :- !.
fibo(N, X) :-
M is N-1, fibo(M, Y),
L is M-1, fibo(L, Z),
X is Y+Z.

When running fibo(30,X) SWI-Prolog does around
800 garbage collections to keep the environment
small. But SWI-Prolog allocates all its objects

only very seldom on the heap. It uses its own
stack. On the other hand Dogelog runtime creates
everything on the heap. And completely relies on

the host language garbage collection. It only
helps the host language garbage collection it
that it performs from time to time this movement:

Before:

-->[ A ]-->[ B ]-->[ C ]-->

After:

-->[ A ]---------->[ C ]-->

A,B,C are objects of type Variable. The above
movement only happens for objects of type Variables
from time to time. For objects of type Compound

no modifications are done during Prolog garbage
collection. The Prolog garbage collection aggressively
nulls the Variable object B, and the host language

later will garbage collect what the Variable object B
was pointing to. But the Variable object B might
nevertheless have point to something shared with

some other Variable object or a local or a global
Python variable, or a Compound. This is then all
courtesy of the host language to decide reachability.

Chris Angelico schrieb:

Mostowski Collapse

unread,
Sep 17, 2021, 4:58:57 AM9/17/21
to
The Prolog garbage collection that does
the movement on the variable trail is only
a very small fraction of the runtime.

The garbage collection time is measured.
Some measurements with version 0.9.5
took the following values:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Standard Python Version, Warm Run
% ?- time(fibo(23,X)).
% % Wall 3865 ms, gc 94 ms, 71991 lips
% X = 46368.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% GraalVM Python Version, Warm Warm Run
% ?- time(fibo(23,X)).
% % Wall 695 ms, gc 14 ms, 400356 lips
% X = 46368.

The "gc" timing measures Prolog garbage
collection. So you get the following percentage
of time spent in Prolog garbage collection:

Standard Python: 94 / 3865 = 2.4%

GraalVM Python: 14 / 695 = 2.0%

I consider this a good result. The Prolog
garbage collection is not utterly expensive.
The detecting the movement and performing

the variable movement on the trail, doesn't
take so much time. Currently the garbage collection
in Dogelog runtime is configured towards

synchronization with 60FPS, it does around
60-120 garbage collections per second. This
is less than what SWI-Prolog does.

SWI-Prolog has a much higher GC rate.

But I did not yet measure new version 0.9.6.

Mostowski Collapse schrieb:

Mostowski Collapse

unread,
Sep 17, 2021, 7:05:39 AM9/17/21
to
Concerning garbage collection, did a long term
measurement for the first time. I measured
LIPS for fibonacci numbers, i.e. time(fibo(23,X)).

Doing the same thing 16 times, how long does it take?
Here is a depiction how the LIPS relatively differ in each run:
https://gist.github.com/jburse/c85297e97091caf22d306dd8c8be12fe#gistcomment-3896343

I can also calculate the mean and standard deviation.
From this we see that Python has a 5% deviation, whereas
GraalVM has a 1% deviation. So the GraalVM garbage

collector works more evenly? Disclaimer, I measured
different time spans, the GraalVM is now 7x times
faster than Standard Python, so this is inconclusive.

Greg Ewing

unread,
Sep 17, 2021, 11:49:37 PM9/17/21
to
On 17/09/21 7:56 am, Mostowski Collapse wrote:
> The trail in Dogelog
> Runtime is a single linked list:
>
>     -->[ A ]-->[ B ]-->[ C ]-->
>
> Now if B becomes unused, you need to rewire
> the trail, it should then look like:
>
>     -->[ A ]---------->[ C ]-->

Python has a way of creating weak references (see the weakref
module). You could set the trail up like this:

-->[*]-->[*]-->[*]-->
| | |
v v v
[w] [w] [w]
: : :
v v v
[A] [B] [C]

where [w] is a weak reference object. Then you could periodically
scan the trail looking for dead weakref objects and remove the
corresponding [*] node from the list.

You can also attach callbacks to weakref objects that are triggered
when the referenced object dies. You might be able to make use of
that to remove items from the trail instead of the periodic scanning.

--
Greg

Greg Ewing

unread,
Sep 17, 2021, 11:53:45 PM9/17/21
to
On 16/09/21 6:13 am, Mostowski Collapse wrote:
> So in Python I now do the following:
>
> peek = kb.get(functor, NotImplemented)
> if peek is not NotImplemented:

If you're able to use None instead of NotImplemented, you
could simplify that to

peek = kb.get(functor)
if peek:
...

> But if get() in Python is implemented under the hood with
> exception handling. ... then Python get() will probably be quite slow.

No, it doesn't use exceptions as far as I know. It should
be fairly fast.

--
Greg

Greg Ewing

unread,
Sep 18, 2021, 12:29:22 AM9/18/21
to
On 17/09/21 8:41 pm, Mostowski Collapse wrote:
> Are exceptions
> blocks in Python cheap or expensive? Are they like
> in Java, some code annotation, or like in Go
> programming language pushing some panic handler?

They're not free, but they're not hugely expensive either.
Don't be afraid to use them when it makes sense to do so.

I'm not sure what you mean by "some code annotations",
so I don't know how to answer that question. I suspect
that the way exceptions are implemented in the JVM is
similar to the way CPython does it, but I don't know
a lot about the JVM.

--
Greg

Mostowski Collapse

unread,
Sep 18, 2021, 5:29:05 PM9/18/21
to
Yeah, it seems weak references could indeed spare
me mark_term(). But then I am stil left with sweep_trail().
I did not yet measure what takes more time mark_term()
or sweep_trail(). The displayed "gc" is the sum of both.

From what I have seen, very large trail practically reduced
to a zero trail during Prolog GC, I am assuming that
mark_term() is not the working horse. Usually mark_term()
only marks what is not-Garbage, and sweep_trail()

has to deal with Garbage and not-Garbage. And there
is usually a lot of Garbage, much more than not-Garbage.
Finding the objects that survive, is like finding the needle
in the haystack, except we do not have to scan the

haystack, the needles are on the goal list. But afterwards,
the second pass, scanning the trail is the work of Heracles
cleaning the Augeas stables. This scan is trowing away the
hay and keeping the needles.

Chris Angelico

unread,
Sep 18, 2021, 9:58:03 PM9/18/21
to
On Sun, Sep 19, 2021 at 11:46 AM Mostowski Collapse <burs...@gmail.com> wrote:
>
> Yeah, it seems weak references could indeed spare
> me mark_term(). But then I am stil left with sweep_trail().
> I did not yet measure what takes more time mark_term()
> or sweep_trail(). The displayed "gc" is the sum of both.
>
> From what I have seen, very large trail practically reduced
> to a zero trail during Prolog GC, I am assuming that
> mark_term() is not the working horse. Usually mark_term()
> only marks what is not-Garbage, and sweep_trail()
>
> has to deal with Garbage and not-Garbage. And there
> is usually a lot of Garbage, much more than not-Garbage.
> Finding the objects that survive, is like finding the needle
> in the haystack, except we do not have to scan the

If you stop referring to something, it is garbage. Python will dispose of it.

You literally need to do nothing at all, and let the language take
care of things.

ChrisA

Mostowski Collapse

unread,
Sep 19, 2021, 4:51:03 AM9/19/21
to
I am refering to:

Greg Ewing schrieb:
> where [w] is a weak reference object. Then you could periodically
> scan the trail looking for dead weakref objects and remove the
> corresponding [*] node from the list.
>
> You can also attach callbacks to weakref objects that are triggered
> when the referenced object dies. You might be able to make use of
> that to remove items from the trail instead of the periodic scanning.

Question to Chris Angelico: If I stay with my
sweep_trail(), which is the periodically scanning,
I can use a single linked list.

On the other hand if I would use the trigger
from Python, I possibly would need a double linked
list, to remove an element.

Chris Angelico, is there a third option, that I have
overlooked? Single linked list uses less space
than double linked list, this why I go with scan.

Chris Angelico schrieb:

Mostowski Collapse

unread,
Sep 19, 2021, 5:03:14 AM9/19/21
to
The trail itself can possibly not be eliminated. Its like a
database logfile. The trail is used during backtracking to
undo variable bindings. Which is like a database rollback.

Here is an example where a tail is used:

/* X equals 1 or X equals 2 */
?- X=1; X=2.
X = 1;
X = 2.

In the first answer the trail will have recorded that X
was bound to 1. When the second answer is requested,
the trail is used to unbind X, so that it can be bound to 2.

The Prolog garbage collection is a compactification
of the trail. Maybe databases can do the same with their
logfiles, for example if a logfile contains an insert and

then a delete of the same row, then these two logentries
can be merged. The only difference here is that Prolog
garbage collection primarily compactes towards trail

entries that have become irrelevant. This is part of
intelligent bracktracking, and backjumping over multiple
goal invocations, that didn't record a choice point.

The Prolog garbage collection can compact the trail
before backjumping happens. So that Prolog search
has more space available.

Chris Angelico

unread,
Sep 19, 2021, 2:34:10 PM9/19/21
to
On Mon, Sep 20, 2021 at 3:19 AM Mostowski Collapse <janb...@fastmail.fm> wrote:
>
> I am refering to:
>
> Greg Ewing schrieb:
> > where [w] is a weak reference object. Then you could periodically
> > scan the trail looking for dead weakref objects and remove the
> > corresponding [*] node from the list.
> >
> > You can also attach callbacks to weakref objects that are triggered
> > when the referenced object dies. You might be able to make use of
> > that to remove items from the trail instead of the periodic scanning.
>
> Question to Chris Angelico: If I stay with my
> sweep_trail(), which is the periodically scanning,
> I can use a single linked list.
>
> On the other hand if I would use the trigger
> from Python, I possibly would need a double linked
> list, to remove an element.
>
> Chris Angelico, is there a third option, that I have
> overlooked? Single linked list uses less space
> than double linked list, this why I go with scan.
>

I don't know. I don't understand your code well enough to offer advice
like that, because *your code is too complicated* and not nearly clear
enough.

But however it is that you're doing things, the best way is almost
always to directly refer to objects. Don't fiddle around with creating
your own concept of a doubly-linked list and a set of objects; just
refer directly to the objects. Let Python be Python, don't try to
build your own language on top of it.

ChrisA

Mostowski Collapse

unread,
Sep 19, 2021, 3:46:20 PM9/19/21
to
sympy also builds a language on top of Python.
pandas also builds a language on top of Python.

Is there some pope that says this wouldn't be
allowed, I dont think so, otherwise sympy, pandas, etc..

wouldn't exist. I dont understand your argument.

Chris Angelico schrieb:

Mostowski Collapse

unread,
Sep 19, 2021, 4:02:41 PM9/19/21
to
Also I nowhere wrote that I would use doubly-linked list or
a set of objects. The trail is neither, its a single linked list. I
also refer directly to objects, I do not need the trail to refer

to objects. The Prolog trail is only a logbook. The Prolog trail
has similarity to a database log:

transaction journal, database log, binary log or audit **trail**
https://en.wikipedia.org/wiki/Transaction_log

Do you say Python should not be used to implement
such things? In my opinion, Python has a high potential
to implement Prolog, because it has also ast.parse()

and compile(). But I do not yet have a showcase that uses
these features of Python to compile Prolog. I dont work 24/7
and I cannot clone myself. Currently the Prolog code is interpreted.

I have a prototype where Prolog code is compiled into JavaScript,
but I did not yet try this approach with Python. Here you see how
JavaScript closures are generated, a first prototype:

const alt4 = make_defined([new Clause(1, [0, 0], function(
display, actual, cont) {return(new Compound(".", [new Compound(
"==", [deref(actual.args[0]), "end_of_file"]), new Compound(
".", [new Compound("$CUT", [deref(display[0])]), cont
])]))}, 0, undefined), new Clause(1, [0, 0], function(
display, actual, cont) {return(new Compound(".", [new Compound(
"expand_include", [deref(actual.args[0]), deref(actual.args[1]
), display[0] = new Variable()]), new Compound(".",
[new Compound("handle_term", [deref(display[0])]), new Compound(
".", ["fail", cont])])]))}, -1, undefined)]);

add("next_term", 1, new Clause(2, [0], function(display, actual,
cont) {return(new Compound(".", [new Compound("read_term",
[deref(actual.args[0]), display[0] = new Variable(),
new Compound(".", [new Compound("variable_names", [
display[1] = new Variable()]), "[]"])]), new Compound(
".", [new Compound(alt4, [deref(display[0]), deref(
display[1])]), cont])]))}, -1, undefined));

https://github.com/jburse/dogelog-moon/issues/184

Will do the same for Python in the next weeks. Then later this approach
will be combined with a few planned optimizations. So far got a 25%
speed increase for JavaScript with this new compilation scheme, but

there is no official release out yet, that features this approach. And
there should be much more in it, also for Python.

Mostowski Collapse

unread,
Sep 19, 2021, 4:22:27 PM9/19/21
to
Please be patient. A big problem with development can be
burnout. So I am trying to slow down things at the moment.
The ideas are easy to sketch, but implementation can take

weeks. Here is the idea again in a nutshell: use ast.parse()
and compile(). Or build directly an AST, not using the string
detour as in ast.parse(). How long will it take to have a

working solution? 11 years ago there was:

Pyrolog: Prolog written in Python using PyPy's RPython tool chain
https://www.reddit.com/r/prolog/comments/fbuz1/pyrolog_prolog_written_in_python_using_pypys/

RPython is a framework for implementing interpreters and virtual
machines for programming languages, especially dynamic languages.
https://rpython.readthedocs.io/en/latest/faq.html

Currently I am not planning to use RPython, want to to use
standard Python AST and compile(). Might have a look at
RPython or similar stuff later.

Greg Ewing

unread,
Sep 19, 2021, 6:53:50 PM9/19/21
to
> On Mon, Sep 20, 2021 at 3:19 AM Mostowski Collapse <janb...@fastmail.fm> wrote:
>>
>> On the other hand if I would use the trigger
>> from Python, I possibly would need a double linked
>> list, to remove an element.

Here's another idea: Put weak references in the trail, but
don't bother with the scanning -- just skip over dead ones
during backtracking.

Seems to me you would be doing about the same amount of
work either way, just doing it at different times.

--
Greg

Mostowski Collapse

unread,
Sep 20, 2021, 2:44:49 AM9/20/21
to
This strategy works if you use failure driven loops.
It doesn't work you program recursive loops that go
on forever. Like Erlang processes.

foo(X) :-
bar(X,Y), foo(Y).

Typically Y is a fresh variable. A good Prolog system
with good Garbage Collection can run such a process
for days and months.

If you don't clean up the trail, you exhaust the
memory after a few minutes or seconds.

Greg Ewing schrieb:

Mostowski Collapse

unread,
Sep 20, 2021, 6:04:49 AM9/20/21
to
But I dont see any utility in investing too much time with
the Prolog garbage collection of Dogelog runtime. It is
only a small share of the execution time:

Mostowski Collapse schrieb am Freitag, 17. September 2021 um 10:58:57 UTC+2:
> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
> % Standard Python Version, Warm Run
> % ?- time(fibo(23,X)).
> % % Wall 3865 ms, gc 94 ms, 71991 lips
> % X = 46368.
>
> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
> % GraalVM Python Version, Warm Warm Run
> % ?- time(fibo(23,X)).
> % % Wall 695 ms, gc 14 ms, 400356 lips
> % X = 46368.
>
> The "gc" timing measures Prolog garbage
> collection. So you get the following percentage
> of time spent in Prolog garbage collection:
>
> Standard Python: 94 / 3865 = 2.4%
>
> GraalVM Python: 14 / 695 = 2.0%

If you spare these 2-3% it will not speed-up Dogelog runtime.
The Prolog garbage collection is there to allow Erlang
processes. And its also there to allow more Prolog search

without exhausting the memory. But it cost you only 2-3%
judging from the Fibonnacci Numbers example. Need to
check with other examples whether its higher.

But since the ratio between Garbage and non-Garbage is
usually high, and non-Garbage is easily identified, and
the Garbage is also easily removed, the time will

possibly not exceed much more than the same 2-3% for
other examples. So in conclusion I am least worried
about the Prolog garbage collection. You guys are only

worried because its something new. But its nothing that
does any harm and costs a lot, it only does good and
is very cheap in terms of extra runtime effort.

Peter J. Holzer

unread,
Sep 20, 2021, 7:49:49 AM9/20/21
to
On 2021-09-20 04:33:39 +1000, Chris Angelico wrote:
> On Mon, Sep 20, 2021 at 3:19 AM Mostowski Collapse <janb...@fastmail.fm> wrote:
> > Question to Chris Angelico: If I stay with my
> > sweep_trail(), which is the periodically scanning,
> > I can use a single linked list.
> >
> > On the other hand if I would use the trigger
> > from Python, I possibly would need a double linked
> > list, to remove an element.
> >
> > Chris Angelico, is there a third option, that I have
> > overlooked? Single linked list uses less space
> > than double linked list, this why I go with scan.
> >
>
> I don't know. I don't understand your code well enough to offer advice
> like that, because *your code is too complicated* and not nearly clear
> enough.
>
> But however it is that you're doing things, the best way is almost
> always to directly refer to objects. Don't fiddle around with creating
> your own concept of a doubly-linked list and a set of objects; just
> refer directly to the objects.

And almost certainly: Just use the builtin list type if you need a list.
Don't build one yourself.


> Let Python be Python, don't try to build your own language on top of
> it.

Well, he's writing a Prolog interpreter, so building his own language on
top of Python is sort of the point. I think a better way to put it is
"Don't try to write Python as if it was C". A C operation may be
compiled to a single machine instruction which is executed in a fraction
of a nanosecond. A Python instruction (in CPython) always includes at
least the interpreter overhead and often several method lookups and method
calls. You want to amortize that overhead over as much work as possible.

hp

--
_ | Peter J. Holzer | Story must make more sense than reality.
|_|_) | |
| | | h...@hjp.at | -- Charles Stross, "Creative writing
__/ | http://www.hjp.at/ | challenge!"
signature.asc

Mostowski Collapse

unread,
Sep 20, 2021, 8:09:07 AM9/20/21
to
I read the following, and you should also know:

> Python's [] is implemented as an array, not a linked list.
> Although resizing is O(n), appending to it is amortized O(1),
> because resizes happen very rarely.
https://stackoverflow.com/a/5932364/502187

The list type doesn't have an O(1) operation to remove
an element during sweep. The list type, not like its name
would suggest, in Python is an array.

These arrays are not so expensive when you append()
an element. Because they are allocated with some excess
capacity. And they grow exponentially.

So amortisized you can append() a lot of elements to
a Python list, which is an array. But you cannot poke so
cheaply holes into it. So if you have this scenario:

Before:
- [ A1, .., An , B, C1, .., Cm ]

After:
- [ A1, .., An , C1, .., Cm ]

You have to copy C1,..,Cm one position down. On the other
hand, when scanning the single list, removing the
element is just pointer swizzling.

The code is here, the positive if-then-else branch keeps
the element, the negative if-then-else branch drops the
element. Thats quite standard algorithm for linked lists:

/* pointer swizzling */
while temp is not None:
term = temp
temp = term.tail
if (term.flags & MASK_VAR_MARK) != 0:
term.flags &= ~MASK_VAR_MARK
if back is not None:
back.tail = term
else:
trail = term
back = term
else:
term.instantiated = NotImplemented
term.tail = None
count -= 1

https://github.com/jburse/dogelog-moon/blob/main/devel/runtimepy/drawer/machine.py#L163

There is nothing wrong with implementing a single list
in Python. Only you never saw that one maybe. If you would
indeed use Python lists which are arrays, you would

maybe get a much slower sweep_trail() and this would
be seen. But currently its not seen. It happens that 1000000
of elements are sweeped, if you would do this with copy

inside a Python list which are arrays, it would get much
more expensive, and the extremly cheap Prolog garbage
collection, as it stands now, wouldn't be that cheap anymore.

You can try yourself. My sweep_trail() needs frequent resize,
which would be O(n) each, so that sweep_trail becomes O(n^2).
Which the current implementation its only O(n).

Mostowski Collapse

unread,
Sep 20, 2021, 8:16:49 AM9/20/21
to
What would be maybe possible, is to
scan the trail from the other side, and
use a first pass to determine the new

size, and use a second pass to fill a new
array with the remaining elments. This would
be two times O(n), so it would be linear

and not quadratic O(n^2) as when you scan
from the top and poke holes. But then something
else doesn't work so easily. Namely:

def adjust_mark(temp):
while temp is not None:
if (temp.flags & MASK_VAR_MARK) != 0:
return temp
else:
temp = temp.tail
return temp

https://github.com/jburse/dogelog-moon/blob/main/devel/runtimepy/drawer/machine.py#L151

This is needed to adjust the choice points.
If you have an array instead of a linked
listed as I have now, you would need

to adjust array indexes instead pointers
into linked list elements. I havent come
up with an array solution yet for the trail,

since I dont see any utility in investing too
much time with the Prolog garbage collection of
Dogelog runtime. It is only a small share

of the execution time:

Mostowski Collapse schrieb am Freitag, 17. September 2021 um 10:58:57 UTC+2:
> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
> % Standard Python Version, Warm Run
> % ?- time(fibo(23,X)).
> % % Wall 3865 ms, gc 94 ms, 71991 lips
> % X = 46368.
>
> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
> % GraalVM Python Version, Warm Warm Run
> % ?- time(fibo(23,X)).
> % % Wall 695 ms, gc 14 ms, 400356 lips
> % X = 46368.

Mostowski Collapse

unread,
Sep 20, 2021, 8:22:05 AM9/20/21
to
In general the algorithm I am using is from
a paper by Matts Carlson from SICStus Prolog.
Its this paper here:

Garbage Collection for Prolog Based on WAM
January 1986
Karen Appleby, Mats Carlsson, Seif Haridi, Dan Sahlin
https://www.researchgate.net/publication/279463524

But since you guys are so facinated with
the Prolog garbage collection aspect, which
is not the locus where you can do a lot

of optimizations, feel free to investigate
and come up with a solution. It will not
change the performance of Dogelog runtime,

but it could be an academic experiment
neverthless. There is nothing wrong with the
simgle linked list as it stands, since

it has O(n) sweep_trail(). It uses a litte
more storage than an array would do.

Chris Angelico

unread,
Sep 20, 2021, 8:25:12 AM9/20/21
to
On Mon, Sep 20, 2021 at 9:50 PM Peter J. Holzer <hjp-p...@hjp.at> wrote:
> > Let Python be Python, don't try to build your own language on top of
> > it.
>
> Well, he's writing a Prolog interpreter, so building his own language on
> top of Python is sort of the point. I think a better way to put it is
> "Don't try to write Python as if it was C".

Fair point. Or combining them both: Writing a language interpreter in
Python as if you were writing it in C, and then complaining that it is
slow, is only going to elicit "well uhh yes?" responses.

Languages like NetRexx (and, I think, Jython, although I can't find
any definitive and current answers) are slightly different from their
"parent" languages, because they make good use of their implementation
languages' features. This Prolog interpreter might not even need to be
different in functionality, but its implementation would be different,
and it could take advantage of the underlying garbage collection.

ChrisA

Mostowski Collapse

unread,
Sep 20, 2021, 8:36:01 AM9/20/21
to
The sweep_trail() is not an issue. There must be a bottleneck
somewhere else in Python. The sweep_trail() respectively the
paremt call gc() only takes a very small fraction of the runtime:

Check the "gc" timing, the bottleneck is somewhere else?

Mostowski Collapse schrieb am Freitag, 17. September 2021 um 10:58:57 UTC+2:
> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
> % Standard Python Version, Warm Run
> % ?- time(fibo(23,X)).
> % % Wall 3865 ms, gc 94 ms, 71991 lips
> % X = 46368.
>
> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
> % GraalVM Python Version, Warm Warm Run
> % ?- time(fibo(23,X)).
> % % Wall 695 ms, gc 14 ms, 400356 lips
> % X = 46368.

Also my code is not C-style. If I would use C-style code, I would
use address calculations and the adress operator &. But you
don't find and according C-style in the Python or JavaScript code.

Also there is no substitute for such coding style in the for
of value holders or some such. Its all plain Python respectively
JavaScript not at all inspired by the C programming language.

The single linked list is not some indicative of C programming
language style. With C programming language sytle one would
do other tricks, you cannot read off from my Python or JavaScript

code, since I cannot apply them to Python or JavaScript. Among
the other C programming language tricks not available in Python
or JavaScript would for example be inlining the args in Compound

and so on. But I am not sure whether this is the bottleneck.

Mostowski Collapse

unread,
Sep 20, 2021, 8:43:01 AM9/20/21
to
Also I am not a C programmer. The last time I was programming C
was 30 years ago. I am mostly a Java programmer the recent years.
Dogelog Runtime adopts a lot of implementation details from

Jekejeke Prolog which is a Prolog written in Java. The difference
betweeen the two is that Jekejeke Prolog has another Prolog term
model where Prolog terms are passed around as molecs, which

are pairs of skeleton and display. On the other in Dogelog Runtime
uses a simpler representation, Prolog terms are passed around
as only one object. Programming language wise the difference

between using Java and JavaScript or Python, is that Java has
types. So variables need a type declaration. Otherwise Java is
very similar to JavaScript or Python, it also provides a runtime with

a garbage collection. The idea I would use C-style is a little absurd. It
would also require that a free() objects manually. But sweep_trail() has
nothing to do with freeing objects manually, its the anti-thesis to

freeing objects manually, its a Prolog garbage collector after all!

Mostowski Collapse

unread,
Sep 20, 2021, 8:52:18 AM9/20/21
to
Now I am expecting somebody telling me I should not
program Java style in Python. So I wonder what would have
happened if I would tell you I am FORTRAN programmer.

Then somebody would tell me I should not program
FORTRAN style in Python. Hopefully this makes it evident
that this argumentation is moot.

Better would be more facts on the table based on
the existing code of Dogelog runtime. The future Dogelog
runtime code will possibly use ast.parse() and compile().

Thats why I am conducting this experiment which has only
been half way completed. The experiment is conducted
because Python belongs to the champ of dynamic languages:

Dynamic programming language
https://en.wikipedia.org/wiki/Dynamic_programming_language

The experiment will be completed when the ast.parse()
and compile() thingy was explored as well. As it happens I
am conducting the experiment in parallel for JavaScript and

Python, both being dynamic programming languages.

Chris Angelico

unread,
Sep 20, 2021, 1:53:36 PM9/20/21
to
On Tue, Sep 21, 2021 at 3:51 AM Mostowski Collapse <janb...@fastmail.fm> wrote:
>
> sympy also builds a language on top of Python.
> pandas also builds a language on top of Python.
>
> Is there some pope that says this wouldn't be
> allowed, I dont think so, otherwise sympy, pandas, etc..
>
> wouldn't exist. I dont understand your argument.
>

That's not the same thing as reimplementing your own low-level
features on top of a high level language.

ChrisA

Michael F. Stemper

unread,
Sep 20, 2021, 1:56:59 PM9/20/21
to
On 20/09/2021 07.08, Mostowski Collapse wrote:
> I read the following, and you should also know:
>
>> Python's [] is implemented as an array, not a linked list.
>> Although resizing is O(n), appending to it is amortized O(1),
>> because resizes happen very rarely.
> https://stackoverflow.com/a/5932364/502187
>
> The list type doesn't have an O(1) operation to remove
> an element during sweep. The list type, not like its name
> would suggest, in Python is an array.
>
> These arrays are not so expensive when you append()
> an element. Because they are allocated with some excess
> capacity. And they grow exponentially.
>
> So amortisized you can append() a lot of elements to
> a Python list, which is an array. But you cannot poke so
> cheaply holes into it. So if you have this scenario:
>
> Before:
> - [ A1, .., An , B, C1, .., Cm ]
>
> After:
> - [ A1, .., An , C1, .., Cm ]
Do you happen to know what the asymptotic complexity of the
remove operation is?

>>> l = ["A1","A2","An","B","C1","C2","Cm"]
>>> l.remove("B")
>>> l
['A1', 'A2', 'An', 'C1', 'C2', 'Cm']
>>>

I can't figure out any way to benchmark it that doesn't involve
creation of ever-longer lists, which would probably mask its effects.

--
Michael F. Stemper
Deuteronomy 24:17

Mostowski Collapse

unread,
Sep 20, 2021, 1:58:36 PM9/20/21
to
Again, I don't implement any low level features,
that would explain any bottleneck in the performance
of Python. gc() is only a very small percentage.

You are halucinating.

Mostowski Collapse

unread,
Sep 20, 2021, 2:00:17 PM9/20/21
to
Maybe you could stop spamming my thread with
your idee fix, about something low level. Thats
just absurd. The language itself, Prolog, is extremly

highlevel, and I am using Python in a very highlevel
fashion. There is nothing C-ish in my code. You
are just spamming the ever same nonsense.

Mostowski Collapse

unread,
Sep 20, 2021, 2:02:20 PM9/20/21
to
If you don't know the index of the element, you
need first to find the element which is O(n).
And then removing it is O(n) again.

See also here:

> Python's [] is implemented as an array, not a linked list.
> Although resizing is O(n), appending to it is amortized O(1),
> because resizes happen very rarely.
https://stackoverflow.com/a/5932364/502187

Chris Angelico

unread,
Sep 20, 2021, 2:09:52 PM9/20/21
to
How about, instead: Use a Python list, and instead of removing
individual items one by one, filter out the ones you don't want, using
a list comprehension? That would be O(n) to completely remove all the
ones you don't want, instead of O(n) for each individual removal.

Also, have you actually benchmarked a version that uses Python's
lists, or are you simply assuming that the removals will be slow?
Implementing your own singly-linked list was clearly suboptimal, but
have you tried writing simpler code and seeing if it is also faster?

ChrisA

Mostowski Collapse

unread,
Sep 20, 2021, 2:28:12 PM9/20/21
to
Its a good idea to use list comprehension. But its
an incomplete solution.

I don't think the single list is suboptimal, it has complexity
O(n). How would you solve the adjust_mark() problem with
list comprehension?

Also I am not using anything low-level. I don't use a
module unsafe or some such. I even don't know whether
Python has a module unsafe, but other high level

languages do it by a module unsafe. There are now
3 claims by Chris Angelico that are completely baseless
and just polute this thread:

- Baseless Claim 1: Need to optimize gc(), but
it takes only 2-3% of runtime. Why would I optimize gc().

- Baseless Claim 2: My single list is subobtimal,
but it is O(n), the same complexity as list comprehension,
but there is no list comprehension solution around
that also solves the adjust_mark() issue.

- Baseless Claim 3: My code is low level. But nothing
in the code is low level, its all programmed in proper
Python high level. List comprehension, that doesn't
solve the problem is not more high level than single
linked lists. Its just a failed attempt at a solution.

The adjust_mark() is also explained here. See section
"Sweeping the Stack" (Page 22). The paper does it slightly
different than Dogelog Runtime. Dogelog Runtime does it:

/* Dogelog Runtime */
adjust_mark() /* which is the SICStus sweep_stack() */
sweep_stack() /* which includes the SICStus compact_heap(), so to speak */

SICStus has sweep_stack() without compact_heap() first.
The paper shows C-code. But this doesn't imply that
I want C-code. I am only using it as an algorithmic

template, not as a verbatim code template.

Mostowski Collapse schrieb am Montag, 20. September 2021 um 14:22:05 UTC+2:
> Garbage Collection for Prolog Based on WAM
> January 1986
> Karen Appleby, Mats Carlsson, Seif Haridi, Dan Sahlin
> https://www.researchgate.net/publication/279463524

Mark Lawrence

unread,
Sep 20, 2021, 2:36:52 PM9/20/21
to
On Monday, September 20, 2021 at 1:52:18 PM UTC+1, Mostowski Collapse wrote:
> Now I am expecting somebody telling me I should not
> program Java style in Python. So I wonder what would have
> happened if I would tell you I am FORTRAN programmer.
>

http://dirtsimple.org/2004/12/python-is-not-java.html
http://dirtsimple.org/2004/12/java-is-not-python-either.html

Mostowski Collapse

unread,
Sep 20, 2021, 2:39:43 PM9/20/21
to
A good picture in the paper is on Page 25. The picture
as 3 diagrams. The following diagrams correspond
to Dogelog runtime code:

2. Diagram: Updating the Choice Points, this my adjust_mark()
3. Diagram: Sliding the Trail, this is my sweep_trail()

The above solution is linked list based. If you can
provide an array based solution, using list comprehension,
why not? But is it possible? And how?

I am refering to this paper:

Mostowski Collapse schrieb am Montag, 20. September 2021 um 14:22:05 UTC+2:
> Garbage Collection for Prolog Based on WAM
> January 1986
> Karen Appleby, Mats Carlsson, Seif Haridi, Dan Sahlin
> https://www.researchgate.net/publication/279463524

P.S.: I know how it could be done with list comprehension,
in that you do a kind of double comprehension, or two separate
comprehensions with some variable remembering the last

non-marked trail. This would require to traverse the choice
points from the bottom, in parallel to the trail. But it would
require to replace the choice points by another datastructure,

because the choice points also point only from top to bottom,
and there is no order from bottom to top. But even if you
manage to implement this circus act, it still begs the

question what you gain? Since garbage collection only
takes 2-3% of execution time?

Mostowski Collapse

unread,
Sep 20, 2021, 2:48:51 PM9/20/21
to
The double comprehension, might even spare some double
work. It can happen that two different adjust_mark() calls
for two different choice points, run over the same

unmarked trail segment. I did not yet measure how often
this happened. Initially, the early Dogelog Runtime versions,
every clause had at least one variable for the continuation,

and therefore this double work didn't happen. But release 0.9.6
eliminated this variable. This makes the trail also smaller.
But now it becomes possible that adjust_mark() slides

along longer unmarked trail segments, and different adjust_mark()
might even have such segments in common now.

Peter J. Holzer

unread,
Sep 20, 2021, 3:51:59 PM9/20/21
to
On 2021-09-20 05:08:55 -0700, Mostowski Collapse wrote:
> You have to copy C1,..,Cm one position down. On the other
> hand, when scanning the single list, removing the
> element is just pointer swizzling.

That's the problem. You think that pointer swizzling is fast because you
are used to languages where this is compiled into a few machine
instructions. (Even there it depends a lot on locality: A random memory
access can take hundreds of clock cycles.)

In Python something like «a.next = b.next» normally does a dict lookup
on both objects, plus it also has to check for the existence of special
methods on both objects. You can probably copy quite a few contiguous
bytes while this is happening.

Here is a simple demo:

------------------------------------------------------------------------
#!/usr/bin/python3

import time

n = 10000

def build_linked_list(n):
head = [None, None]
tail = head
for i in range(n):
tail[1] = [i, None]
tail = tail[1]
return head

def empty_list(ls):
while ls[1]:
ls[1] = ls[1][1]

t0 = time.monotonic()
ll = build_linked_list(n)
t1 = time.monotonic()
empty_list(ll)
t2 = time.monotonic()
print(n, t1 - t0, t2 - t1)
------------------------------------------------------------------------
#!/usr/bin/python3

import time

n = 10000

def build_list(n):
return list(range(n))

def empty_list(ls):
while ls:
ls.pop(0)

t0 = time.monotonic()
ll = build_list(n)
t1 = time.monotonic()
empty_list(ll)
t2 = time.monotonic()
print(n, t1 - t0, t2 - t1)
------------------------------------------------------------------------

Both scripts create a list of n integers, then repeatedly pop off the
first element until the list is empty. The first uses a linked list
(where each element is a Python list - I guess this is faster than an
object although I haven't timed it) the second a python list. Popping
off the first element is the worst case for a python list since it is
implemented as an array of pointers and n-1 pointers have to be copied
for each operation and the best case for a linked list.

Still, on my laptop with CPython 3.8, the builtin list is faster for
lists of less than 100 elements, they are about on par between 100 and
1000 elements and only for longer lists does the linked list become
faster.

With Pypy 6.0 (yes, I know it's old), the linked list seems to be always
a bit faster although the difference is very small for lists shorter
than 100 elements - GraalVM is probably closer to PyPy than to CPython
in its performance characteristics, but the point is that the dynamic
nature of Python makes some seemingly cheap operations much more costly
than one would expect - a good optimizing compiler may be able to detect
that all that is not needed in a specific case and produce
straightforward code. But unlike for JavaScript (which has a very
similar problem) no company has spent millions of dollars on an
optimizing JIT compiler for Python yet.
signature.asc

Mostowski Collapse

unread,
Sep 20, 2021, 5:53:32 PM9/20/21
to
ls.pop() is probably also, not only attribute access. Just check
out the figures here, its not pop() but append():

>>> %%timeit
out = []
for x in range(10**6):
out.append(x**2)
...
1 loops, best of 3: 510 ms per loop

>>> %%timeit
out = [];append=out.append
for x in range(10**6):
append(x**2)
...
1 loops, best of 3: 467 ms per loop

https://stackoverflow.com/a/30097520/502187

Mostowski Collapse

unread,
Sep 20, 2021, 6:12:02 PM9/20/21
to
Woa! PyPy is faster! Faster than GraalVM. Now I get
for my usual benchmark, figures in milliseconds:

Standard GraalVM PyPy
170'996 28'523 9'755

Not bad. Only like 2x times slower than node.exe.
Now imaging I can realize the ast.parse() and

compile() idea as well! (Made an experiment
today, testing some corner with evaluation)

BTW: Here the full results. Was using:
pypy3.7-v7.3.5-win64\pypy3.exe

Test Standard GraalVM PyPy
nrev 20'389 3'611 1'254
crypt 17'103 3'248 960
deriv 20'808 3'111 1'250
poly 17'518 3'045 1'149
qsort 20'191 3'243 1'127
tictac 13'761 2'469 783
queens 17'691 3'242 1'024
query 6'161 1'155 313
mtak 17'209 2'300 907
perfect 20'165 3'099 988
calc N/A N/A N/A
Total 170'996 28'523 9'755

Mostowski Collapse

unread,
Sep 20, 2021, 7:06:02 PM9/20/21
to
The Java vs Python articles are utterly outdated.
For example they think a difference between Java
and Python is that Java is interface centric.

The articles are from 2004. But the ABC PEP is from 2007:

https://www.python.org/dev/peps/pep-3119/

https://www.python.org/dev/peps/pep-3141/

You find also write-ups like:

https://realpython.com/python-interface/

Hope this helps!

Greg Ewing

unread,
Sep 20, 2021, 7:34:38 PM9/20/21
to
On 21/09/21 12:16 am, Mostowski Collapse wrote:
> This is needed to adjust the choice points.
> If you have an array instead of a linked
> listed as I have now, you would need
> to adjust array indexes instead pointers
> into linked list elements.

All right, so you don't just have a list, you have a more complicated
data structure. The way you're doing it is probably reasonable, and
good enough given it's not a significant part of the running time.

--
Greg

Mostowski Collapse

unread,
Sep 23, 2021, 12:18:30 PM9/23/21
to
A nice project could now be to redo this one,
see how the prover would perform:

FLiP, a Logical Framework in Python
http://staff.washington.edu/jon/flip/www/index.html

The prover is listed here:

Proof checkers - Joseph Vidal-Rosset
https://www.vidal-rosset.net/proof_checkers.html

Ha Ha, the prover was just right in front of my nose
all the time. But before venturing into such a quest,
need to add occurs check to the Dogelog runtime.

I had some very good dynamic optimization for
occurs check in Jekejeke Prolog, but for Dogelog
runtime everything is new, need to figure out what

optimizations could be applied there. There is a ticket
for occurs check already on GitHub:

Bring occurs check to Dogelog runtime #143
https://github.com/jburse/dogelog-moon/issues/143

Mostowski Collapse

unread,
Sep 23, 2021, 12:36:14 PM9/23/21
to
That I get censored on Python pipermail, is possibly an
out burst of taking Python too literal, like here:

Monty Python - She's a witch!
https://www.youtube.com/watch?v=zrzMhU_4m-g

But we can turn this into a FLiP, a Logical Framework in Python exercise:

"There are ways of telling whether she's a witch."
"What do you do with witches?" "Burn them!"
Ax.(Witch(x) -> Burn(x)) (1) Given
"Why do witches burn?" "'Cause they're made of wood!"
Ax.(Wood(x) -> Witch(x)) (2) Given
"How do we tell if she's made of wood?" "Does wood sink in water?" "It floats!"
Ax.(Floats(x) -> Wood(x)) (3) Given
"What also floats in water?" "A duck!"
Floats(duck) (4) Given
"Exactly! So, logically ..."
"If she weights the same as a duck, she's made of wood!"
Ax.Ay.((Floats(x) & (weight(x) = weight(y))) -> Floats(y)) (5) Given
"We shall use my largest scales. ... Remove the supports!"
weight(duck) = weight(girl) (6) Given
Ay.((Floats(duck) & (weight(duck) = weight(y))) -> Floats(y)) (7) A-Elimination (5)
(Floats(duck) & (weight(duck) = weight(girl))) -> Floats(girl) (8) A-Elimination (7)
Floats(duck) & (weight(duck) = weight(girl)) (9) And-Introduction (4) (6)
Floats(girl) (10) Implication-Elimination (Modus Ponens) (8) (9)
Floats(girl) -> Wood(girl) (11) A-Elimination (3)
Wood(girl) (12) Implication-Elimination (Modus Ponens) (11) (10)
"A witch! A witch!"
Wood(girl) -> Witch(girl) (13) A-Elimination (2)
Witch(girl) (14) Implication-Elimination (Modus Ponens) (13) (12)
"Burn her! Burn!"
Witch(girl) -> Burn(girl) (15) A-Elimination (1)
Burn(girl) (16) Implication-Elimination (Modus Ponens) (15) (14)
http://staff.washington.edu/jon/flip/www/witch.html

Mostowski Collapse

unread,
Nov 7, 2021, 7:25:43 PM11/7/21
to
Dogelogs new signature sound:

Led Zeppelin Immigrant song (Racing Beat remix)
https://www.youtube.com/watch?v=oryBBkmIVCc

LoL
0 new messages