Почему чтение всего файла занимает больше оперативной памяти, чем его размер на диске? - PullRequest
4 голосов
/ 24 сентября 2019

Предостережение

Это НЕ дубликат этого .Я не заинтересован в выяснении моего потребления памяти или вопроса, как я уже делаю это ниже.Вопрос в том, что ПОЧЕМУ потребление памяти такое же.

Кроме того, даже если мне нужен был способ для профилирования памяти, учтите, что guppy (предлагаемый профилировщик памяти Python ввышеупомянутая ссылка не поддерживает Python 3, а альтернатива guppy3 не дает точных результатов, которые дают такие результаты, как (см. фактические размеры ниже):

Partition of a set of 45968 objects. Total size = 5579934 bytes.
 Index  Count   %     Size   % Cumulative  % Kind (class / dict of class)
     0  13378  29  1225991  22   1225991  22 str
     1  11483  25   843360  15   2069351  37 tuple
     2   2974   6   429896   8   2499247  45 types.CodeType

Фон

Правильно, у меня есть этот простой скрипт, который я использую для некоторых RAM тестов потребления, считывая файл двумя различными способами:

  1. чтениеподавать по одной строке за раз, обрабатывая и отбрасывая ее (через generators), что эффективно и рекомендуется для практически любого размера файла (особенно больших файлов), который работает как положено .

  2. чтение целого файла в память (я знаю, что это не рекомендуется, однако это было только для образовательных целей).


Тестовый скрипт

import os
import psutil
import time


with open('errors.log') as file_handle:
    statistics = os.stat('errors.log')  # See below for contents of this file
    file_size = statistics.st_size / 1024 ** 2

    process = psutil.Process(os.getpid())

    ram_usage_before = process.memory_info().rss / 1024 ** 2
    print(f'File size: {file_size} MB')
    print(F'RAM usage before opening the file: {ram_usage_before} MB')

    file_handle.read()  # loading whole file in memory

    ram_usage_after = process.memory_info().rss / 1024 ** 2
    print(F'Expected RAM usage after loading the file: {file_size + ram_usage_before} MB')
    print(F'Actual RAM usage after loading the file: {ram_usage_after} MB')

    # time.sleep(30)

Выход

File size: 111.75 MB
RAM usage before opening the file: 8.67578125 MB
Expected RAM usage after loading the file: 120.42578125 MB
Actual RAM usage after loading the file: 343.2109375 MB

Я также добавил 30-секундный сон, чтобы проверить с помощью awk на уровне операционной системы, где я использовал следующую команду:

ps aux | awk '{print $6/1024 " MB\t\t" $11}' | sort -n

который дает:

...
343.176 MB      python  # my script
619.883 MB      /Applications/PyCharm.app/Contents/MacOS/pycharm
2277.09 MB      com.docker.hyperkit

Файл содержит около 800K копий следующей строки:

[2019-09-22 16:50:17,236] ERROR in views, line 62: 404 Not Found: The
following URL: http://localhost:5000/favicon.ico was not found on the
server.

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

1 Ответ

3 голосов
/ 24 сентября 2019

Когда вы открываете файл в Python, по умолчанию вы открываете его в Text-mode .Это означает, что двоичные данные декодируются на основе значений по умолчанию операционной системы или явно заданных кодеков.

Как и все данные, текстовые данные на вашем компьютере представлены байтами.Большая часть английского алфавита представлена ​​в виде одного байта, например буква «А» обычно переводится на число 65 или в двоичном виде: 01000001.Эта кодировка (ASCII) подходит для многих случаев, но когда вы хотите написать текст на таких языках, как румынский, этого уже недостаточно, поскольку символы ă, ţ и т. Д. Не являются частью ASCII.

Некоторое время люди использовали разные кодировки для каждого языка (группы), например, группу кодировок Latin-x (ISO-8859-x) для языков, основанных на латинском алфавите, и другие кодировки для других (особенно *).1010 * CJK ) языков.

Если вы хотите представить некоторые азиатские языки или несколько разных языков, вам потребуются кодировки, которые кодируют один символ в несколько байтов.Это может быть либо фиксированное число (например, в UTF-32 и UTF-16), либо переменное число, как в самой распространенной на сегодняшний день кодировке UTF-8.


Назад к Python: Строковый интерфейс Python обещает множество свойств, в том числе произвольный доступ в сложности O (1), что означает, что вы можете получить 1245-й символ даже из очень длинной строки очень быстро.Это противоречит компактной кодировке UTF-8: поскольку один «символ» (на самом деле: одна кодовая точка Unicode) иногда составляет один, а иногда и несколько байтов, Python не может просто перейти к адресу памяти start_of_string + length_of_one_character * offset, так как length_of_one_characterварьируется в UTF-8.Поэтому Python должен использовать кодирование с фиксированной байтовой длиной.

По причинам оптимизации он не всегда использует UCS-4 (~ UTF-32), потому что это будет тратить много места при текст только для ASCII.Вместо этого Python динамически выбирает Latin-1, UCS-2 или UCS-4 для внутреннего хранения строк.


Чтобы свести все воедино с примером:

Скажите, что вы хотитесохранить строку «soluţie» в памяти из файла, закодированного как UTF-8.Поскольку для представления буквы ţ требуется два байта, Python выбирает UCS-2:

characters | s       | o       | l       | u       | ţ       | i       | e         
     utf-8 |0x73     |0x6f     |0x6c     |0x75     |0xc5 0xa3|0x69     |0x65
     ucs-2 |0x00 0x73|0x00 0x6f|0x00 0x6c|0x00 0x75|0x01 0x63|0x00 0x69|0x00 0x65

Как видите, UTF-8 (файл на диске) требуется 8 байтов, тогда как UCS-2 -14.

Добавьте к этому накладные расходы на строку Python и сам интерпретатор Python, и ваши вычисления снова обретут смысл.


Когда вы открываете файл в двоичном режиме (* 1039)*), вы не декодируете байты, а принимаете их как есть.Это проблематично, если в файле есть текст (потому что для обработки данных вы рано или поздно захотите преобразовать его в строку, где вам придется выполнять декодирование), но если это действительно двоичные данные, такиекак изображение, это хорошо (и лучше).


Этот ответ содержит упрощения.Используйте с осторожностью.

...