Обработка огромного файла (9,1 ГБ) и его обработка быстрее - Python

У меня есть текстовый файл из твитов в формате 9GB в следующем формате:

T      'time and date'
U      'name of user in the form of a URL'
W      Actual tweet

Всего насчитывается 6 000 000 пользователей и более 60 000 000 твитов. Я читаю по 3 строки за раз, используя itertools.izip(), а затем в соответствии с именем записываю его в файл. Но его принятие слишком длинное (26 часов и подсчет). Как это можно сделать быстрее?

Код публикации для полноты,

s='the existing folder which will have all the files'
with open('path to file') as f:
 for line1,line2,line3 in itertools.izip_longest(*[f]*3):
            if(line1!='\n' and line2!='\n' and line3!='\n'):
     line1=line1.split('\t')
     line2=line2.split('\t')
     line3=line3.split('\t')
     if(not(re.search(r'No Post Title',line1[1]))):
         url=urlparse(line3[1].strip('\n')).path.strip('/')

  if(url==''):
   file=open(s+'junk','a')
   file.write(line1[1])
   file.close()
  else:
   file=open(s+url,'a')
   file.write(line1[1])
   file.close()

Моя цель - использовать моделирование темы на небольших текстах (как, например, при запуске lda во всех твитах одного пользователя, что требует отдельного файла для каждого пользователя), но для этого требуется слишком много времени.

UPDATE. Я использовал предложения пользователя S.Lott и использовал следующий код:

import re
from urlparse import urlparse
import os 
def getUser(result):
    result=result.split('\n')
    u,w=result[0],result[1]
    path=urlparse(u).path.strip('/')
    if(path==''):
        f=open('path to junk','a')
        f.write('its Junk !!')
        f.close()
    else:
        result="{0}\n{1}\n{2}\n".format(u,w,path)
        writeIntoFile(result)
def writeIntoFile(result):
    tweet=result.split('\n')
    users = {}
    directory='path to directory'
    u, w, user = tweet[0],tweet[1],tweet[2]
    if user not in users :
        if(os.path.isfile(some_directory+user)):
            if(len(users)>64):
                lru,aFile,u=min(users.values())
                aFile.close()
                users.pop(u)
            users[user]=open(some_directory+user,'a')
            users[user].write(w+'\n')
            #users[user].flush
        elif (not(os.path.isfile(some_directory+user))):
            if len(users)>64:
                lru,aFile,u=min(users.values())
                aFile.close()
                users.pop(u)

            users[user]=open(some_directory+user,'w')
            users[user].write(w+'\n')
    for u in users:
        users[u].close()
import sys
s=open(sys.argv[1],'r')
tweet={}
for l in s:
    r_type,content=l.split('\t')
    if r_type in tweet:
    u,w=tweet.get('U',''),tweet.get('W','')
            if(not(re.search(r'No Post Title',u))):
                result="{0}{1}".format(u,w)
                getUser(result)
                tweet={}
        tweet[r_type]=content

Очевидно, это зеркало того, что он предложил и любезно разделял. Первоначально скорость была очень быстрой, но затем она стала медленнее. Я опубликовал обновленный код, чтобы я мог получить еще несколько предложений о том, как это могло быть сделано быстрее. Если я читал из sys.stdin, тогда была ошибка импорта, которая не могла быть решена мной. Таким образом, чтобы сэкономить время и заняться этим, я просто использовал это, надеясь, что он работает и делает это правильно. Спасибо.

Ответ 1

Вот почему ваша ОС имеет многопроцессорные конвейеры.

collapse.py sometweetfile | filter.py | user_id.py | user_split.py -d some_directory

collapse.py

import sys
with open("source","r") as theFile:
    tweet = {}
    for line in theFile:
        rec_type, content = line.split('\t')
        if rec_type in tweet:
            t, u, w = tweet.get('T',''), tweet.get('U',''), tweet.get('W','')
            result=  "{0}\t{1}\t{2}".format( t, u, w )
            sys.stdout.write( result )
            tweet= {}
        tweet[rec_type]= content
    t, u, w = tweet.get('T',''), tweet.get('U',''), tweet.get('W','')
    result=  "{0}\t{1}\t{2}".format( t, u, w )
    sys.stdout.write( result )

filter.py

import sys
for tweet in sys.stdin:
    t, u, w = tweet.split('\t')
    if 'No Post Title' in t:
        continue
    sys.stdout.write( tweet )

user_id.py

import sys
import urllib
for tweet in sys.stdin:
    t, u, w = tweet.split('\t')
    path=urlparse(w).path.strip('/')
    result= "{0}\t{1}\t{2}\t{3}".format( t, u, w, path )
    sys.stdout.write( result )

user_split.py

users = {}
for tweet in sys.stdin:
    t, u, w, user = tweet.split('\t')
    if user not in users:
        # May run afoul of open file limits...
        users[user]= open(some_directory+user,"w")
    users[user].write( tweet )
    users[user].flush( tweet )
for u in users:
    users[u].close()

Ух ты, говоришь. Какой код.

Да. Но. Он распространяется среди ВСЕХ процессорных ядер, которыми вы владеете, и все работает одновременно. Кроме того, когда вы подключаете stdout к stdin через канал, это действительно только общий буфер: никаких физических операций ввода-вывода не происходит.

Удивительно быстро сделать так. Вот почему работают операционные системы * Nix. Это то, что вам нужно сделать для реальной скорости.


Алгоритм LRU, FWIW.

    if user not in users:
        # Only keep a limited number of files open
        if len(users) > 64: # or whatever your OS limit is.
            lru, aFile, u = min( users.values() )
            aFile.close()
            users.pop(u)
        users[user]= [ tolu, open(some_directory+user,"w"), user ]
    tolu += 1
    users[user][1].write( tweet )
    users[user][1].flush() # may not be necessary
    users[user][0]= tolu

Ответ 2

Вы проводите большую часть времени в I/O. Решения:

  • делать более крупные операции ввода-вывода, то есть читать в буфер, скажем 512K, и не записывать информацию, пока не будет иметь буфер размером не менее 256 тыс.
  • избегайте максимально возможного открытия и закрытия файла.
  • используйте несколько потоков для чтения из файла, то есть разделите файл на куски и дайте каждому потоку его собственный кусок для работы

Ответ 3

Для такой массы информации я бы использовал базу данных (MySQL, PostgreSQL, SQLite и т.д.). Они оптимизированы для того, что вы делаете.

Таким образом, вместо добавления к файлу вы можете просто добавить строку в таблицу (либо нежелательную, либо "хорошую" таблицу), с URL-адресом и связанными с ним данными (один и тот же URL-адрес может быть на нескольких строках), Это, безусловно, ускорит процесс написания.

При существующем подходе время теряется, потому что входной файл считывается из одного места на вашем жестком диске, когда вы пишете в разных местах: голова жесткого диска перемещается взад и вперед физически, что происходит медленно. Кроме того, создание новых файлов требует времени. Если вы можете в основном читать из входного файла и позволить базе данных обрабатывать кеширование данных и оптимизацию записи на диск, обработка, несомненно, будет быстрее.

Ответ 4

Не уверен, что это будет быстрее, просто идея. Ваш файл выглядит как csv с вкладками в качестве разделителей. Вы пытались создать читатель csv?

import csv
reader = csv.reader(open('bigfile'), 'excel-tab')
for line in reader:
    process_line()

EDIT: Вызов csv.field_size_limit(new_limit) здесь бессмысленен.

Ответ 5

Вы можете попробовать создать dict с форматом {url: [lines...]} и только писать каждый файл в конце. Я подозреваю, что многократное открытие и закрытие файлов - это много накладных расходов. Сколько строк вы пишете на файл в среднем? Если в основном каждая строка получает свой собственный файл, то вы не можете сделать ничего, кроме изменения этого требования:)

Ответ 6

В моей системе, по крайней мере, почти все время работы будет потрачено на закрытие файлов. Последовательное чтение и запись выполняется быстро, поэтому вы можете очень хорошо сделать несколько проходов над данными. Вот что я буду делать:

  • Разделите файл на столько файлов, сколько вы можете открыть сразу, таким образом, чтобы
    • пользовательские твиты переходят в один и тот же файл
    • вы отслеживаете, какие файлы содержат более одного пользователя.
  • Продолжайте разделять выходные файлы, пока каждый файл не будет содержать только одного пользователя

Если вы можете записать в 200 файлов параллельно, после двух проходов по всем данным у вас будет 40000 файлов, содержащих в среднем 150 пользователей, поэтому после третьего прохода вы, вероятно, почти закончили.

Здесь некоторый код, предполагающий, что файл был предварительно обработан в соответствии с ответом S.Lott(сбой, фильтр, user_id). Обратите внимание, что он удалит входной файл вместе с другими промежуточными файлами.

todo = ['source']
counter = 0

while todo:
    infilename = todo.pop()
    infile = open(infilename)
    users = {}
    files = []
    filenames = []
    for tweet in infile:
        t, u, w, user = tweet.split('\t')
        if user not in users:
            users[user] = len(users) % MAX_FILES
            if len(files) < MAX_FILES:
                filenames.append(str(counter))
                files.append(open(filenames[-1], 'w'))
                counter += 1
        files[users[user]].write(tweet)
    for f in files:
        f.close()
    if len(users) > MAX_FILES:
        todo += filenames[:len(users)-MAX_FILES]
    infile.close()
    os.remove(infilename)

Ответ 7

Я думаю, что запись строк за строкой - это убийца во время работы с такими огромными данными. Вы можете значительно ускорить его с помощью векторизованных операций, т.е. Читать/писать сразу несколько строк, как в этом ответе здесь