Mailing List Archive

1 2 3 4  View All
Re: ANN: Dogelog Runtime, Prolog to the Moon (2021) [ In reply to ]
On Tue, Sep 21, 2021 at 3:51 AM Mostowski Collapse <janburse@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
--
https://mail.python.org/mailman/listinfo/python-list
Re: ANN: Dogelog Runtime, Prolog to the Moon (2021) [ In reply to ]
On Tue, Sep 21, 2021 at 3:58 AM Mostowski Collapse <bursejan@gmail.com> 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 ]
>
> 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).
>

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
--
https://mail.python.org/mailman/listinfo/python-list
Re: ANN: Dogelog Runtime, Prolog to the Moon (2021) [ In reply 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.

hp

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

1 2 3 4  View All