Я читаю файл viewfactor, сгенерированный в Ansys (с использованием VFOPT), и преобразовываю его в 2d-массив в python. Я знаю, что окончательный фактор просмотра должен быть массивом 6982 * 6982.
Коэффициент просмотра (файл viewfactor.db
, размером от нескольких МБ до нескольких ГБ) форматируется следующим образом:
Ansys Release 2020 R2 Build 20.2 Update 20200601 Format 0
RS3D
Number of Enclosures = 1
Enclosure Number = 1 Number of Surfaces = 6982
Element number = 28868 Face Number 2 TOTAL= 1.0000
0.0000 0.0000 0.0000 0.0029 0.0056 0.0000 0.0000 0.0105 0.0000 0.0000
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
(numbers numbers numbers)
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
0.0000 0.0000
Element number = 28869 Face Number 2 TOTAL= 1.0000
0.0000 0.0000 0.0000 0.0029 0.0056 0.0000 0.0000 0.0105 0.0000 0.0000
0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000 0.0000
etc etc
Итак, для 1-го элемента (элемент 28868) даны номера коэффициентов просмотра (по 10 за раз, с последней строкой, возможно, меньше), затем есть две строки, которые я могу игнорировать, затем даны новые числа и так далее.
В настоящее время я читаю файл следующим образом:
import numpy as np
# the file reading part here is rather quick, it takes a couple seconds
with open('viewfactor.db', 'r') as f:
lines = f.read().splitlines()
nfaces = 6982 # I already had this number from before
vf = [[None] * nfaces] * nfaces
row = 0
col = 0
# this is the part that needs optimizing
for line in lines[7:]:
if line == '': continue
elif line.startswith('Element number'): # reset counters
row += 1
col = 0
else: # save numbers
nums = list(map(float, line.split()))
vf[row][col:col+len(nums)] = nums
col += len(nums)
vf = np.array(vf)
Это работает, но скрипт выполняется вечно. Я протестировал его с файлом viewfactor размером 350 МБ, и это заняло около 50 секунд (при необходимости я могу предоставить файл).
Можно ли уменьшить время выполнения? Могу ли я сотворить магию параллельных вычислений? Использовать С? Делать без петель?
Спасибо за любое предложение!
не могли бы вы поделиться ссылкой на этот файл?
@ dankal444 Реализация Numpy не кажется особенно эффективной. На самом деле, это выглядит немного медленнее, чем np.array(list(map(float, line.split())))
на моей машине на нескольких простых тестах... Это довольно печально...
Такие вычисления можно значительно ускорить, используя нативные языки, такие как C или C++. Его также можно распараллелить с помощью OpenMP без особых усилий (в то время как это трудно сделать эффективно в Python, и полученный код, вероятно, будет значительно медленнее, чем код нативного кода). Разумно оптимизированный последовательный код C уже должен обеспечивать хорошее ускорение. Разбор — дорогостоящий процесс. Рассмотрите возможность использования двоичных файлов, если можете.
@JérômeRichard, к сожалению, ты прав. Я сделал numba-подход к байтам и нескольким строкам, кажется многообещающим (посмотрите на ответ)
У меня есть кое-что, что вы можете включить в свой код. Эта функция ниже принимает любую строку с числами (разделенную пробелом " " или символом новой строки "\n") и преобразует ее в массив numpy. Не кормите его отдельными строками, найдите все последовательные строки с номерами и дайте им это.
В моем бенчмарке преобразование 100 строк из 10 чисел было всего в 6 раз медленнее, чем выполнение одной строки вашего np.array(list(map(float, test_string.split())))
. Так что ускорение должно быть не менее 10x
Примечание 1: вам нужно сначала преобразовать эти строки в байты, как в приведенном мной примере. Может быть, вы можете оптимизировать и работать с байтами для начала.
Примечание 2: первый запуск этой функции будет медленным из-за компиляции, не удалось найти правильную подпись для массива байтов. Может быть, @JeromeRichard может помочь с этим :) (EDIT: добавлена подпись и исправления, предложенные Джеромом в комментариях)
Примечание 3: у вас есть дополнительные знания об этом конкретном файле, и вы можете найти другие вещи для оптимизации (я полагаю, что подсчет пробелов и новых строк не требуется, и вы можете предоставить его в функцию в качестве входных данных).
@nb.jit('(Bytes(uint8, 1, "C"),)', nopython=True)
def numba_str_multiline(txt):
# zero digit code = 48
# space code = 32
# new line code = 10
# dot code = 46
n_spaces_and_new_lines = 0
for i in range(len(txt)):
char = txt[i]
if char == 32 or char == 10:
n_spaces_and_new_lines += 1
result = np.zeros(n_spaces_and_new_lines)
current_number = 0.0
idx_of_current_number = 0
before_dot = True
divisor = 10
for i in range(len(txt)):
char = txt[i]
if char == 32 or char == 10:
result[idx_of_current_number] = current_number
idx_of_current_number += 1
divisor = 10
current_number = 0.0
before_dot = True # for cases if there is no dot at all
elif char == 46:
before_dot = False
else:
if before_dot:
current_number = current_number * 10.0 + (char - 48)
else:
current_number += (char - 48) / divisor
divisor *= 10
return result
test_string = "1.2345 678.123400 0.0000 0.0029 0.0056 0.0000 0.0000 0.0105 0.0000 0.0000"
test_string_multiline = """1.2345 678.123400 0.0000 0.0029 0.0056 0.0000 0.0000 0.0105 0.0000 0.0000
""" * 100
test_bytes_multiline = bytes(test_string_multiline, 'utf-8')
result = numba_str_multiline(test_bytes_multiline)
Вы можете использовать подпись @nb.njit('(Bytes(uint8, 1, "C"),)')
для того, чтобы этот код скомпилировался охотно ;) . Примечание. Numba очень неэффективен для строк и не очень хорош (но хорош) для байтов. Код Numba быстрее, чем наивный подход Python, но он все еще неэффективен по сравнению с тем, что можно было бы сделать на C (конечно, в несколько раз быстрее с оптимизированным кодом, особенно если числа имеют фиксированный размер). На моей машине значительное время уходит на повторение txt
и выделение result
.
Вы можете использовать for i in range(len(txt)): char = txt[i]
для более быстрой работы и (char - 48)
вместо (char - 48.0)
. На моей машине это на 25-30% быстрее. Ключом к производительности здесь является использование инструкции SIMD, но это на самом деле невозможно в Numba (по крайней мере, это непросто и не приводит к простому коду). Например, AVX может вычислить 32 символа подряд. Большинство процессоров x86 могут выполнять от 2 до 3 основных целочисленных инструкций AVX за цикл. Для двойной точности AVX может вычислять 4 числа за инструкцию (а часто и 2 за цикл). Обычно этого достаточно для обработки файлов со скоростью >1 ГиБ/с.
В любом случае, это в 15-20 раз быстрее, чем наивная реализация Python на моей машине после оптимизации, и этого может быть достаточно для ОП.
Привет @dankal444, спасибо за ответ! Извините, если я еще не протестировал код, но я уезжаю на пасхальные каникулы, и у меня нет времени добраться до своего компьютера, но я попробую скрипт, как только смогу. У меня есть возможность напрямую экспортировать файл коэффициента просмотра в двоичной форме, я посмотрю, поможет ли это в дальнейшем повышении скорости. Еще раз спасибо!
@JérômeRichard спасибо за подпись и эти исправления! У меня было подобное улучшение на моей машине. Кроме того, я пытался преобразовать в uint8, но это не дало никаких улучшений. Кажется, байты дают ту же производительность, что и массив uint8 numpy (что, я думаю, хорошо?).
@man-teiv Счастливой Пасхи, мужик :) не торопись
Я проверил эту функцию, и она отлично работает! Время чтения сократилось с 50 с до 2,5 с, поэтому производительность увеличилась примерно в 20 раз :) У меня только есть сомнение: я хочу пропустить первый цикл для определения n_spaces_and_new_lines
, так как я уже знаю это число. Но я попытался изменить функцию с def numba_str_multiline(txt):
на def numba_str_multiline(txt, num):
, чтобы напрямую построить массив как result = np.zeros(num)
, но появляется следующее сообщение об ошибке: TypeError: Failed in nopython mode pipeline (step: fix up args)
. Можно ли передать более одного аргумента?
Да, можно передать любое (вменяемое) количество аргументов, нужно только поправить подпись. @nb.jit('(Bytes(uint8, 1, "C"), int64)', nopython=True)
Вот оно! Еще раз спасибо за ваше терпение и ваше супер полезное решение
Проверьте это: numpy.org/doc/stable/reference/generated/numpy.fromstring.html