为什么os.lseek()比类似文件的对象上的seek()慢?

时间:2016-02-20 20:16:06

标签: python macos python-2.7

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

$ 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。

1 个答案:

答案 0 :(得分:5)

低级别并不一定意味着更快。它只是意味着低级别。鉴于python主要用于高级别的使用,高级API通常都经过了相当优化,可以避免在编写“等效”低级代码时遇到的陷阱。

现在os.open返回一个文件描述符,这是一个整数,实际上是在系统调用周围传递的(这就是为什么它被称为低级别。你通常不会'我想直接处理文件描述符并将其留给解释器。)

open函数返回file个对象。搜索方法的实现可以找到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

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

  1. 整数在正确的范围内
  2. 它引用现有的打开文件描述符
  3. 使用文件对象时,您可以假设与文件对象关联的文件描述符有效并避免检查。

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

    还有第三种寻找文件的方法,即使用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倍。