Germany | Finland | Saint Petersburg | Drive

Особенности работы с файлами в qpile

Опубликовано в QPILE

Язык Qpile имеет встроенные средства для работы с файлами. Несмотря на бедность этого набора средств, он позволяет кое-как решить большинство возникающих задач. Однако и здесь имеются подводные камни.

Самая распространенная необходимость - прочитать текстовый файл. Для этой цели предусмотрены 2 встроенные функции - get_file_len и read_line. Первая возвращает количество строк во входном файле, вторая - читает строку с требуемым порядковым номером в строковую переменную.

Таким образом, QPILE оперирует минимальным квантом, равным одной строке, при этом предоставляя прямой доступ к ней. Можно сразу получить любую строку по ее номеру.

Однако, для операционной системы Windows (да и для подавляющего числа остальных операционных систем) файл есть объект последовательного доступа, когда получить очередной квант можно лишь прочитав все предыдущие. Да и сам квант - это не строка, а байт. Таким образом, интерпретатор QPILE на уровне своей стандартной библиотеки предоставляет сервис, подменяющий последовательный доступ к содержимому файла на прямой, при этом меняющий единицу квантования содержащейся в нём информации.

Для упрощения текстов, написанных на QPILE - удобно. Однако, вспоминая про бесплатный сыр, необходимо понимать цену такого сервиса. А она велика. В некоторых случаях она велика неприемлемо.

Начнем с get_file_len. Windows предоставляет лишь возможность узнать длину файла в байтах. Про количество строк внутри файла ей ничего неизвестно. Поэтому для того, чтобы узнать количество строк в файле (как это делает get_file_len), необходимо прочитать его полностью и посчитать в нем количество символов перевода строки (CR-LF) - иного способа в рамках операционной системы Windows не существует. Вывод. Каждый вызов функции Get_File_Len есть чтение файла полностью, от начала и до конца.

Вторая функция - read_line. И здесь ситуация аналогичная. Чтобы получить, скажем строку с номером 5, нужно прочитать как минимум все предыдущие. Чтобы прочитать 6 строку - снова с самого начала прочитать 6 строк и так далее.

Вот пример из документации, иллюстрирующий применение функции read_line:

 msg = READ_LINE ("filename.ext", GET_FILE_LEN("filename.ext"), error)
MESSAGE (msg,1)

Этот фрагмент читает последнюю строку из файла filename.ext и выводит ее в виде сообщения.

Учитывая все написанное ранее, оказывается, что для чтения всего одной последней строки интерпретатор делает следующую последовательность действий:

  1. открывает файл
  2. читает файл полностью, узнаёт количество строк в нем
  3. закрывает файл
  4. открывает файл
  5. читает файл полностью, возвращает нужную строку
  6. закрывает файл

Действий много. При этом надо учитывать, что все файловые операции - весьма затратные действия.

Нет проблем, если файл, который мы читаем, небольшой длины. При длине 1 Кб никаких затруднений такая последовательность действий не вызовет. При 10 килобайтах - тоже. Теперь давайте представим себе  случай. Есть файл, содержащий 10000 строк, каждая из которых имеет длину 100 байт.  Таким файлом, например, может быть список сделок по фьючерсу на индекс РТС за один час торговли. Или реестр клиентов брокера. Или лог-файл, созданный какой-то программой, который надо обработать. 10000 строк по 100 байт = (примерно) 1 Мегабайт - совсем не грандиозный объём. Что будет, если мы начнем его читать из QPILE портфеля?

for i from 1 to get_file_len("filename.ext")
    str = read_line("filename.ext",i,error)
end for

Напомню, в файле 10000 строк. В результате, для прочтения этого файла построчно необходимо 20000 раз его открыть, полностью прочитать и закрыть. Нужно учесть, что увеличение количества строк в файле приводит не только к пропорциональному росту числа файловых операций, но и к увеличению длительности каждой операции чтения! В результате сложность алгоритма имеет порядок N2. Даже с учетом приличной мощности компьютера, быстрых жестких дисков, помощи операционной среды в виде файлового кэширования и всего остального процесс займёт вполне заметное время - секундомер для замеров не потребуется. А для файлов в десятки мегабайт и больше ситуация становится просто невыносимой.  Для процессов, идущих в реальном времени, недопустимо.

Где деньги, Зин? Где выход?

Советов имеется 2.

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

Дополнительно можно вдвое уменьшить и количество самих файловых операций. Для этого необходимо вынести инварианту get_file_len за цикл. То есть не вычислять размер файла на каждой итерации. 

length = get_file_len("filename.ext")
for i from 1 to length
    str = read_line("filename.ext",i,error)
end for

Разновидность - читать файл с конца по направлению к началу, также избегая при этом лишних чтений для определения размера файла: 

for i from -get_file_len("filename.ext") to -1
    str = read_line("filename.ext",-i,error)
end for

Комбинация обоих способов позволяет несколько снизить остроту проблемы и ускорить обработку больших файлов из qpile в реальном времени. Полностью болезнь не лечится.

 

Добавить комментарий