9

I am ordering a huge pile landsat scenes from the USGS, which come as tar.gz archives. I am writing a simple python script to unpack them. Each archive contains 15 tiff images from 60-120 mb in size, totalling just over 2 gb. I can easily extract an entire archive with the following code:

import tarfile
fileName = "LT50250232011160-SC20140922132408.tar.gz"
tfile = tarfile.open(fileName, 'r:gz')
tfile.extractall("newfolder/")

I only actually need 6 of those 15 tiffs, identified as "bands" in the title. These are some of the larger files, so together they account for about half the data. So, I thought I could speed this process up by modifying the code as follows:

fileName = "LT50250232011160-SC20140922132408.tar.gz"
tfile = tarfile.open(fileName, 'r:gz')
membersList = tfile.getmembers()
namesList = tfile.getnames()
bandsList = [x for x, y in zip(membersList, namesList) if "band" in y]
print("extracting...")
tfile.extractall("newfolder/",members=bandsList)

However, adding a timer to both scripts reveals no significant efficiency gain of the second script (on my system, both run in about a minute on a single scene). While the extraction is somewhat faster, it seems like that gain is offset by the time it takes to figure out which files need to be extracted int he first place.

The question is, is this tradeoff inherant in what I am doing, or just the result of my code being inefficient? I'm relatively new to python and only discovered tarfile today, so it wouldn't surprise me if the latter were true, but I haven't been able to find any recommendations for efficient extraction of only part of an archive.

Thanks!

Joe
  • 3,831
  • 4
  • 28
  • 44

2 Answers2

11

You can do that more efficiently, by opening the tarfile as a stream.(https://docs.python.org/2/library/tarfile.html#tarfile.open)

mkdir tartest
cd tartest/
dd if=/dev/urandom of=file1 count=100 bs=1M
dd if=/dev/urandom of=file2 count=100 bs=1M
dd if=/dev/urandom of=file3 count=100 bs=1M
dd if=/dev/urandom of=file4 count=100 bs=1M
dd if=/dev/urandom of=file5 count=100 bs=1M
cd ..
tar czvf test.tgz tartest

Now read like this:

import tarfile
fileName = "test.tgz"
tfile = tarfile.open(fileName, 'r|gz')
for t in tfile:
    if "file3" in t.name: 
        f = tfile.extractfile(t)
        if f:
            print(len(f.read()))

Note the | in the open command. We only read the file3.

$ time python test.py

104857600

real    0m1.201s
user    0m0.820s
sys     0m0.377s

If I change the r|gz back to the r:gz I get:

$ time python test.py 
104857600

real    0m7.033s
user    0m6.293s
sys     0m0.730s

Roughly 5 times faster (since we have 5 equally sized files). It is so because the standard way of opening allows seeking backwards; it can only do so in a compressed tarfile by extracting (I do not know the exact reason for that). If you open as a stream, you cannot seek randomly any more but if you read sequentially, which is possible in your case, it is much faster. However, you cannot to the getnames anymore beforehand. But that is not necessary in this case.

cronos
  • 2,268
  • 16
  • 17
  • 1
    i get the same speed. perhaps `tarfile` has been improved for the non-stream case... – user3204459 Jun 24 '18 at 14:38
  • thanks. This was *very* helpful. using `'r|gz'` was over one order of magnitude faster. Therefore, I can easily live without the ability to go back in the tar stream. python 2.7.18 (tarfile revision 85213) – user8162 Sep 16 '21 at 17:37
10

The problem is that a tar file does not have a central file list, but stores files sequentially with a header before each file. The tar file is then compressed via gzip to give you tar.gz. With a tar file, if you don't want to extract a certain file, you simply skip the next header->size bytes in an archive and then read the next header. If the archive is additionally compressed, you'll still have to skip that many bytes, only not within the archive file but within the decompressed data stream - which for some compression formats works, but for others requires you to decompress everything in between.

gzip belongs to the latter class of compression schemes. So while you save some time by not writing the undesired files to the disk, your code still decompresses them. You might be able to overcome that problem by overriding the _Stream class for non-gzip archives, but for your gz files, there is nothing you can do about it.

Community
  • 1
  • 1
Phillip
  • 13,448
  • 29
  • 41
  • 1
    You still save on the cost of I/O for the extracted files though. – o11c Sep 26 '14 at 21:28
  • Interesting. That strikes me as somewhat of a limitation of the gzip format, but I suppose it makes the compression more efficient. Thanks for the info! – Joe Sep 29 '14 at 13:36
  • No, it doesn't. Formats that combine archive and compression are also able to share e.g. compession tables between all contained files, and therefore are no less efficient. The reason why `tar` does not do that is that it adheres to [the unix philosophy](http://en.wikipedia.org/wiki/Unix_philosophy#Program_Design_in_the_UNIX_Environment): Archiving and compression are two different problems and should therefore be tackled by separate programs. Your question shows one of the disadvantages of that ansatz. – Phillip Sep 29 '14 at 13:50