CとPythonでO_SYNCファイル書き込み
概要
CとPythonでファイル書き込みの時間コストを比較する。
存在する適当なファイル('DRIVER')に対して、毎回ファイルの先頭から適当なデータ('COMMAND')を、適当な回数(10000回)だけ書き込んだ場合の1回当たりの書き込みにかかる平均時間コストを計測する。
(ファイルのopen/closeは時間コストに含めない。)
毎回ファイルの先頭からデータを書き込む理由は後述の背景のため。
背景
31313 マインドストーム EV3 で ev3dev を動作させ、モータ制御をする際にデバイスファイルに命令を送るのだが、その処理に限定してCとPythonで速度に差異があるか気になった。
- 31313 マインドストーム EV3 (https://www.lego.com/ja-jp/mindstorms/products/mindstorms-ev3-31313)
- ev3dev (http://www.ev3dev.org/)
準備
環境は以下の通り。
- macOS: 10.12.4
software:
- cc: Apple LLVM version 8.1.0 (clang-802.0.41)
- python: 3.6.1
作成したコードは以下の通り。Pythonのファイル操作APIがいくつかあったので、3種類(io.FileIO, io.BaseIO, os)試してみる。
test_syncw.c
// cc -Weverything -Ofast test_syncw.c -o test_syncw
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define TEST_COUNT 10000
#define DRIVER_FILE "DRIVER"
#define COMMAND "COMMAND"
int main(__attribute__((unused)) int argc, __attribute__((unused)) char* argv[]) {
int fd = open(DRIVER_FILE, O_WRONLY | O_SYNC);
do {
clock_t elapsed_time;
do {
clock_t start_clock = clock();
for (int i = 0; i < TEST_COUNT; ++i) {
lseek(fd, 0, SEEK_SET);
write(fd, COMMAND, sizeof(COMMAND));
}
elapsed_time = clock() - start_clock;
} while(0);
printf("%lfus\n", (double) elapsed_time / TEST_COUNT * (1000000.0 / CLOCKS_PER_SEC));
} while(0);
close(fd);
return EXIT_SUCCESS;
}
test_syncw.py
import os
import io
import datetime
import time
TEST_COUNT = 10000
DRIVER_FILE = 'DRIVER'
COMMAND = b'COMMAND'
def test_with_fileio():
elapsed_time = -1
with io.FileIO(DRIVER_FILE, mode='w') as file:
start = time.time()
for i in range(TEST_COUNT):
file.seek(0)
file.write(COMMAND)
file.flush()
elapsed_time = (time.time() - start) * 1000000
file.close()
print(f'io.FileIO: {elapsed_time / TEST_COUNT}us')
def test_with_iobase():
elapsed_time = -1
with open(DRIVER_FILE, mode='wb', buffering=0) as file:
start = time.time()
for i in range(TEST_COUNT):
file.seek(0)
file.write(COMMAND)
file.flush()
elapsed_time = (time.time() - start) * 1000000
file.close()
print(f'io.IOBase: {elapsed_time / TEST_COUNT}us')
def test_with_os():
elapsed_time = -1
fd = os.open(DRIVER_FILE, flags=(os.O_WRONLY | os.O_SYNC))
start = time.time()
for i in range(TEST_COUNT):
os.lseek(fd, 0, os.SEEK_SET)
os.write(fd, COMMAND)
os.close(fd)
elapsed_time = (time.time() - start) * 1000000
print(f'os: {elapsed_time / TEST_COUNT}us')
if __name__ == '__main__':
test_with_fileio()
test_with_iobase()
test_with_os()
実験
コードを実行してみる。適当なファイル('DRIVER')は予め作成する必要がある。
$ touch DRIVER
$ cc -Weverything -Ofast test_syncw.c -o test_syncw
$ ./test_syncw
30.814900us
$ python test_syncw.py
io.FileIO: 3.4329us
io.IOBase: 3.3681us
os: 61.7254us
Pythonのio.FileIOとio.IOBaseはそもそもSYNCできていない。PythonのosはCよりも遅いが、これが比較するべき時間コストになる。
念のため5回繰り返してみた。
C | Python (os) | |
---|---|---|
1 | 33.2873us | 58.1087us |
2 | 33.6538us | 72.6557us |
3 | 33.2398us | 64.4080us |
4 | 34.0902us | 60.7077us |
5 | 34.0923us | 62.3773us |
Pythonの方がCの2倍くらい時間がかかるけど、絶対値的にはそんなに気になるレベルではないと思う。