From 9dee68f7253005d2555b86e11d1eea849f226ee4 Mon Sep 17 00:00:00 2001 From: Karim Bahgat Date: Tue, 16 Aug 2016 00:53:16 +0200 Subject: [PATCH 1/3] Unpack all coordinate points in one go, 5-9x speedup When unpacking the list of coordinate points, each point pair is unpacked one at a time, making it very slow for long lists of points. So a fairly simple change to read all points at once, which btw is in line with the existing convention elsewhere, leads to 5-9x speedup when reading shapes, with bigger gains for files with more complex shapes (more points). --- shapefile.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/shapefile.py b/shapefile.py index d23f393f..3c94d6a7 100644 --- a/shapefile.py +++ b/shapefile.py @@ -346,7 +346,8 @@ def __shape(self): record.partTypes = _Array('i', unpack("<%si" % nParts, f.read(nParts * 4))) # Read points - produces a list of [x,y] values if nPoints: - record.points = [_Array('d', unpack("<2d", f.read(16))) for p in range(nPoints)] + flat = unpack("<%sd" % (2 * nPoints), f.read(16*nPoints)) + record.points = list(izip(*(iter(flat),) * 2)) # Read z extremes and values if shapeType in (13,15,18,31): (zmin, zmax) = unpack("<2d", f.read(16)) From c1b54f3b2d01e386fad7374a7aaea810efd33566 Mon Sep 17 00:00:00 2001 From: Karim Bahgat Date: Tue, 16 Aug 2016 01:59:03 +0200 Subject: [PATCH 2/3] Unpack all shx index offsets at once Implemented basic version from #52 , but without numpy and memoryview to keep it simple. Not sure if any practical/noticable speedup, since reading the offsets is only a one-time issue and very little data involved. More out of principle. --- shapefile.py | 10 ++++++---- shapefile.pyc | Bin 0 -> 41262 bytes 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 shapefile.pyc diff --git a/shapefile.py b/shapefile.py index 3c94d6a7..cf2ca6c6 100644 --- a/shapefile.py +++ b/shapefile.py @@ -390,10 +390,12 @@ def __shapeIndex(self, i=None): numRecords = shxRecordLength // 8 # Jump to the first record. shx.seek(100) - for r in range(numRecords): - # Offsets are 16-bit words just like the file length - self._offsets.append(unpack(">i", shx.read(4))[0] * 2) - shx.seek(shx.tell() + 4) + shxRecords = _Array('i') + # Each offset consists of two nrs, only the first one matters + shxRecords.fromfile(shx, 2 * numRecords) + if sys.byteorder != 'big': + shxRecords.byteswap() + self._offsets = [2 * el for el in shxRecords[::2]] if not i == None: return self._offsets[i] diff --git a/shapefile.pyc b/shapefile.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c074f53e474009083dabb541d153771b3c0ca090 GIT binary patch literal 41262 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 O{NCPpt*gCf)&B>EbDNO> literal 0 HcmV?d00001 From 75d28ae9588c19e6c283932d6c0ef792d7e5eab5 Mon Sep 17 00:00:00 2001 From: Karim Bahgat Date: Tue, 16 Aug 2016 02:51:09 +0200 Subject: [PATCH 3/3] Precompile record format, and batch load all records Since each row/record has the same format every time, instead of a regular unpack, precompile the format using struct.Struct to unpack faster. Leads to roughly x1.33 speedup. In addition, when users arent worried about memory (ie the "records" method), we can exploit this by reading all records to memory at once. Leads to 15-20x speedup. --- shapefile.py | 17 ++++++++--------- shapefile.pyc | Bin 41262 -> 41616 bytes 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/shapefile.py b/shapefile.py index cf2ca6c6..2158da0f 100644 --- a/shapefile.py +++ b/shapefile.py @@ -10,7 +10,7 @@ __version__ = "1.2.3" -from struct import pack, unpack, calcsize, error +from struct import pack, unpack, calcsize, error, Struct import os import sys import time @@ -472,6 +472,8 @@ def __dbfHeader(self): if terminator != b("\r"): raise ShapefileException("Shapefile dbf header lacks expected terminator. (likely corrupt?)") self.fields.insert(0, ('DeletionFlag', 'C', 1, 0)) + fmt,fmtSize = self.__recordFmt() + self.__recStruct = Struct(fmt) def __recordFmt(self): """Calculates the size of a .shp geometry record.""" @@ -484,8 +486,7 @@ def __recordFmt(self): def __record(self): """Reads and returns a dbf record row as a list of values.""" f = self.__getFileObj(self.dbf) - recFmt = self.__recordFmt() - recordContents = unpack(recFmt[0], f.read(recFmt[1])) + recordContents = self.__recStruct.unpack(f.read(self.__recStruct.size)) if recordContents[0] != b(' '): # deleted record return None @@ -538,7 +539,7 @@ def record(self, i=0): if not self.numRecords: self.__dbfHeader() i = self.__restrictIndex(i) - recSize = self.__recordFmt()[1] + recSize = self.__recStruct.size f.seek(0) f.seek(self.__dbfHeaderLength() + (i * recSize)) return self.__record() @@ -547,13 +548,11 @@ def records(self): """Returns all records in a dbf file.""" if not self.numRecords: self.__dbfHeader() - records = [] f = self.__getFileObj(self.dbf) f.seek(self.__dbfHeaderLength()) - for i in range(self.numRecords): - r = self.__record() - if r: - records.append(r) + flat = unpack(self.__recStruct.format * self.numRecords, f.read(self.__recStruct.size * self.numRecords)) + rowlen = len(self.fields) - 1 + records = list(izip(*(iter(flat),) * rowlen)) return records def iterRecords(self): diff --git a/shapefile.pyc b/shapefile.pyc index c074f53e474009083dabb541d153771b3c0ca090..74e2b4391fa65d5a2d713cd4c2254fef04303ef3 100644 GIT binary patch delta 7018 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 DivA!c delta 6681 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