2014-12-04 3 views
7

Я довольно новичок в Python, и я написал (возможно, очень уродливый) скрипт, который должен случайно выбрать подмножество последовательностей из файла fastq. Fastq-файл хранит информацию в блоках по четыре строки. Первая строка в каждом блоке начинается с символа «@». Файл fastq, который я использую в качестве входного файла, составляет 36 ГБ, содержащий около 14 000 000 строк.Как я могу сделать свой скрипт Python быстрее?

Я попытался переписать уже существующий скрипт, который использовал слишком много памяти, и мне удалось значительно сократить использование памяти. Но сценарий длится вечно, и я не понимаю, почему.

parser = argparse.ArgumentParser() 
parser.add_argument("infile", type = str, help = "The name of the fastq input file.", default = sys.stdin) 
parser.add_argument("outputfile", type = str, help = "Name of the output file.") 
parser.add_argument("-n", help="Number of sequences to sample", default=1) 
args = parser.parse_args() 


def sample(): 
    linesamples = [] 
    infile = open(args.infile, 'r') 
    outputfile = open(args.outputfile, 'w') 
    # count the number of fastq "chunks" in the input file: 
    seqs = subprocess.check_output(["grep", "-c", "@", str(args.infile)]) 
    # randomly select n fastq "chunks": 
    seqsamples = random.sample(xrange(0,int(seqs)), int(args.n)) 
    # make a list of the lines that are to be fetched from the fastq file: 
    for i in seqsamples: 
     linesamples.append(int(4*i+0)) 
     linesamples.append(int(4*i+1)) 
     linesamples.append(int(4*i+2)) 
     linesamples.append(int(4*i+3)) 
    # fetch lines from input file and write them to output file. 
    for i, line in enumerate(infile): 
     if i in linesamples: 
      outputfile.write(line) 

Grep шаг не занимает практически нет времени на все, но после более чем 500 минут, сценарий до сих пор не начал писать в выходной файл. Поэтому я полагаю, что это один из шагов между grep и последним for-loop, который занимает такое долгое время. Но я не понимаю, какой именно шаг, и что я могу сделать, чтобы ускорить его.

+7

Вы должны рассмотреть свою [программу] (https://docs.python.org/2/library/profile.html) вашу программу, чтобы увидеть, какие шаги зависают.Также попробовали ли вы запустить свой код в файле меньшего размера и посмотреть, завершено ли оно? Еще один шаг, который я рассмотрел позже в оптимизации, - это использование потоковой обработки и многопроцессорности, чтобы разделить работу. –

+0

Не называть 'int' все время внутри цикла. Также используйте инструкцию 'with'. – Veedrac

ответ

2

В зависимости от размера linesamplesif i in linesamples займет много времени с момента поиска по каждой итерации через infile. Вы можете преобразовать это в set, чтобы улучшить время поиска. Кроме того, enumerate не очень эффективен - я заменил его конструкцией line_num, которую мы увеличиваем на каждой итерации.

def sample(): 
    linesamples = set() 
    infile = open(args.infile, 'r') 
    outputfile = open(args.outputfile, 'w') 
    # count the number of fastq "chunks" in the input file: 
    seqs = subprocess.check_output(["grep", "-c", "@", str(args.infile)]) 
    # randomly select n fastq "chunks": 
    seqsamples = random.sample(xrange(0,int(seqs)), int(args.n)) 
    for i in seqsamples: 
     linesamples.add(int(4*i+0)) 
     linesamples.add(int(4*i+1)) 
     linesamples.add(int(4*i+2)) 
     linesamples.add(int(4*i+3)) 
    # make a list of the lines that are to be fetched from the fastq file: 
    # fetch lines from input file and write them to output file. 
    line_num = 0 
    for line in infile: 
     if line_num in linesamples: 
      outputfile.write(line) 
     line_num += 1 
    outputfile.close() 
+0

Я думаю, что этот ответ правильно определяет узкое место 'if i in linesamples'. Явное закрытие дескрипторов файлов было бы, вероятно, хорошей идеей, хотя :) – cel

+0

@cel, согласился - я обновил код. – vikramls

+3

'enumerate' не эффективен? Есть ли у вас какие-либо критерии, чтобы доказать это? – Matthias

0

Попробуйте распараллелить свой код. Я имею в виду это. У вас есть 14 000 000 строк ввода.

  1. Работа Ваш Grep и фильтровать строки первой и записать его на filteredInput.txt
  2. Разделите filteredInput на 10.000-100.000 строк файлов, таких как filteredInput001.txt, filteredInput002.txt
  3. работы нашего кода на это разделенные файлы. Напишите свой вывод в разные файлы, такие как output001.txt, output002.txt
  4. Объедините результаты в качестве заключительного шага.

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

Вы также можете использовать несколько процессов python таким образом (после шага 1), используя потоки оболочки или python.

+1

Возможно, не рекомендуется предлагать распараллеливание перед оптимизацией алгоритма. С правильным алгоритмом IO будет шея бутылки, а не процессор. – cel

+0

@cel Его код даже не работает прямо сейчас, но проблема расщепления и распараллеливание - это не очень хорошая идея. –

1

Вы сказали, что Grep завершит работу довольно быстро, так что в этом случае вместо того, чтобы просто использовать Grep для подсчета вхождений @ имеют Grep выходные байтовые смещения каждого символа @ он видит (используя -b вариант для Grep). Затем используйте random.sample, чтобы выбрать, который когда-либо блокирует вас. После того, как вы выбрали смещенные байты, используйте infile.seek, чтобы перейти к каждому смещению байта и распечатать 4 строки оттуда.

0

Вы можете использовать алгоритм Reservoir Sampling. С помощью этого алгоритма вы читаете данные только один раз (нет необходимости заранее подсчитывать строки файла), чтобы вы могли передавать данные через ваш скрипт. Вот пример кода python на странице Википедии.

Существует также реализация C для выборки fastq в Heng Li's seqtk.

Смежные вопросы