Performance of lists vs. list comprehensions
Alf P. Steinbach
alfps at start.no
Wed Jan 20 03:21:30 EST 2010
* Steven D'Aprano:
> On Wed, 20 Jan 2010 05:25:22 +0100, Alf P. Steinbach wrote:
>
>> * Steven D'Aprano:
>>> On Tue, 19 Jan 2010 16:20:42 -0500, Gerald Britton wrote:
>>>
>>>> That's surprising. I wouldn't implement it that way at all. I'd use a
>>>> dynamically-expanding buffer as I suggested. That way you get a
>>>> single pass and don't have to calculate anything before you begin. In
>>>> the best case, you'd use half the memory (the result just fits in the
>>>> buffer after its last expansion and no saved tuple). In the worst
>>>> case, the memory use is about the same (you just expanded the buffer
>>>> using a 2x expansion rule then you hit the last item).
>>> In the worst case, you waste 50% of the memory allocated.
>> Yes. That is a good argument for not doing the expanding buffer thing.
>> But such buffers may be generally present anyway, resulting from
>> optimization of "+".
>
>
> As near as I can determine, the CPython optimization you are referring to
> doesn't use the "double the buffer when needed" technique. It operates on
> a completely different strategy. As near as I can tell (as a non-C
> speaker), it re-sizes the string in place to the size actually needed,
> thus reducing the amount of copying needed.
>
> The optimization patch is here:
>
> http://bugs.python.org/issue980695
>
> and some history (including Guido's opposition to the patch) here:
>
> http://mail.python.org/pipermail/python-dev/2004-August/046686.html
>
> Nevertheless, the patch is now part of CPython since 2.4, but must be
> considered an implementation-specific optimization. It doesn't apply to
> Jython, and likely other implementations.
Provided that the CPython code is that code then you're right, it only calls
PyObject_REALLOC, depending on that operation having (amortized) constant time.
And PyObject_REALLOC can in principle achieve that by relying on paging, e.g.
using the OS' virtual memory allocator, instead of doubling and copying.
However, I'm baffled by the code I find via Google Code search; there
PyObject_REALLOC simply calls malloc and copies, which if that were the code
used in CPython 2.4 and greater, and if the '+' code is the code that you refer
to above, would produce O(n^2) time for the first '+' example.
>> Using CPython 2.6.4 in Windows XP:
> [snip time measurements]
>> Here the linear increase of times indicate that the "+" is being
>> optimized using an expanding buffer for the string.
>
> Be careful of jumping to the conclusion from timings that a certain
> algorithm is used. All you can really tell is that, whatever the
> algorithm is, it has such-and-such big-oh behaviour.
Well, as for intended meaning you're right about that, it needs not be a
doubling buffer.
>> If only the minimal
>> space was allocated each time then one would expect O(n^2) behavior
>> instead of the apparently O(n) above.
>
> I don't follow that reasoning.
The reasoning assumes independent allocations rather than reallocation
(extension of existing allocation).
Then quadratic time for the example follows from sum(range(n)) = (n^2-n)/2 of
the sizes of the data copied.
But with PyObject_REALLOC as essentially constant time also 'join' could take
advantage of this. :-)
Cheers,
- Alf
More information about the Python-list
mailing list