Я нашел решение Cython, которое отвечает моим потребностям.Это использует магию ячеек Cython для ноутбука Jupyter для обработки компиляции.Я выбрал 2000000 для инициализации массива, потому что это разумный верхний предел для моих данных.Функция возвращает только те строки массива numpy, которые были фактически заполнены.Передача этого массива в массив данных pandas обходится довольно дешево.
Я не уверен, сколько еще можно провести оптимизации, учитывая, что я также выбрасываю некоторые строки мусора, которые, как мне кажется, исключают память-отображением.Возможно, я мог бы использовать указатели, как в для ответа на другой вопрос, который у меня был , но было бы сложно найти данные в моем файле и обнаружить плохие строки (подробнее о моей большей проблеме чтения страниц см. Ниже)данных), если бы я перемещал указатель вместо итерации строк.
%%cython
import numpy as np
cimport numpy as np
np.import_array()
from libc.stdlib cimport atof
from cpython cimport bool
def read_with_cython(filename):
cdef float[:, ::1] data = np.zeros((2000000, 13), np.float32)
cdef int i = 0
with open(filename, 'rb') as f:
for line in f:
if len(line) == 133:
data[i, 0] = atof(line[0:5])
data[i, 1] = atof(line[5:10])
data[i, 2] = atof(line[12:21])
data[i, 3] = atof(line[23:32])
data[i, 4] = atof(line[34:43])
data[i, 5] = atof(line[45:54])
data[i, 6] = atof(line[56:65])
data[i, 7] = atof(line[67:76])
data[i, 8] = atof(line[78:87])
data[i, 9] = atof(line[89:98])
data[i, 10] = atof(line[100:109])
data[i, 11] = atof(line[111:120])
data[i, 12] = atof(line[122:131])
i += 1
return data.base[:i]
С этим я смог выполнить следующее:
text = ''' 372 1 0.000E+00 0.000E+00 0.000E+00 9.150E+02 3.236E+03 0.000E+00 0.000E+00 0.000E+00 0.000E+00 0.000E+00 3.623E+02\n'''*300000
with open('demo_file.txt', 'w') as f:
f.write(text)
%timeit result = read_with_cython('demo_file.txt')
и получить этот результат:
473 ms ± 6.63 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Для сравнения и полноты я также написал быструю версию на чистом Python:
def read_python(text):
data = np.zeros((300000, 13), dtype=np.float)
for i, line in enumerate(text.splitlines()):
data[i, 0] = float(line[:5])
data[i, 1] = float(line[5:10])
for j in range(11):
a = 10+j*11
b = a + 11
data[i, j+2] = float(line[a:b])
return data
, которая работала за 1,15 с:
1.15 s ± 8.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Затем я попытался приспособитьсяэто к очень простому примеру Cython, который работал в 717ms:
%%cython
def read_python_cy(text):
text.replace('\r\n', '')
i = 0
while True:
float(line[i:i+5])
float(line[i+5:i+10])
for j in range(11):
a = i+10+j*11
b = i+a + 11
float(line[a:b])
i += 131
return 0
717 ms ± 5.66 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
Тогда я сломался и выяснил более оптимизированную версию Cython выше.
Именно в тот момент я понял Cythonможет более эффективно решить и эту проблему, и проблему регулярных выражений с медленной скоростью.Я использовал regex для поиска и захвата ~ 5000 страниц данных, которые затем объединялись в таблицу, которую я пытаюсь прочитать здесь.Что-то ближе к моей реальной функции Cython показано ниже.Это обрабатывает поиск страницы данных, захватывает детализацию на уровне страницы (время), затем читает строки фактических данных, пока не будет обнаружен флаг остановки (строка, начинающаяся с 0 или 1).Моему регулярному выражению потребовалось 1 с, чтобы извлечь нужные данные, так что это экономит мне много времени в целом.
%%cython
import numpy as np
cimport numpy as np
np.import_array()
from libc.stdlib cimport atof
import cython
from cpython cimport bool
def read_pages_cython(filename):
cdef int n_pages = 0
cdef bool reading_page = False
cdef float[:, ::1] data = np.zeros((2000000, 14), np.float32)
cdef int i = 0
cdef float time
with open(filename, 'rb') as f:
for line in f:
if not reading_page:
if b'SUMMARY' in line:
time = atof(line[73:80])
reading_page = True
else:
if len(line) == 133:
data[i, 0] = atof(line[0:5])
# data[i, 1] = atof(line[5:10])
data[i, 2] = atof(line[12:21])
data[i, 3] = atof(line[23:32])
data[i, 4] = atof(line[34:43])
data[i, 5] = atof(line[45:54])
data[i, 6] = atof(line[56:65])
data[i, 7] = atof(line[67:76])
data[i, 8] = atof(line[78:87])
data[i, 9] = atof(line[89:98])
data[i, 10] = atof(line[100:109])
data[i, 11] = atof(line[111:120])
data[i, 12] = atof(line[122:131])
data[i, 13] = time
if len(line) > 6:
if line[:1] == b'1':
if b'SUMMARY' in line:
time = atof(line[73:80])
reading_page = True
else:
reading_page = False
i += 1
continue
elif line[:1] == b'0':
reading_page = False
i += 1
continue
i += 1
return data.base[:i]