Last active
July 17, 2016 17:18
-
-
Save lazka/78bdfdbf8b01f1514f1c801acf321930 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
# Copyright 2016 Christoph Reiter | |
# | |
# This program is free software; you can redistribute it and/or modify | |
# it under the terms of the GNU General Public License version 2 as | |
# published by the Free Software Foundation. | |
import os | |
import sys | |
import time | |
import ctypes | |
import platform | |
import errno | |
from ctypes import cdll, CDLL, c_long, c_int, c_uint64, POINTER, c_size_t, \ | |
c_uint, byref, c_ssize_t | |
from mutagen._util import mmap_move | |
from mutagen._compat import xrange | |
c_loff_t = c_uint64 | |
def get_copy_file_range_func(_cache=[]): | |
"""Returns a callable or None""" | |
if _cache: | |
return _cache[0] | |
def ret(value): | |
_cache.append(value) | |
return value | |
if not sys.platform.startswith("linux"): | |
return ret(None) | |
try: | |
lib = CDLL("libc.so.6", use_errno=True) | |
except OSError: | |
return ret(None) | |
syscall = lib.syscall | |
syscall.argtypes = [ | |
c_long, c_int, POINTER(c_loff_t), c_int, POINTER(c_loff_t), | |
c_ssize_t, c_uint] | |
syscall.restype = c_ssize_t | |
# See https://github.com/systemd/systemd/blob/ | |
# 8869a0b40b1cf82d264cabc2ff8052e8e3/src/basic/missing_syscall.h#L270 | |
syscall_nums = { | |
"x86_64": 326, | |
"i386": 377, | |
"s390": 375, | |
"arm": 391, | |
"aarch64": 285, | |
} | |
machine = platform.machine() | |
try: | |
NR_copy_file_range = syscall_nums[machine] | |
except KeyError: | |
return ret(None) | |
return ret(lambda *args: syscall(NR_copy_file_range, *args)) | |
def copy_file_range(fd_in, off_in, fd_out, off_out, length, flags): | |
""" | |
Args: | |
fd_in (int): File descriptor to read from | |
off_in (int): Offset to read from or None to use the file offset | |
fd_out (int): File descriptor to write to | |
off_out (int): Offset to write to or None to use the file offset | |
length (int): Amount of bytes to copy | |
flags (int): always 0 | |
Will either copy everything or raise OSError. See OSError.errno for the | |
error code. | |
For more info see ``man 2 copy_file_range`` | |
""" | |
if off_in is not None: | |
off_in_v = c_loff_t(off_in) | |
off_in = byref(off_in_v) | |
if off_out is not None: | |
off_out_v = c_loff_t(off_out) | |
off_out = byref(off_out_v) | |
syscall = get_copy_file_range_func() | |
if syscall is None: | |
raise OSError(errno.ENOSYS, os.strerror(errno.ENOSYS)) | |
i = 0 | |
bytes_copied = -1 | |
while length > 0: | |
# off_in_v, off_out_v will be adjusted by the syscall | |
bytes_copied = syscall(fd_in, off_in, fd_out, off_out, length, flags) | |
if bytes_copied == -1: | |
err = ctypes.get_errno() | |
raise OSError(err, os.strerror(err)) | |
length -= bytes_copied | |
if bytes_copied == 0 and length != 0: | |
# https://bugzilla.kernel.org/show_bug.cgi?id=135451 | |
if os.fstat(fd_in).st_size == off_in_v.value: | |
raise OSError(errno.EINVAL, os.strerror(errno.EINVAL)) | |
def main(): | |
h = open("dsafdsafsafdf.txt", "wb+") | |
for i in xrange(2000000): | |
h.write(b"abcdefghijklmnop\n") | |
h.flush() | |
h.seek(0, 2) | |
size = h.tell() | |
fd = h.fileno() | |
N = 200 | |
os.fsync(fd) | |
t = time.time() | |
for i in xrange(N): | |
mmap_move(h, 1, 0, size - 1) | |
print(time.time() - t, "mmap") | |
os.fsync(fd) | |
t = time.time() | |
for i in xrange(N): | |
copy_file_range(fd, 0, fd, 1, size - 1, 0) | |
print(time.time() - t, "copy_file_range") | |
h.close() | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment