Пишу свой супер-пупер производительный парсер на CL под sbcl, GNU/Linux. И давно вот заметил что кусками равного размера чтение из файла идёт быстрее чем если читать поток по символу.
(in-package cl-user)
(defpackage buffering-tests
(:use :cl))
(in-package buffering-tests)
(defparameter *test-text*
(merge-pathnames #P"Desktop/buffering-test/text.txt"
(user-homedir-pathname)))
(defun count-chars (file)
(with-open-file (in file)
(loop :for c = (read-char in nil nil)
:while c
:counting 1)))
(defparameter *default-buffer-size*
(floor (* 4 (expt 2 10))))
(defun count-chars-with-buffering (file
&optional (buffer-size *default-buffer-size*))
(with-open-file (in file)
(loop :with buffer = (make-string buffer-size)
:for e = (read-sequence buffer in)
:while (plusp e)
:summing e)))
BUFFERING-TESTS> (let (r)
(sb-ext:gc :full t)
(time (dotimes (i 15)
(setf r
(count-chars *test-text*))))
r)
Evaluation took:
1.514 seconds of real time
1.512376 seconds of total run time (1.490228 user, 0.022148 system)
99.87% CPU
3,487,695,298 processor cycles
63,488 bytes consed
6210660
BUFFERING-TESTS> (let (r)
(sb-ext:gc :full t)
(time (dotimes (i 15)
(setf r
(count-chars-with-buffering *test-text*))))
r)
Evaluation took:
0.693 seconds of real time
0.692603 seconds of total run time (0.669793 user, 0.022810 system)
100.00% CPU
1,597,583,804 processor cycles
323,520 bytes consed
6210660
Разница есть если даже пытаться сравнять количество итераций:
(defun count-chars-with-buffering (file
&optional (buffer-size *default-buffer-size*))
(with-open-file (in file)
(loop :with buffer = (make-string buffer-size)
:for e = (read-sequence buffer in)
:while (plusp e)
:summing
(loop :repeat e
:for i :upfrom 0
:for c = (schar buffer i)
:when c
:counting 1))))
BUFFERING-TESTS> (let (r)
(sb-ext:gc :full t)
(time (dotimes (i 15)
(setf r
(count-chars-with-buffering *test-text*))))
r)
Evaluation took:
1.193 seconds of real time
1.191737 seconds of total run time (1.172685 user, 0.019052 system)
99.92% CPU
2,748,553,404 processor cycles
323,520 bytes consed
6210660
BUFFERING-TESTS>
В парсере это привело к созданию прослойки над потоками - объекта, содержащего ссылку на поток, буфер и инфу откуда программе начинать чтение. Остаётся вопрос - для чего так реализовано? Недостающая оптимизация sbcl? В других реализациях других языков тоже такая разница? Понятно что сварганить вышеописанный объект может любой программист в два счёта, но почему бы не сделать это в рамках реализации чтобы не было разницы посимвольно ты читаешь поток или кусками? Почему бы эту абстракцию не взять на себя ядру, у которого и так в IO буфер буфером погоняет?