I have a number of very large text files which I need to process, the largest being about 60GB.
Each line has 54 characters in seven fields and I want to remove the last three characters from each of the first three fields – which should reduce the file size by about 20%.
I am brand new to Python and have a code which will do what I want to do at about 3.4 GB per hour, but to be a worthwhile exercise I really need to be getting at least 10 GB/hr – is there any way to speed this up? This code doesn’t come close to challenging my processor, so I am making an uneducated guess that it is limited by the read and write speed to the internal hard drive?
def ProcessLargeTextFile(): r = open("filepath", "r") w = open("filepath", "w") l = r.readline() while l: x = l.split(' ') y = l.split(' ') z = l.split(' ') w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])) l = r.readline() r.close() w.close()
Any help would be really appreciated. I am using the IDLE Python GUI on Windows 7 and have 16GB of memory – perhaps a different OS would be more efficient?.
Edit: Here is an extract of the file to be processed.
70700.642014 31207.277115 -0.054123 -1585 255 255 255 70512.301468 31227.990799 -0.255600 -1655 155 158 158 70515.727097 31223.828659 -0.066727 -1734 191 187 180 70566.756699 31217.065598 -0.205673 -1727 254 255 255 70566.695938 31218.030807 -0.047928 -1689 249 251 249 70536.117874 31227.837662 -0.033096 -1548 251 252 252 70536.773270 31212.970322 -0.115891 -1434 155 158 163 70533.530777 31215.270828 -0.154770 -1550 148 152 156 70533.555923 31215.341599 -0.138809 -1480 150 154 158
It’s more idiomatic to write your code like this
def ProcessLargeTextFile(): with open("filepath", "r") as r, open("outfilepath", "w") as w: for line in r: x, y, z = line.split(' ')[:3] w.write(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
The main saving here is to just do the
split once, but if the CPU is not being taxed, this is likely to make very little difference
It may help to save up a few thousand lines at a time and write them in one hit to reduce thrashing of your harddrive. A million lines is only 54MB of RAM!
def ProcessLargeTextFile(): bunchsize = 1000000 # Experiment with different sizes bunch =  with open("filepath", "r") as r, open("outfilepath", "w") as w: for line in r: x, y, z = line.split(' ')[:3] bunch.append(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])) if len(bunch) == bunchsize: w.writelines(bunch) bunch =  w.writelines(bunch)
suggested by @Janne, an alternative way to generate the lines
def ProcessLargeTextFile(): bunchsize = 1000000 # Experiment with different sizes bunch =  with open("filepath", "r") as r, open("outfilepath", "w") as w: for line in r: x, y, z, rest = line.split(' ', 3) bunch.append(' '.join((x[:-3], y[:-3], z[:-3], rest))) if len(bunch) == bunchsize: w.writelines(bunch) bunch =  w.writelines(bunch)
Measure! You got quite some useful hints how to improve your python code and I agree with them. But you should first figure out, what your real problem is. My first steps to find your bottleneck would be:
- Remove any processing from your code. Just read and write the data and measure the speed. If just reading and writing the files is too slow, it’s not a problem of your code.
- If just reading and writing is already slow, try to use multiple disks. You are reading and writing at the same time. On the same disc? If yes, try to use different discs and try again.
- Some async io library (Twisted?) might help too.
If you figured out the exact problem, ask again for optimizations of that problem.
As you don’t seem to be limited by CPU, but rather by I/O, have you tried with some variations on the third parameter of
Indeed, this third parameter can be used to give the buffer size to be used for file operations!
open( "filepath", "r", 16777216 ) will use 16 MB buffers when reading from the file. It must help.
Use the same for the output file, and measure/compare with identical file for the rest.
Note: This is the same kind of optimization suggested by other, but you can gain it here for free, without changing your code, without having to buffer yourself.
I’ll add this answer to explain why buffering makes sense and also offer one more solution
You are getting breathtakingly bad performance. This article Is it possible to speed-up python IO? shows that a 10 gb read should take in the neighborhood of 3 minutes. Sequential write is the same speed. So you’re missing a factor of 30 and your performance target is still 10 times slower than what ought to be possible.
Almost certainly this kind of disparity lies in the number of head seeks the disk is doing. A head seek takes milliseconds. A single seek corresponds to several megabytes of sequential read-write. Enormously expensive. Copy operations on the same disk require seeking between input and output. As has been stated, one way to reduce seeks is to buffer in such a way that many megabytes are read before writing to disk and vice versa. If you can convince the python io system to do this, great. Otherwise you can read and process lines into a string array and then write after perhaps 50 mb of output are ready. This size means a seek will induce a <10% performance hit with respect to the data transfer itself.
The other very simple way to eliminate seeks between input and output files altogether is to use a machine with two physical disks and fully separate io channels for each. Input from one. Output to other. If you’re doing lots of big file transformations, it’s good to have a machine with this feature.
ProcessLargeTextFile(): r = open("filepath", "r") w = open("filepath", "w") l = r.readline() while l:
As has been suggested already, you may want to use a for loop to make this more optimal.
x = l.split(' ') y = l.split(' ') z = l.split(' ')
You are performing a split operation 3 times here, depending on the size of each line this will have a detremental impact on performance. You should split once and assign x,y,z to the entries in the array that comes back.
Each line you are reading, you are writing immediately to the file, which is very I/O intensive. You should consider buffering your output to memory and pushing to the disk periodically. Something like this:
BUFFER_SIZE_LINES = 1024 # Maximum number of lines to buffer in memory def ProcessLargeTextFile(): r = open("filepath", "r") w = open("filepath", "w") buf = "" bufLines = 0 for lineIn in r: x, y, z = lineIn.split(' ')[:3] lineOut = lineIn.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]) bufLines+=1 if bufLines >= BUFFER_SIZE: # Flush buffer to disk w.write(buf) buf = "" bufLines=1 buf += lineOut + "n" # Flush remaining buffer to disk w.write(buf) buf.close() r.close() w.close()
You can tweak BUFFER_SIZE to determine an optimal balance between memory usage and speed.
Heres the code for loading text files of any size without causing memory issues. It support gigabytes sized files. It will run smoothly on any kind of machine, you just need to configure CHUNK_SIZE based on your system RAM. More the CHUNK_SIZE, more will be the data read at a time
download the file data_loading_utils.py and import it into your code
import data_loading_utils.py.py file_name = 'file_name.ext' CHUNK_SIZE = 1000000 def process_lines(line, eof, file_name): # check if end of file reached if not eof: # process data, data is one single line of the file else: # end of file reached data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=process_lines)
process_lines method is the callback function. It will be called for all the lines, with parameter line representing one single line of the file at a time.
You can configure the variable CHUNK_SIZE depending on your machine hardware configurations.
Your code is rather un-idiomatic and makes far more function calls than needed. A simpler version is:
ProcessLargeTextFile(): with open("filepath") as r, open("output") as w: for line in r: fields = line.split(' ') fields[0:2] = [fields[:-3], fields[:-3], fields[:-3]] w.write(' '.join(fields))
and I don’t know of a modern filesystem that is slower than Windows. Since it appears you are using these huge data files as databases, have you considered using a real database?
Finally, if you are just interested in reducing file size, have you considered compressing / zipping the files?
Those seem like very large files… Why are they so large? What processing are you doing per line? Why not use a database with some map reduce calls (if appropriate) or simple operations of the data? The point of a database is to abstract the handling and management large amounts of data that can’t all fit in memory.
Create a database
conn = sqlite3.connect('pts.db') c = conn.cursor()
Creates a table
c.execute('''CREATE TABLE ptsdata (filename, line, x, y, z''')
Then use one of the algorithms above to insert all the lines and points in the database by calling
c.execute("INSERT INTO ptsdata VALUES (filename, lineNumber, x, y, z)")
Now how you use it depends on what you want to do. For example to work with all the points in a file by doing a query
c.execute("SELECT lineNumber, x, y, z FROM ptsdata WHERE filename=file.txt ORDER BY lineNumber ASC")
n lines at a time from this query with
I’m sure there is a better wrapper for the sql statements somewhere, but you get the idea.