Автор Тема: Буферизация ввода/вывода  (Прочитано 27445 раз)

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Буферизация ввода/вывода
« Ответ #45 : Январь 17, 2013, 12:17:27 pm »
Не должно зависеть от выбранного ЯП. Только от реализации системных вызовов.
Дьявол в нюансах.
Y = λf.(λx.f (x x)) (λx.f (x x))

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Буферизация ввода/вывода
« Ответ #46 : Январь 17, 2013, 12:20:13 pm »
А если не видно разницы, зачем платить больше? :-)

Я на BB тоже вчера проверял (только на чтение 1,8ГБ)  :)
2 - 28876 миллисек
4 - 27846 миллисек
8 -  27815 миллисек
2 - это 2 Кб, или таки 2 байта?

И как читалось - побайтово, или как-то иначе? Тут есть разница. Если не сложно, то неплохо бы привести код (чтобы другие у себя могли проверить)
Y = λf.(λx.f (x x)) (λx.f (x x))

Romiras

  • Sr. Member
  • ****
  • Сообщений: 264
    • Просмотр профиля
    • Romiras Dev Lab
Re: Буферизация ввода/вывода
« Ответ #47 : Январь 17, 2013, 12:21:58 pm »
Надо понимать, что между этапом где данные, находящиеся на жестком диске, и этапом их проекции в память лежат несколько прослоек, начиная от драйвера ФС, обращающегося к ФС и до аппликации, использующей системные вызовы. А программный буфер - это штука чисто виртуальная. Она должна влиять лишь на объём потребляемой памяти программой.
Таковы мои соображения.

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Буферизация ввода/вывода
« Ответ #48 : Январь 17, 2013, 12:24:35 pm »
Кстати, свой эксперимент я признаю неудовлетворительным. Fprintf слишком долго думает самостоятельно. Заменил на тупой вызов WriteByte - стало работать в 20 раз быстрее.

Так что сейчас будет серия новых экспериментов.
Y = λf.(λx.f (x x)) (λx.f (x x))

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Буферизация ввода/вывода
« Ответ #49 : Январь 17, 2013, 12:26:02 pm »
А программный буфер - это штука чисто виртуальная. Она должна влиять лишь на объём потребляемой памяти программой.
Таковы мои соображения.

Оставленное в цитате - не правильные соображение. Програмный буфер значительно влияет на производительность за счет снижения числа системных вызовов - системный вызов это большие тормоза.
Y = λf.(λx.f (x x)) (λx.f (x x))

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Буферизация ввода/вывода
« Ответ #50 : Январь 17, 2013, 12:32:08 pm »
В HostFiles меняем размер буфера и линкуем новый exe'шник BB.

Читаются побайтово файлы из папки с:\temp:

MODULE ilovbTestIO;
    IMPORT
        Log, Files, HostFiles;

    PROCEDURE Do*;
        VAR
            loc: Files.Locator;
            file: Files.File;
            list: Files.FileInfo;
            rd: Files.Reader;
            b: BYTE;
    BEGIN
        loc := HostFiles.NewLocator("c:\temp\");
        list := Files.dir.FileList(loc);
        WHILE list # NIL DO
            file := Files.dir.Old(loc, list.name, Files.shared);
            rd := file.NewReader(NIL);
            rd.SetPos(0);
            REPEAT
                rd.ReadByte(b);
            UNTIL rd.eof;
            list := list.next;
        END;
       
    END Do;
   
BEGIN
END ilovbTestIO.
^Q DevProfiler.Execute ilovbTestIO.Do

^Q DevLinker.Link
BlackBox4.exe := Kernel$+ Files HostFiles StdLoader
1 Applogo.ico 2 Doclogo.ico 3 SFLogo.ico 4 CFLogo.ico 5 DtyLogo.ico
1 Move.cur 2 Copy.cur 3 Link.cur 4 Pick.cur 5 Stop.cur 6 Hand.cur 7 Table.cur

Romiras

  • Sr. Member
  • ****
  • Сообщений: 264
    • Просмотр профиля
    • Romiras Dev Lab
Re: Буферизация ввода/вывода
« Ответ #51 : Январь 17, 2013, 12:41:37 pm »
Програмный буфер значительно влияет на производительность за счет снижения числа системных вызовов
Под системным вызовом я понимаю вызов системного API. Он, в свою очередь, должен обеспечивать чтение блоков данных без задержек, при помощи внутреннего (системного) буфера.

Далее. Касательно побайтового чтения. Это лишь установка позиции в программном буфере. Так что не должно влиять на скорость чтения данных.

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Буферизация ввода/вывода
« Ответ #52 : Январь 17, 2013, 12:42:54 pm »
Далее. Касательно побайтового чтения. Это лишь установка позиции в программном буфере. Так что не должно влиять на скорость чтения данных.
+1

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Буферизация ввода/вывода
« Ответ #53 : Январь 17, 2013, 12:44:38 pm »
2 - это 2 Кб, или таки 2 байта?
КБ

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Буферизация ввода/вывода
« Ответ #54 : Январь 17, 2013, 01:39:28 pm »
А программный буфер - это штука чисто виртуальная. Она должна влиять лишь на объём потребляемой памяти программой.
Таковы мои соображения.

Оставленное в цитате - не правильные соображение. Програмный буфер значительно влияет на производительность за счет снижения числа системных вызовов - системный вызов это большие тормоза.

Исправляюсь. Теперь эксперимент более чистый. Причем на чтение.

Буфер у меня, как понимаете, програмный. На стороне приложения. К системе отношения не имеющий.

Исходник такой:
package main

import "fmt"
import "os"
import "bufio"

func main() {
file, err := os.Open("1000000000.txt")
if err!= nil {
fmt.Print(err)
return
}
r := bufio.NewReaderSize(file,16)
for i:=0; i<1000000000; i++ {
r.ReadByte()
}
}

Вариация для чтения без буфера:
func main() {
file, err := os.Open("1000000000.txt")
if err!= nil {
fmt.Print(err)
return
}
var buf []byte = make([]byte, 1)
for i:=0; i<1000000000; i++ {
file.Read(buf)
}
}

Результаты:
64*1024:
Цитировать
ETime(   0:00:07.977 ) UTime(   0:00:07.456 ) KTime(   0:00:00.499 )
ITime(   0:00:00.000 )

4096:
Цитировать
ETime(   0:00:08.935 ) UTime(   0:00:07.347 ) KTime(   0:00:01.591 )
ITime(   0:00:00.000 )

1234:
Цитировать
ETime(   0:00:11.981 ) UTime(   0:00:08.892 ) KTime(   0:00:03.010 )
ITime(   0:00:00.000 )

1031 (простое число):
Цитировать
ETime(   0:00:12.444 ) UTime(   0:00:08.034 ) KTime(   0:00:04.414 )
ITime(   0:00:00.000 )
Но тут все сложно на самом деле - время скачет при этом размере буфера довольно сильно - бывает что подскакивает аж до 17ти секунд.

1024:
Цитировать
ETime(   0:00:12.423 ) UTime(   0:00:08.236 ) KTime(   0:00:04.180 )
ITime(   0:00:00.000 )
А вот тут - стабильно. Похоже, прыжки - особенность простых чисел. Но это конечно требует дополнительного исследования.

1000 (кратно размеру файла (в байтах)):
Цитировать
ETime(   0:00:12.719 ) UTime(   0:00:08.860 ) KTime(   0:00:03.837 )
ITime(   0:00:00.000 )

907 (простое число)
Цитировать
ETime(   0:00:13.378 ) UTime(   0:00:09.172 ) KTime(   0:00:04.056 )
ITime(   0:00:00.000 )
Прыжков нет. Все стабильно. Ничего не понимаю.

400 байт:
Цитировать
ETime(   0:00:19.790 ) UTime(   0:00:10.264 ) KTime(   0:00:09.500 )
ITime(   0:00:00.000 )

16:
Цитировать
ETime(   0:05:27.683 ) UTime(   0:01:21.962 ) KTime(   0:03:52.878 )
ITime(   0:00:00.000 )

Без буфера (буфера приложения, системный кеш я не отключал, а отключить кеш самого hdd не представляется возможным) вообще. Поскольку я не смог дождаться на гигабайтном файле (точнее на 1000000000 байтном), я уменьшил размер в 100 раз:
Цитировать
ETime(   0:00:48.116 ) UTime(   0:00:10.561 ) KTime(   0:00:37.549 )
ITime(   0:00:00.000 )
Соответственно и времена нужно умножить на 100.

Итого сводная табличка:
Буфер Full T System T
65536 7.977 0,499
4096 8.935 1,591
1234 11.981 3.01
1031 12.444 4.414
1024 12.423 4.18
1000 12.719 3.837
907 13.378 4.056
400 19.939 9.874
16 327.683 232.878
0 48116.0 37549.0

Итак, програмный буфер (буфер который ничего не знает о ФС, который у нас в программе и про который ни ФС ни драйвер диска ни система ничего не знает) существенно влияет на производительность. Минимальный разумный размер такого буфера - порядка 1000 байт. И само это число не обязано быть степенью двойки. Единственное - следует избегать простых чисел.

Что-то осталось не ясным? :-)
Y = λf.(λx.f (x x)) (λx.f (x x))

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Буферизация ввода/вывода
« Ответ #55 : Январь 17, 2013, 01:48:29 pm »
А самое оптимальное таки 4096  :D

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Буферизация ввода/вывода
« Ответ #56 : Январь 17, 2013, 01:50:23 pm »
Что-то осталось не ясным? :-)
Ну в общем то все так, как и ожидалось.

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Буферизация ввода/вывода
« Ответ #57 : Январь 17, 2013, 01:57:07 pm »
А самое оптимальное таки 4096  :D
Ну, оно столь же оптимально, как и 4000 или там, не знаю, 4123 :-)

Ну и опять таки - все ж зависит от задачи. Кому то может 64К понадобиться. Все же оно дает выигрыш.

Вообще, мне нравится подход Go, точнее принятый в библиотеке Go'шной - вот есть файл, если нужен буферизированный ввод-вывод - вот тебе еще одна либа, прикручивай (в одну строчку) сверху буферизирующую насадку.

Таким образом каждая либа делает только что-то узкое (например работает с файлом, или занимается буферизацией), при этом их можно скрещивать получая что-то новое (например буферизированный ввод-вывод для файлов). Причем для каждого файла с которым ты работаешь можно все это проделать индивидуально. И размеры буфера будет индивидуально для каждого свой (если нужно).
Y = λf.(λx.f (x x)) (λx.f (x x))

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Буферизация ввода/вывода
« Ответ #58 : Январь 17, 2013, 05:30:12 pm »
Ну и чтобы была понятна разница между наличием системного буфера и его отсутствием, отрезаем буферизацию полностью. Ну, то есть не полностью, но заставляем систему в буфере ничего не держать, и сразу писать на диск:

Буфер 16 байт:
Цитировать
ETime(   0:00:00.163 ) UTime(   0:00:00.031 ) KTime(   0:00:00.124 )
ITime(   0:00:00.000 )

Буфер 16 байт, fsync на каждый байт:
Цитировать
ETime(   0:01:06.595 ) UTime(   0:00:01.014 ) KTime(   0:00:16.380 )
ITime(   0:00:00.000 )

Без буфера (без bufio):
Цитировать
ETime(   0:00:02.256 ) UTime(   0:00:00.608 ) KTime(   0:00:01.653 )
ITime(   0:00:00.000 )

Без буфера (без bufio) и с fsync:
Цитировать
ETime(   0:13:58.277 ) UTime(   0:00:06.396 ) KTime(   0:02:05.721 )
ITime(   0:00:00.000 )

Сводная табличка (размер записываемого файла - мегабайт):
Buf size | fsync |  Time (sec)
---------+-------+------------
16       |   -   |   0.163
16       |   +   |  66.595
 0       |   -   |   2.256
 0       |   +   | 838.227

Как видим, Аксенов был прав - fsync это зло. А ведь именно так сконфигурирован MySql + innodb по умолчанию. Зато надежно :-)

Также видим, что собственный буфер спасает (разница на два порядка) даже если fsync работает (то есть системный буфер по сути не используется). А системный буфер спасает еще на пару порядков.

PS. Странное дело - если я создаю файл, и файл такой уже существует (а создаю я его с truncate), то время записи в два раза больше. И это вне зависимости от буферизации и fsync.
Y = λf.(λx.f (x x)) (λx.f (x x))

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Буферизация ввода/вывода
« Ответ #59 : Январь 17, 2013, 05:49:24 pm »
Также видим, что собственный буфер спасает (разница на два порядка) даже если fsync работает (то есть системный буфер по сути не используется). А системный буфер спасает еще на пару порядков.
Ошибочка. Собственный буфер дает, в данном случае, выигрыш на порядок а не на два. А если его сделать нормального размера, то на полтора порядка.
Y = λf.(λx.f (x x)) (λx.f (x x))