为什么使用Python mmap模块要比从C ++调用POSIX mmap慢得多?

时间:2016-07-31 09:18:00

标签: python c++ performance posix mmap

C ++代码:

#include <string>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <sys/time.h>

using namespace std;
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

int main() {
    timeval tv1, tv2, tv3, tve;
    gettimeofday(&tv1, 0);
    int size = 0x1000000;
    int fd = open("data", O_RDWR | O_CREAT | O_TRUNC, FILE_MODE);
    ftruncate(fd, size);
    char *data = (char *) mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    for(int i = 0; i < size; i++) {
        data[i] = 'S';
    }
    munmap(data, size);
    close(fd);
    gettimeofday(&tv2, 0);
    timersub(&tv2, &tv1, &tve);
    printf("Time elapsed: %ld.%06lds\n", (long int) tve.tv_sec, (long int) tve.tv_usec);
}

Python代码:

import mmap
import time

t1 = time.time()
size = 0x1000000

f = open('data/data', 'w+')
f.truncate(size)
f.close()

file = open('data/data', 'r+b')
buffer = mmap.mmap(file.fileno(), 0)

for i in xrange(size):
    buffer[i] = 'S'

buffer.close()
file.close()
t2 = time.time()
print "Time elapsed: %.3fs" % (t2 - t1)

我认为这两个程序基本相同,因为C ++和Python调用相同的系统调用(mmap)。

但是Python版本比C ++慢得多:

Python: Time elapsed: 1.981s
C++:    Time elapsed: 0.062143s

有没有人可以解释为什么mmap Python比C ++慢得多?

环境:

C ++:

$ c++ --version
Apple LLVM version 7.3.0 (clang-703.0.31)
Target: x86_64-apple-darwin15.5.0

的Python:

$ python --version
Python 2.7.11 :: Anaconda 4.0.0 (x86_64)

2 个答案:

答案 0 :(得分:6)

不是mmap更慢,而是用值填充数组。众所周知,Python在进行基本操作时会很慢。使用更高级别的操作:

buffer[:] = 'S' * size

答案 1 :(得分:3)

详细说明@Daniel所说的内容 - 任何Python操作都有比使用C ++实现解决方案的可比代码量更多的开销(在某些情况下方式更多,如数量级)。

填充缓冲区的循环确实是罪魁祸首 - 而mmap模块本身也有比你想象的要多得多的内务处理,尽管它提供了一个界面,其语义是错误的,错误的,紧密排列的与POSIX mmap()。你知道POSIX mmap()只是向你扔了void*(你必须使用munmap()来清理它,在某些时候)? Python的mmap必须分配一个PyObject结构来监督void* - 通过为运行时提供元数据和回调,传播和排队读写,维护GIL,使其符合Python的缓冲协议状态,无论发生什么错误都要清理其分配......

所有这些都需要时间和记忆。我个人从来没有发现自己使用mmap模块,因为它没有为任何I / O问题提供明确的优势,比如开箱即用 - 你可以很容易使用mmap可以减慢速度,因为可以加快速度。

相比之下我经常*做*发现使用POSIX mmap() 在从Python C / C ++扩展中进行I / O时非常有用(前提是你是注意GIL状态),正是因为 mmap() 周围的编码首先避免了所有Python内部基础设施的东西。