[postgis-devel] Memory Leak (Two Senses)
Mark Cave-Ayland
mark.cave-ayland at siriusit.co.uk
Wed Jan 7 02:50:57 PST 2009
Paul Ramsey wrote:
> The second memory leak. The lwfree_pointarray function is defined in
> lwgeom_api, and it is directly below a comment block that says *this*:
>
> /****************************************************************
> * memory management
> *
> * these only delete the memory associated
> * directly with the structure - NOT the stuff pointing into
> * the original de-serialized info
> *
> ****************************************************************/
>
> Oh! That seems pretty clear. So by changing the function to fully
> clean up after itself, I'm contravening the documented intent.
> However, grep tells me the function is but lightly used in the code
> base, hardly at all, really. Our second memory leak is historical, we
> have no idea why the intent was once this way.
>
> Before I commit the changes to the lwfree_* set to make them finally,
> actually, do deep freeing, can anyone provide the historical context
> around the rules I seem to want to break?
Yes - if you do this it will break liblwgeom use within PostgreSQL :(
Before I was ill over Christmas, I started looking at your complaint
about manipulating MULTI-geoms in liblwgeom, and realised that the
reason the LWGEOMs are deep-copied is because the POINTARRAYs within
LWGEOM *obtained from PostgreSQL* MUST be immutable. Hence if we wish to
alter a LWGEOM, we *MUST* copy it and return the copy.
And why is this? It's because when PostgreSQL passes us a geometry, we
are passed a copy of the geometry allocated by PostgreSQL. But the
LWGEOM deserialization code sets the POINTARRAY pointer directly to the
doubles array within the PostgreSQL geometry - hence the memory within
the POINTARRAY *is not ours to free*. This is why there are lots of
separate lwfree(line->pa)-type calls scattered around liblwgeom to free
the parts we know can be discarded, but otherwise the memory has to be
freed by PostgreSQL.
Since I've been spending a lot of time re-working liblwgeom over the
past few months, I (and I think you also) have separately come to the
conclusion that having this type of memory model is not good. The
principle of least surprise dictates that lwfree()-ing a geometry should
free all of it, and not assume any behaviour on the part of the
environment using the geometry.
Part of the testing I was doing over Christmas involved experimenting
with memcpy() to see what the impact would be of copying the POINTARRAY
out of the PostgreSQL allocated geometry so that the geometries are
separate - and I came up with some very surprising results. I know strk
was keen to optimise the performance of the LWGEOMs, but from my results
I think there has been a case of premature optimisation here.
The attached file testmem.c is used to compare the performance of LWGEOM
construction on an array of 80,000 2D points. The first test is
designed to compare the difference between scanning through the point
array using the existing getPoint2d_p() function (unaligned memcpy
access) and scanning through an aligned array of doubles:
Testing unaligned point access...
x: -2.6378e+08
y: -3.66648e+08
x: 2.13081e+09
y: 8.59704e+08
x: 1.01699e+09
y: -1.04434e+09
x: 4.88672e+08
y: 6.35536e+08
x: -5.43084e+08
y: -6.452e+08
Execution time: 0.26586 s
(Speed: 300910 points per second)
Testing aligned point access...
x: -2.6378e+08
y: -3.66648e+08
x: 2.13081e+09
y: 8.59704e+08
x: 1.01699e+09
y: -1.04434e+09
x: 4.88672e+08
y: 6.35536e+08
x: -5.43084e+08
y: -6.452e+08
Execution time: 0.0176489 s
(Speed: 4.53285e+06 points per second)
Wow. This goes back to your original idea with respect to the speed of
aligned/versus unaligned access and we can see that the aligned version
blows away the unaligned version in terms of access speed. So then the
question to ask is that if we were to memcpy() the unaligned point array
into an aligned double array when constructing the LWGEOM, would this
still be faster than access the unaligned array? And the answer is a
resounding yes:
Testing non-memcpy lwline constructor with point iteration...
Execution time: 1.01137 s
(Speed: 79100.2 points per second)
Testing memcpy lwline constructor with point iteration...
Execution time: 0.368717 s
(Speed: 216969 points per second)
This test shows the time taken to memcpy() the POINTARRAY and also
iterate through all of the points within it. The results clearly show
that if we visit every point in the geometry (which we have to do if we
call serialize to send the LWGEOM back to PostgreSQL anyway), then even
with the initial memcpy() from unaligned to aligned memory, the aligned
version is still nearly 3 times faster than the unaligned version! :O
Now there is a slight degenerate case here: what if we simply want to
access a single point within the geometry? This is demonstrated by this
case here:
Testing non-memcpy lwline constructor with single point access (middle
point)...
Execution time: 0.0055759 s
(Speed: 1.43475e+07 points per second)
Testing memcpy lwline constructor with single point access (middle point)...
Execution time: 29.0237 s
(Speed: 2756.37 points per second)
Ouch. So if we don't visit all points within the memcpy() point array
then the overhead of the memcpy() becomes quite substantial. This then
led me to think about LWGEOM_INSPECTED which is supposed to be for
high-speed access - perhaps we can make use of this? Unfortunately this
makes use of the serialized form which is only something that would be
useful to PostGIS and not other users of liblwgeom. But it still makes
sense to offer a non-memcpy() alternative read-only LWGEOM derivative
for these cases.
So in summary, based upon this work I would suggest the following
recommendations:
- Alter the deserialize routines to memcpy() the unaligned pointarray
into aligned liblwgeom allocated memory
- Swap liblwgeom code over access the arrays directly instead of using
getPoint2d_p()
- Alter the collection manipulation routines so that they no-longer deep
copy LWGEOMs
- Alter the lwfree_* functions so that they free the pointarray copy
Once liblwgeom manages its own memory in this way are then very close to
getting rid of the bad hack that is lwgeom_init_allocators() and
lwgeom_install_default_allocators().
Then just to complete the icing on the cake:
- Remove the LWGEOM_INSPECTED routines (I think given the non-alignment
of these they will also prove to be slower in real life)
- Create a new LWGEOM_INLINE read-only type which works in a similar
manner to existing LWGEOM without memcpy(). We can then use this for
pulling out single unaligned points out of large arrays if required.
I realise that this is quite a long email, but I think that the attached
test show there is a strong case for changing the way in which liblwgeom
which will not only speed up performance for routines which process
geometries, but will also untangle the liblwgeom memory-management from
PostgreSQL (which as Paul notes will make it much easier to detect
memory leaks) :)
ATB,
Mark.
--
Mark Cave-Ayland
Sirius Corporation - The Open Source Experts
http://www.siriusit.co.uk
T: +44 870 608 0063
-------------- next part --------------
A non-text attachment was scrubbed...
Name: testmem.c
Type: text/x-csrc
Size: 3962 bytes
Desc: not available
URL: <http://lists.osgeo.org/pipermail/postgis-devel/attachments/20090107/395343a7/attachment.c>
More information about the postgis-devel
mailing list