Python mmap - 访问文件末尾速度慢[带有测试代码]
Python mmap - slow access to end of files [with test code]
前几天发了一个类似的问题,但是没有代码,现在自己写了一个测试代码,希望能得到一些帮助。
代码在最下方
我得到了一些数据集,其中有一堆大文件 (~100),我想非常有效地从这些文件中提取特定行(在内存和速度上)。
我的代码获取相关文件的列表,代码使用 [第 1 行] 打开每个文件,然后使用 [第 2 行] 将文件映射到内存,同时,对于每个文件,我收到一个索引列表并继续在索引上我检索相关信息(本例为 10 个字节),如下所示:[第 3-4 行],最后我用 [第 5-6 行].
关闭句柄
binaryFile = open(path, "r+b")
binaryFile_mm = mmap.mmap(binaryFile.fileno(), 0)
for INDEX in INDEXES:
information = binaryFile_mm[(INDEX):(INDEX)+10].decode("utf-8")
binaryFile_mm.close()
binaryFile.close()
此代码并行运行,每个文件有数千个索引,并且每秒连续执行几次,持续数小时。
现在开始解决问题 - 当我将索引限制得很小时代码运行良好(意思是 - 当我要求代码从文件开头获取信息时) .但!当我增加索引的范围时,一切都减慢到(几乎)停止并且 buff/cache 内存变满(我不确定内存问题是否与减速有关)。
所以我的问题是,为什么我从文件的开头或结尾检索信息很重要,我该如何克服这个问题,以便从文件的结尾即时访问信息,而不会减慢和增加 buff/cache 内存使用。
PS - 一些数字和大小:所以我得到了大约 100 个文件,每个文件的大小约为 1GB,当我将索引限制在文件的 0%-10% 时,它运行良好,但是当我允许索引位于文件中的任何位置时,它停止工作。
代码 - 在linux和windows上测试python 3.5,需要10 GB的存储空间(创建 3 个文件,每个文件包含 3GB 内的随机字符串)
import os, errno, sys
import random, time
import mmap
def create_binary_test_file():
print("Creating files with 3,000,000,000 characters, takes a few seconds...")
test_binary_file1 = open("test_binary_file1.testbin", "wb")
test_binary_file2 = open("test_binary_file2.testbin", "wb")
test_binary_file3 = open("test_binary_file3.testbin", "wb")
for i in range(1000):
if i % 100 == 0 :
print("progress - ", i/10, " % ")
# efficiently create random strings and write to files
tbl = bytes.maketrans(bytearray(range(256)),
bytearray([ord(b'a') + b % 26 for b in range(256)]))
random_string = (os.urandom(3000000).translate(tbl))
test_binary_file1.write(str(random_string).encode('utf-8'))
test_binary_file2.write(str(random_string).encode('utf-8'))
test_binary_file3.write(str(random_string).encode('utf-8'))
test_binary_file1.close()
test_binary_file2.close()
test_binary_file3.close()
print("Created binary file for testing.The file contains 3,000,000,000 characters")
# Opening binary test file
try:
binary_file = open("test_binary_file1.testbin", "r+b")
except OSError as e: # this would be "except OSError, e:" before Python 2.6
if e.errno == errno.ENOENT: # errno.ENOENT = no such file or directory
create_binary_test_file()
binary_file = open("test_binary_file1.testbin", "r+b")
## example of use - perform 100 times, in each itteration: open one of the binary files and retrieve 5,000 sample strings
## (if code runs fast and without a slowdown - increase the k or other numbers and it should reproduce the problem)
## Example 1 - getting information from start of file
print("Getting information from start of file")
etime = []
for i in range(100):
start = time.time()
binary_file_mm = mmap.mmap(binary_file.fileno(), 0)
sample_index_list = random.sample(range(1,100000-1000), k=50000)
sampled_data = [[binary_file_mm[v:v+1000].decode("utf-8")] for v in sample_index_list]
binary_file_mm.close()
binary_file.close()
file_number = random.randint(1, 3)
binary_file = open("test_binary_file" + str(file_number) + ".testbin", "r+b")
etime.append((time.time() - start))
if i % 10 == 9 :
print("Iter ", i, " \tAverage time - ", '%.5f' % (sum(etime[-9:]) / len(etime[-9:])))
binary_file.close()
## Example 2 - getting information from all of the file
print("Getting information from all of the file")
binary_file = open("test_binary_file1.testbin", "r+b")
etime = []
for i in range(100):
start = time.time()
binary_file_mm = mmap.mmap(binary_file.fileno(), 0)
sample_index_list = random.sample(range(1,3000000000-1000), k=50000)
sampled_data = [[binary_file_mm[v:v+1000].decode("utf-8")] for v in sample_index_list]
binary_file_mm.close()
binary_file.close()
file_number = random.randint(1, 3)
binary_file = open("test_binary_file" + str(file_number) + ".testbin", "r+b")
etime.append((time.time() - start))
if i % 10 == 9 :
print("Iter ", i, " \tAverage time - ", '%.5f' % (sum(etime[-9:]) / len(etime[-9:])))
binary_file.close()
我的结果:(从整个文件中获取信息的平均时间比从头开始获取信息慢近 4 倍,大约有 100 个文件并并行计算这个差异变得更大)
Getting information from start of file
Iter 9 Average time - 0.14790
Iter 19 Average time - 0.14590
Iter 29 Average time - 0.14456
Iter 39 Average time - 0.14279
Iter 49 Average time - 0.14256
Iter 59 Average time - 0.14312
Iter 69 Average time - 0.14145
Iter 79 Average time - 0.13867
Iter 89 Average time - 0.14079
Iter 99 Average time - 0.13979
Getting information from all of the file
Iter 9 Average time - 0.46114
Iter 19 Average time - 0.47547
Iter 29 Average time - 0.47936
Iter 39 Average time - 0.47469
Iter 49 Average time - 0.47158
Iter 59 Average time - 0.47114
Iter 69 Average time - 0.47247
Iter 79 Average time - 0.47881
Iter 89 Average time - 0.47792
Iter 99 Average time - 0.47681
你有这个时间差的根本原因是你必须在文件中寻找你需要的地方。离位置 0 越远,需要的时间越长。
可能 的帮助是,因为您知道所需的起始索引,所以在文件描述符上查找该点,然后执行 mmap。或者真的,为什么首先要使用 mmap - 只需从 seeked-to 位置读取所需的字节数,然后将其放入结果变量中。
要确定您是否获得了足够的性能,请检查 buffer/page 缓存可用的内存(Linux 中的 free
),I/O 统计 - 数字读取次数、大小和持续时间(iostat
;与您的硬件规格进行比较),以及您进程的 CPU 利用率。
[编辑] 假设您从本地连接的 SSD 读取数据(缓存中没有您需要的数据):
- 在单线程中读取时,50,000 次读取的批处理时间应该超过 7 秒 (50000*0.000150)。可能更长,因为对 mmap 文件的 50k 次访问会触发更多或更大的读取,因为您的访问不是页面对齐的——正如我在另一个问答中所建议的那样,我会使用简单的
seek
/read
相反(open
带有 buffering=0
的文件以避免不必要的读取 Python 缓冲 I/O)。
- 随着更多 threads/processes 同时读取,您可以使您的 SSD 吞吐量饱和(它可以处理多少 4KB reads/s - 它可以在 5,000 到 1,000,000 之间的任何地方),然后单个读取将变为甚至更慢。
[/edit]
第一个示例仅访问 3*100KB 的文件数据,因此您有比缓存可用的更多的数据,所有 300KB 很快就会进入缓存,因此您将看不到 I/O,并且您的 python 进程将 CPU-绑定。
我有 99.99% 的把握,如果您测试从每个文件的最后 100KB 读取数据,它的性能将与第一个示例一样好——这与数据的位置无关,而是关于访问数据的大小。
第二个示例访问 9GB 中的随机部分,因此只有当您有足够的可用 RAM 来缓存所有 9GB 并且只有在将文件预加载到缓存中之后,您才希望看到类似的性能,以便测试用例 运行s 为零 I/O.
在实际情况下,文件不会完全在缓存中 - 因此您会看到许多 I/O 请求和 python 的低得多的 CPU 利用率。由于 I/O 比缓存访问慢得多,您应该期望此示例 运行 慢。
前几天发了一个类似的问题,但是没有代码,现在自己写了一个测试代码,希望能得到一些帮助。
代码在最下方
我得到了一些数据集,其中有一堆大文件 (~100),我想非常有效地从这些文件中提取特定行(在内存和速度上)。
我的代码获取相关文件的列表,代码使用 [第 1 行] 打开每个文件,然后使用 [第 2 行] 将文件映射到内存,同时,对于每个文件,我收到一个索引列表并继续在索引上我检索相关信息(本例为 10 个字节),如下所示:[第 3-4 行],最后我用 [第 5-6 行].
关闭句柄binaryFile = open(path, "r+b")
binaryFile_mm = mmap.mmap(binaryFile.fileno(), 0)
for INDEX in INDEXES:
information = binaryFile_mm[(INDEX):(INDEX)+10].decode("utf-8")
binaryFile_mm.close()
binaryFile.close()
此代码并行运行,每个文件有数千个索引,并且每秒连续执行几次,持续数小时。
现在开始解决问题 - 当我将索引限制得很小时代码运行良好(意思是 - 当我要求代码从文件开头获取信息时) .但!当我增加索引的范围时,一切都减慢到(几乎)停止并且 buff/cache 内存变满(我不确定内存问题是否与减速有关)。
所以我的问题是,为什么我从文件的开头或结尾检索信息很重要,我该如何克服这个问题,以便从文件的结尾即时访问信息,而不会减慢和增加 buff/cache 内存使用。
PS - 一些数字和大小:所以我得到了大约 100 个文件,每个文件的大小约为 1GB,当我将索引限制在文件的 0%-10% 时,它运行良好,但是当我允许索引位于文件中的任何位置时,它停止工作。
代码 - 在linux和windows上测试python 3.5,需要10 GB的存储空间(创建 3 个文件,每个文件包含 3GB 内的随机字符串)
import os, errno, sys
import random, time
import mmap
def create_binary_test_file():
print("Creating files with 3,000,000,000 characters, takes a few seconds...")
test_binary_file1 = open("test_binary_file1.testbin", "wb")
test_binary_file2 = open("test_binary_file2.testbin", "wb")
test_binary_file3 = open("test_binary_file3.testbin", "wb")
for i in range(1000):
if i % 100 == 0 :
print("progress - ", i/10, " % ")
# efficiently create random strings and write to files
tbl = bytes.maketrans(bytearray(range(256)),
bytearray([ord(b'a') + b % 26 for b in range(256)]))
random_string = (os.urandom(3000000).translate(tbl))
test_binary_file1.write(str(random_string).encode('utf-8'))
test_binary_file2.write(str(random_string).encode('utf-8'))
test_binary_file3.write(str(random_string).encode('utf-8'))
test_binary_file1.close()
test_binary_file2.close()
test_binary_file3.close()
print("Created binary file for testing.The file contains 3,000,000,000 characters")
# Opening binary test file
try:
binary_file = open("test_binary_file1.testbin", "r+b")
except OSError as e: # this would be "except OSError, e:" before Python 2.6
if e.errno == errno.ENOENT: # errno.ENOENT = no such file or directory
create_binary_test_file()
binary_file = open("test_binary_file1.testbin", "r+b")
## example of use - perform 100 times, in each itteration: open one of the binary files and retrieve 5,000 sample strings
## (if code runs fast and without a slowdown - increase the k or other numbers and it should reproduce the problem)
## Example 1 - getting information from start of file
print("Getting information from start of file")
etime = []
for i in range(100):
start = time.time()
binary_file_mm = mmap.mmap(binary_file.fileno(), 0)
sample_index_list = random.sample(range(1,100000-1000), k=50000)
sampled_data = [[binary_file_mm[v:v+1000].decode("utf-8")] for v in sample_index_list]
binary_file_mm.close()
binary_file.close()
file_number = random.randint(1, 3)
binary_file = open("test_binary_file" + str(file_number) + ".testbin", "r+b")
etime.append((time.time() - start))
if i % 10 == 9 :
print("Iter ", i, " \tAverage time - ", '%.5f' % (sum(etime[-9:]) / len(etime[-9:])))
binary_file.close()
## Example 2 - getting information from all of the file
print("Getting information from all of the file")
binary_file = open("test_binary_file1.testbin", "r+b")
etime = []
for i in range(100):
start = time.time()
binary_file_mm = mmap.mmap(binary_file.fileno(), 0)
sample_index_list = random.sample(range(1,3000000000-1000), k=50000)
sampled_data = [[binary_file_mm[v:v+1000].decode("utf-8")] for v in sample_index_list]
binary_file_mm.close()
binary_file.close()
file_number = random.randint(1, 3)
binary_file = open("test_binary_file" + str(file_number) + ".testbin", "r+b")
etime.append((time.time() - start))
if i % 10 == 9 :
print("Iter ", i, " \tAverage time - ", '%.5f' % (sum(etime[-9:]) / len(etime[-9:])))
binary_file.close()
我的结果:(从整个文件中获取信息的平均时间比从头开始获取信息慢近 4 倍,大约有 100 个文件并并行计算这个差异变得更大)
Getting information from start of file
Iter 9 Average time - 0.14790
Iter 19 Average time - 0.14590
Iter 29 Average time - 0.14456
Iter 39 Average time - 0.14279
Iter 49 Average time - 0.14256
Iter 59 Average time - 0.14312
Iter 69 Average time - 0.14145
Iter 79 Average time - 0.13867
Iter 89 Average time - 0.14079
Iter 99 Average time - 0.13979
Getting information from all of the file
Iter 9 Average time - 0.46114
Iter 19 Average time - 0.47547
Iter 29 Average time - 0.47936
Iter 39 Average time - 0.47469
Iter 49 Average time - 0.47158
Iter 59 Average time - 0.47114
Iter 69 Average time - 0.47247
Iter 79 Average time - 0.47881
Iter 89 Average time - 0.47792
Iter 99 Average time - 0.47681
你有这个时间差的根本原因是你必须在文件中寻找你需要的地方。离位置 0 越远,需要的时间越长。
可能 的帮助是,因为您知道所需的起始索引,所以在文件描述符上查找该点,然后执行 mmap。或者真的,为什么首先要使用 mmap - 只需从 seeked-to 位置读取所需的字节数,然后将其放入结果变量中。
要确定您是否获得了足够的性能,请检查 buffer/page 缓存可用的内存(Linux 中的 free
),I/O 统计 - 数字读取次数、大小和持续时间(iostat
;与您的硬件规格进行比较),以及您进程的 CPU 利用率。
[编辑] 假设您从本地连接的 SSD 读取数据(缓存中没有您需要的数据):
- 在单线程中读取时,50,000 次读取的批处理时间应该超过 7 秒 (50000*0.000150)。可能更长,因为对 mmap 文件的 50k 次访问会触发更多或更大的读取,因为您的访问不是页面对齐的——正如我在另一个问答中所建议的那样,我会使用简单的
seek
/read
相反(open
带有buffering=0
的文件以避免不必要的读取 Python 缓冲 I/O)。 - 随着更多 threads/processes 同时读取,您可以使您的 SSD 吞吐量饱和(它可以处理多少 4KB reads/s - 它可以在 5,000 到 1,000,000 之间的任何地方),然后单个读取将变为甚至更慢。
[/edit]
第一个示例仅访问 3*100KB 的文件数据,因此您有比缓存可用的更多的数据,所有 300KB 很快就会进入缓存,因此您将看不到 I/O,并且您的 python 进程将 CPU-绑定。
我有 99.99% 的把握,如果您测试从每个文件的最后 100KB 读取数据,它的性能将与第一个示例一样好——这与数据的位置无关,而是关于访问数据的大小。
第二个示例访问 9GB 中的随机部分,因此只有当您有足够的可用 RAM 来缓存所有 9GB 并且只有在将文件预加载到缓存中之后,您才希望看到类似的性能,以便测试用例 运行s 为零 I/O.
在实际情况下,文件不会完全在缓存中 - 因此您会看到许多 I/O 请求和 python 的低得多的 CPU 利用率。由于 I/O 比缓存访问慢得多,您应该期望此示例 运行 慢。