【问题标题】:Why is os.lseek() slower than seek() on file-like objects?为什么 os.lseek() 在类文件对象上比 seek() 慢?
【发布时间】:2016-02-20 20:16:05
【问题描述】:

在 Python 中,为什么 os.lseek()file-like objects 上的 seek() 方法慢这么多?

$ dd if=/dev/urandom of=test.bin bs=1024 count=1024
1024+0 records in
1024+0 records out
1048576 bytes transferred in 0.063247 secs (16579072 bytes/sec)
$ python -m timeit -s 'import os; f = open("test.bin", "r")' 'for i in xrange(10000): f.seek(i, os.SEEK_SET)'
100 loops, best of 3: 2.62 msec per loop
$ python -m timeit -s 'import os; f = os.open("test.bin", os.O_RDONLY)' 'for i in xrange(10000): os.lseek(f, i, os.SEEK_SET)'
100 loops, best of 3: 4.23 msec per loop

docs for os.open() 表示“此函数适用于低级 I/O。”我认为“低级 I/O”会更快。

我在带有固态驱动器的 MacBook Pro 上的 Mac OS 10.10.5 上使用 CPython 2.7.9。

【问题讨论】:

    标签: python macos python-2.7


    【解决方案1】:

    低级并不一定意味着更快。它只是意味着低级。鉴于 python 主要用于高级用途,高级 API 通常经过优化,避免了编写“等效”低级代码时必须处理的陷阱。

    现在os.open 返回一个文件描述符,它是一个整数,它实际上是在系统调用中传递的(这就是它被称为低级的原因。你通常不希望直接处理文件描述符并将其留给解释器。)

    open 函数返回一个file 对象。 seek 方法的实现可以在here 找到,它非常简单:它会进行一些错误检查,最后调用_portable_fseek

    Py_DECREF(off_index);
    if (PyErr_Occurred())
        return NULL;
    
    FILE_BEGIN_ALLOW_THREADS(f)
    
    errno = 0;
    ret = _portable_fseek(f->f_fp, offset, whence);
    FILE_END_ALLOW_THREADS(f)
    
    if (ret != 0) {
        PyErr_SetFromErrno(PyExc_IOError);
        clearerr(f->f_fp);
        return NULL;
    }
    
    f->f_skipnextlf = 0;
    Py_INCREF(Py_None);
    return Py_None;
    

    其中_portable_fseek 定义为here,其实现为 真的只是:

    static int
    _portable_fseek(FILE *fp, Py_off_t offset, int whence)
    {
    #if !defined(HAVE_LARGEFILE_SUPPORT)
        return fseek(fp, offset, whence);
    
    #elif defined(HAVE_FSEEKO) && SIZEOF_OFF_T >= 8
        return fseeko(fp, offset, whence);
    
    #elif defined(HAVE_FSEEK64)
        return fseek64(fp, offset, whence);
    
    #elif defined(__BEOS__)
        return _fseek(fp, offset, whence);
    
    #elif SIZEOF_FPOS_T >= 8
        /* lacking a 64-bit capable fseek(), use a 64-bit capable fsetpos()
           and fgetpos() to implement fseek()*/
        fpos_t pos;
        switch (whence) {
        case SEEK_END:
    #ifdef MS_WINDOWS
            fflush(fp);
            if (_lseeki64(fileno(fp), 0, 2) == -1)
                return -1;
    #else
            if (fseek(fp, 0, SEEK_END) != 0)
                return -1;
    #endif
            /* fall through */
        case SEEK_CUR:
            if (fgetpos(fp, &pos) != 0)
                return -1;
            offset += pos;
            break;
        /* case SEEK_SET: break; */
        }
        return fsetpos(fp, &offset);
    #else
    #error "Large file support, but no way to fseek."
    #endif
    }
    

    os.lseek 函数被定义为 here,它几乎是相同的代码,除了它这样做:

        if (!_PyVerify_fd(fd))
            return posix_error();
        Py_BEGIN_ALLOW_THREADS
    #if defined(MS_WIN64) || defined(MS_WINDOWS)
        res = _lseeki64(fd, pos, how);
    #else
        res = lseek(fd, pos, how);
    #endif
        Py_END_ALLOW_THREADS
    

    注意对_PyVerify_fd的调用!

    您可以使用 any 整数对象调用 os.lseek,因此解释器必须验证:

    1. 整数在正确的范围内
    2. 它引用了一个现有的打开文件描述符

    当使用文件对象时,你可以假设与文件对象关联的文件描述符是有效的并且避免检查。

    因此在这种情况下,低级函数实际上必须执行更多的错误检查,从而使操作变慢。


    还有第三种查找文件的方法,即使用io 库。结果是:

    $ dd if=/dev/urandom of=test.bin bs=1024 count=1024
    1024+0 record dentro
    1024+0 record fuori
    1048576 byte (1,0 MB) copiati, 0,0851599 s, 12,3 MB/s
    $ python2 -m timeit -s 'import io;import os; f=open("test.bin", "r")' 'for i in xrange(10000): f.seek(i, os.SEEK_SET)'
    100 loops, best of 3: 5.72 msec per loop
    $ python2 -m timeit -s 'import io;import os; f=os.open("test.bin", os.O_RDONLY)' 'for i in xrange(10000): os.lseek(f, i, os.SEEK_SET)'
    100 loops, best of 3: 6.28 msec per loop
    $ python2 -m timeit -s 'import io;import os; f=io.open("test.bin", "r")' 'for i in xrange(10000): f.seek(i, os.SEEK_SET)'
    10 loops, best of 3: 63.8 msec per loop
    

    它们比普通文件花费的时间多 10 倍!但是,如果您查看它们是如何实现的 here,您会发现它们的实现使用了相当高级的 API,并且与纯 C 版本相比引入了相当多的开销。

    另请注意,在我的机器上,os.lseekseek 之间没有 2 倍的差异。

    【讨论】:

      猜你喜欢
      • 2014-10-24
      • 1970-01-01
      • 1970-01-01
      • 2011-03-23
      • 2022-01-06
      • 2013-10-03
      • 2014-10-03
      • 1970-01-01
      相关资源
      最近更新 更多