問題は、文字列形式で保存されているデータが多すぎて、ユースケースにとって非常に無駄であり、実際のメモリが不足してスワップがスラッシングしていることだと思います。これを回避するには 128GBで十分です...:)
とにかく追加情報を格納する必要があることをコメントで示したので、親文字列を参照する別のクラスを選択します。hg18のchromFa.zipのchr21.faを使用して簡単なテストを実行しました。ファイルは約48MBで、100万行弱です。ここには1GBのメモリしかないので、後でオブジェクトを破棄するだけです。したがって、このテストでは、断片化、キャッシュ、または関連する問題は示されませんが、解析スループットを測定するための良い出発点になると思います。
import mmap
import os
import time
import sys
class Subseq(object):
__slots__ = ("parent", "offset", "length")
def __init__(self, parent, offset, length):
self.parent = parent
self.offset = offset
self.length = length
# these are discussed in comments:
def __str__(self):
return self.parent[self.offset:self.offset + self.length]
def __hash__(self):
return hash(str(self))
def __getitem__(self, index):
# doesn't currently handle slicing
assert 0 <= index < self.length
return self.parent[self.offset + index]
# other methods
def parse(file, size=8):
file.readline() # skip header
whole = "".join(line.rstrip().upper() for line in file)
for offset in xrange(0, len(whole) - size + 1):
yield Subseq(whole, offset, size)
class Seq(object):
__slots__ = ("value", "offset")
def __init__(self, value, offset):
self.value = value
self.offset = offset
def parse_sep_str(file, size=8):
file.readline() # skip header
whole = "".join(line.rstrip().upper() for line in file)
for offset in xrange(0, len(whole) - size + 1):
yield Seq(whole[offset:offset + size], offset)
def parse_plain_str(file, size=8):
file.readline() # skip header
whole = "".join(line.rstrip().upper() for line in file)
for offset in xrange(0, len(whole) - size + 1):
yield whole[offset:offset+size]
def parse_tuple(file, size=8):
file.readline() # skip header
whole = "".join(line.rstrip().upper() for line in file)
for offset in xrange(0, len(whole) - size + 1):
yield (whole, offset, size)
def parse_orig(file, size=8):
file.readline() # skip header
buffer = ''
for line in file:
buffer += line.rstrip().upper()
while len(buffer) >= size:
yield buffer[:size]
buffer = buffer[1:]
def parse_os_read(file, size=8):
file.readline() # skip header
file_size = os.fstat(file.fileno()).st_size
whole = os.read(file.fileno(), file_size).replace("\n", "").upper()
for offset in xrange(0, len(whole) - size + 1):
yield whole[offset:offset+size]
def parse_mmap(file, size=8):
file.readline() # skip past the header
buffer = ""
for line in file:
buffer += line
if len(buffer) >= size:
for start in xrange(0, len(buffer) - size + 1):
yield buffer[start:start + size].upper()
buffer = buffer[-(len(buffer) - size + 1):]
for start in xrange(0, len(buffer) - size + 1):
yield buffer[start:start + size]
def length(x):
return sum(1 for _ in x)
def duration(secs):
return "%dm %ds" % divmod(secs, 60)
def main(argv):
tests = [parse, parse_sep_str, parse_tuple, parse_plain_str, parse_orig, parse_os_read]
n = 0
for fn in tests:
n += 1
with open(argv[1]) as f:
start = time.time()
length(fn(f))
end = time.time()
print "%d %-20s %s" % (n, fn.__name__, duration(end - start))
fn = parse_mmap
n += 1
with open(argv[1]) as f:
f = mmap.mmap(f.fileno(), 0, mmap.MAP_PRIVATE, mmap.PROT_READ)
start = time.time()
length(fn(f))
end = time.time()
print "%d %-20s %s" % (n, fn.__name__, duration(end - start))
if __name__ == "__main__":
sys.exit(main(sys.argv))
1 parse 1m 42s
2 parse_sep_str 1m 42s
3 parse_tuple 0m 29s
4 parse_plain_str 0m 36s
5 parse_orig 0m 45s
6 parse_os_read 0m 34s
7 parse_mmap 0m 37s
最初の4つは私のコードですが、origはあなたのもので、最後の2つは他の回答からのものです。
ユーザー定義オブジェクトは、タプルやプレーン文字列よりも作成と収集にはるかにコストがかかります。これはそれほど驚くべきことではありませんが、これほど大きな違いが生じるとは思いもしませんでした(ユーザー定義のクラスとタプルでのみ実際に異なる#1と#3を比較してください)。とにかく(parseやparse_sep_strの場合のように)文字列とともにオフセットなどの追加情報を格納したいとおっしゃっていたので、そのタイプをC拡張モジュールに実装することを検討してください。Cを直接書きたくない場合は、Cythonおよび関連するものを参照してください。
ケース#1と#2は同じであると予想されます。親文字列を指すことにより、処理時間ではなくメモリを節約しようとしましたが、このテストではそれを測定しません。