tags:

views:

159

answers:

3

I need fastest way to convert files from latin1 to utf-8 in python. The files are large ~ 2G. ( I am moving DB data ). So far I have

import codecs
infile = codecs.open(tmpfile, 'r', encoding='latin1')
outfile = codecs.open(tmpfile1, 'w', encoding='utf-8')
for line in infile:
     outfile.write(line)
infile.close()
outfile.close()

but it is still slow. The conversion takes one fourth of the whole migration time.

I could also use a linux command line utility if it is faster than native python code.

+5  A: 

I would go with iconv and a system call.

RC
+2  A: 

You could use blocks larger than one line, and do binary I/O -- each might speed thinks up a bit (though on Linux binary I/O won't, as it's identical to text I/O):

 BLOCKSIZE = 1024*1024
 with open(tmpfile, 'rb') as inf:
   with open(tmpfile, 'wb') as ouf:
     while True:
       data = inf.read(BLOCKSIZE)
       if not data: break
       converted = data.decode('latin1').encode('utf-8')
       ouf.write(converted)

The byte-by-byte parsing implied in by-line reading, line-end conversion (not on Linux;-), and codecs.open-style encoding-decoding, should be part of what's slowing you down. This approach is also portable (like yours is), since control-characters such as \n need no translation among these codecs anyway (in any OS).

This only works for input codecs that have no multibyte characters, but `latin1' is one of those (it does not matter whether the output codec has such characters or not).

Try different block sizes to find the sweet spot performance-wise, depending on your disk, filesystem and available RAM.

Edit: changed code per @John's comment, and clarified a conditon as per @gnibbler's.

Alex Martelli
line[-2].swap('encode', 'decode')
John Machin
and you don't need to import codecs
John Machin
This is only safe in this case since `latin1` has no multibyte characters.
gnibbler
@John, right -- editing to fix. @gnibbler, right -- editing to point this out.
Alex Martelli
For avoidance of any doubt caused by any ambiguity in interpreting "multibyte": It will work for any input encoding with a *fixed* number of bytes per code point -- thus UTF-32 is OK, and UTF-16 is OK if and only if the characters are limited to the BMP (if your buffer ends with a high surrogate, splat).
John Machin
This is the fastest solution. I did a test run of all possible solutions, here are the results1. Using codecs with loop0:03:14.3432202. Using codecs without loop0:02:34.7306633. Using native with loop0:01:21.5175204. Using native no loop0:00:18.2628995. Using iconv0:00:26.2741306. stackoveflow solution0:00:08.001735The tests were done on 1.4G text file. Test number 2 was the worst. It brought my i7 12G machine to a near halt (used lots of memory). Test number 4 was pretty fast but also used much memory. Iconv is faster when running it on its own, not from python.
xsaero00
@xsaero00: please explain "native"
John Machin
native means simplest way possible without any imports.outfile.write(infile.read().decode('latin1').encode('utf-8'))
xsaero00
+2  A: 

If you are desperate to do it in Python (or any other language), at least do the I/O in bigger chunks than lines, and avoid the codecs overhead.

infile = open(tmpfile, 'rb')
outfile = open(tmpfile1, 'wb')
BLOCKSIZE = 65536 # experiment with size
while True:
    block = infile.read(BLOCKSIZE)
    if not block: break
    outfile.write(block.decode('latin1').encode('utf8'))
infile.close()
outfile.close()

Otherwise, go with iconv ... I haven't look under the hood but if it doesn't special-case latin1 input I'd be surprised :-)

John Machin