Oberon space
General Category => Общий раздел => Тема начата: ilovb от Январь 16, 2013, 09:35:05 am
-
Обсуждение началось здесь: http://forum.oberoncore.ru/viewtopic.php?f=23&t=4214#p77131
Вопрос первый: Есть ли смысл заниматься оптимизацией буферизации I/O на современных жестких дисках в современных осях?
Вопрос второй: Почему в BB буфер имеет фиксированный размер 2 * 1024? Есть ли смысл в другом размере?
Вопрос третий: Есть ли смысл делать буфер под размер кластера? (если нет системного буфера)
-
Обсуждение началось здесь: http://forum.oberoncore.ru/viewtopic.php?f=23&t=4214#p77131
Вопрос первый: Есть ли смысл заниматься оптимизацией буферизации I/O на современных жестких дисках в современных осях?
Имеет, но только в крайне специфических случаях (например в случае если ты пишешь собственную высоконагруженную СУБД). Скажем в MySQL в случае движка MyISAM все кеширование отдано на откуп ОСи, в случае innodb - кеширование свое. То есть, видимо, когда твой кеш сможет работать эффективней просто потому, что ты точно знаешь паттерн типичных запросов к диску в твоем приложении (а кэш общего назначения естественно заточен под усредненный вероятностный паттерн).
В современных ОСях по умолчанию ВСЯ не используемая приложениями память (а в случае видны - еще больше) уходит автоматом под дисковый кэш.
Ну и не забываем что у винтов еще свой личноперсональный кэш имеется.
Вопрос второй: Почему в BB буфер имеет фиксированный размер 2 * 1024? Есть ли смысл в другом размере?
Вроде же в исходном топике сказали, что сложилось исторически.
Вопрос третий: Есть ли смысл делать буфер под размер кластера? (если нет системного буфера)
Чтобы избавиться от системного буфера нужно очень специально постараться.
IMHO это все экономия на спичках. Должно сильно повезти (реально повезти) чтобы попалась такая задача, где в обход системного кеша пришлось бы что-то там руками буферизировать.
Впрочем, зачем рассуждать, если можно просто проверить? Поставь эксперимент и не парься. :-)
-
Вопрос второй: Почему в BB буфер имеет фиксированный размер 2 * 1024? Есть ли смысл в другом размере?
В пакете bufio, обеспечивающем в Go буферизированный ввод/вывод дефолтный размер буфера - 4096 байт. Я полагаю это связано не с размером какого-то там кластера на диске, а с размером страницы памяти (4 Кб - пожалуй наиболее частовстречающийся размер страницы). В случае буфера важно обеспечить эффективность распределения и доступа к памяти.
http://golang.org/pkg/bufio/
http://golang.org/src/pkg/bufio/bufio.go
-
по моему, когда скорость ввода кванта данных и скорость вывода кванта данных не равны, то буфер необходим для эффективной работы. Размер же буфера должен определятся конкретной реализацией.
-
по моему, когда скорость ввода кванта данных и скорость вывода кванта данных не равны, то буфер необходим для эффективной работы. Размер же буфера должен определятся конкретной реализацией.
Так точно!
И важно понимать, что в данном случае диск совершенно вторичен. Буферизацию собственную делают лишь затем, чтобы поменьше системных вызовов было, а значит и переключений контекста. Системный вызов же какого-нибудь write обычно всего лишь перекладывает данные из нашего буфера в системный.
То есть fflush - это как раз оно, перекладывание данных из памяти приложения в систему.
А вот fsync уже вынудит систему все что есть в буфере положить на диск.
-
по моему, когда скорость ввода кванта данных и скорость вывода кванта данных не равны, то буфер необходим для эффективной работы. Размер же буфера должен определятся конкретной реализацией.
Так точно!
И важно понимать, что в данном случае диск совершенно вторичен. Буферизацию собственную делают лишь затем, чтобы поменьше системных вызовов было, а значит и переключений контекста. Системный вызов же какого-нибудь write обычно всего лишь перекладывает данные из нашего буфера в системный.
То есть fflush - это как раз оно, перекладывание данных из памяти приложения в систему.
А вот fsync уже вынудит систему все что есть в буфере положить на диск.
Да, и понятно тогда почему размер буфера делается кратным размеру страницы памяти а не кластеру диска - ведь нам нужно быстро скопировать шмат памяти отсюда туда, а постранично это будет быстрее всего.
-
По поводу 4096 вот тут написано:
http://msdn.microsoft.com/ru-ru/library/windows/desktop/cc644950(v=vs.85).aspx
-
Because buffer addresses for read and write operations must be sector-aligned, the application must have direct control of how these buffers are allocated. One way to sector-align buffers is to use the VirtualAlloc function to allocate the buffers.
-
...
/* Buffered I/O macros */
#define BUFSIZ 512
#ifndef _INTERNAL_IFSTRIP_
/*
* Real default size for stdio buffers
*/
#define _INTERNAL_BUFSIZ 4096
#define _SMALL_BUFSIZ 512
#endif /* _INTERNAL_IFSTRIP_ */
...
:D
-
По поводу 4096 вот тут написано:
http://msdn.microsoft.com/ru-ru/library/windows/desktop/cc644950(v=vs.85).aspx
Это не тот случай. В bufio никто не полагается на то, что системное кеширование будет отключено. Также никто не затачивается под винду. Отключение системного дискового кеша это крайне экстремальный случай.
Вопрос - в ББ FILE_FLAG_NO_BUFFERING используется?
Ну и еще раз - буферизация (собственная, на стороне приложения) имеет смысл даже если файл с которым работаем лежит на disk on ram - виртуальном диске в ОЗУ. Потому что сильная экономия на переключении контекстов. Помнишь тормоза в c++ коде в той задачке? Это было ровно из за этого - при чтение и запись было, грубо говоря, не буферизированном. Постоянные системные вызовы. Заметь, что там мой код на c++ ВООБЩЕ с файлами на диске не работал.
-
Это не тот случай. В bufio никто не полагается на то, что системное кеширование будет отключено. Также никто не затачивается под винду. Отключение системного дискового кеша это крайне экстремальный случай.
Вопрос - в ББ FILE_FLAG_NO_BUFFERING используется?
Дисковый кэш никто не отключает. FILE_FLAG_NO_BUFFERING - отключает системный буфер и кэш windows (это позволяет читать/писать асинхронно). В ББ этот флаг не используется.
А вот в MS SQL используется:
Профессиональное руководство по Microsoft SQL Server: структура и реализация (http://books.google.ru/books?id=kuBCF2bsW7YC&pg=PA268&dq=FILE_FLAG_NO_BUFFERING&hl=ru&sa=X&ei=hY33UNW4AciG4gTVpYC4Aw&ved=0CDQQ6AEwAA#v=onepage&q=FILE_FLAG_NO_BUFFERING&f=false)
Ничего страшного в отключении системного буфера нет. На Turbo Pascal ведь пользовались блочным вводом/выводом и никто не боялся :)
Ну и еще раз - буферизация (собственная, на стороне приложения) имеет смысл даже если файл с которым работаем лежит на disk on ram - виртуальном диске в ОЗУ. Потому что сильная экономия на переключении контекстов. Помнишь тормоза в c++ коде в той задачке? Это было ровно из за этого - при чтение и запись было, грубо говоря, не буферизированном. Постоянные системные вызовы. Заметь, что там мой код на c++ ВООБЩЕ с файлами на диске не работал.
А я и не спорю с этим :)
-
Это не тот случай. В bufio никто не полагается на то, что системное кеширование будет отключено. Также никто не затачивается под винду. Отключение системного дискового кеша это крайне экстремальный случай.
Вопрос - в ББ FILE_FLAG_NO_BUFFERING используется?
Дисковый кэш никто не отключает. FILE_FLAG_NO_BUFFERING - отключает системный буфер и кэш windows (это позволяет читать/писать асинхронно). В ББ этот флаг не используется.
А вот в MS SQL используется:
Профессиональное руководство по Microsoft SQL Server: структура и реализация (http://books.google.ru/books?id=kuBCF2bsW7YC&pg=PA268&dq=FILE_FLAG_NO_BUFFERING&hl=ru&sa=X&ei=hY33UNW4AciG4gTVpYC4Aw&ved=0CDQQ6AEwAA#v=onepage&q=FILE_FLAG_NO_BUFFERING&f=false)
Ничего страшного в отключении системного буфера нет. На Turbo Pascal ведь пользовались блочным вводом/выводом и никто не боялся :)
Я именно про системный дисковый кеш и говорил (я не говорил про кеш самого hdd).
Так вот, отключать его в 99.9% случаев смысла не имеет. Имеет смысл отключать только в о-очень редких случаях (см мой первый пост про MySQL). ББ - это не тот случай когда нужно его отключать.
Какую бы аналогию привести... Ну, вот например: пишешь ты на java, или там на c# каком-нибудь. И берешь, лезешь в рантайм и отключаешь автоматическое включение сборщика мусора, вместо этого сам начинаешь дергать GC тогда когда считаешь что пора уже собирать мусор. Или даже пишешь свой сборщик мусора, и заменяешь им штатный.
На всякий случай еще раз - буферизированный ввод-вывод используется в той же стандартной сишной либе не из за того, что это медленный диск на той стороне, а из за того, что это syscall'ы, число их и минимизируем. Ибо переключение контекстов, а это дорого.
-
Вот новость в тему: http://www.opennet.ru/opennews/art.shtml?num=35849
Так что с системным кешированием соревноваться смысла нет (и тем более его отключать). Точнее есть смысл в 0.01% задач.
-
На всякий случай еще раз - буферизированный ввод-вывод используется в той же стандартной сишной либе не из за того, что это медленный диск на той стороне, а из за того, что это syscall'ы, число их и минимизируем. Ибо переключение контекстов, а это дорого.
А если ты на Си под DOS пишешь? :)
-
На всякий случай еще раз - буферизированный ввод-вывод используется в той же стандартной сишной либе не из за того, что это медленный диск на той стороне, а из за того, что это syscall'ы, число их и минимизируем. Ибо переключение контекстов, а это дорого.
А если ты на Си под DOS пишешь? :)
Так там тоже вроде как через int 21h это делается. Нет? То есть через прерывание - а это медленно. Это рраз.
Два - системный буфер в ДОС-е тоже есть (как бишь эту системную стандартную утиль звали, которая его включает... не помню, надо же... А! Во! smartdrv)
-
Но я в принципе согласен что в современных осях производительности системного кэширования достаточно для большинства задач. Свой буфер имеет смысл делать только в экстремальных задачах.
-
Так там тоже вроде как через int 21h это делается. Нет? То есть через прерывание - а это медленно. Это рраз.
Два - системный буфер в ДОС-е тоже есть (как бишь эту системную стандартную утиль звали, которая его включает... не помню, надо же... А! Во! smartdrv)
Не шарю в тонкостях IO DOS. Но в TP вроде был свой буфер, и читалось/писалось из него прямо на диск (а не в какой-то системный буфер). И потому в TP блочный ввод вывод с буфером кратным размеру сектора давал ускорение на порядки за счет минимизации числа обращений к диску. То же самое в оригинальном Обероне.
-
Кроме того если ты не контролируешь буфер, то скорее всего будут проблемы при реализации транзакций например, ибо неизвестно на диске уже данные или нет.
-
Так там тоже вроде как через int 21h это делается. Нет? То есть через прерывание - а это медленно. Это рраз.
Два - системный буфер в ДОС-е тоже есть (как бишь эту системную стандартную утиль звали, которая его включает... не помню, надо же... А! Во! smartdrv)
Не шарю в тонкостях IO DOS. Но в TP вроде был свой буфер, и читалось/писалось из него прямо на диск (а не в какой-то системный буфер). И потому в TP блочный ввод вывод с буфером кратным размеру сектора давал ускорение на порядки за счет минимизации числа обращений к диску. То же самое в оригинальном Обероне.
А вот если запустить до того smartdrv, то разница будет уже не столь велика :-) То есть экономия будет просто за счет меньшего числа прерываний.
-
Ради интереса посмотрел размеры буферов в разных Оберонах:
1. А2 - 4 КБ
2. ETH Oberon - 4 КБ
3. Linz Oberon V4 - 1 КБ
4. оригинальный Oberon:
BufferRecord =
RECORD apos, lim: INTEGER;
mod: BOOLEAN;
next: Buffer;
data: FileDir.DataSector;
END
где:
DataSector = RECORD(Kernel.Sector)
B: ARRAY SectorSize OF SYSTEM.BYTE;
END
где:
SectorSize = 1024
-
Охохо. По моему, у нас тут идет смешение теплого с мягким.
Если мы пишем ОС, или работаем с блочным устройством на прямую, то критерии выбора размера буфера будет одни (будем подстраиваться под устройство оного блочного устройства).
Если мы пишем прикладное ПО, работающее на ОС у которой есть свой буфер, то критерии выбора размера буфера будут уже совсем другие. Ибо это буфер взаимодействия с ОС, а не буфер взаимодействия с блочным устройством.
Внимание вопрос - какой случай нас интересует?
-
Тык вот именно! Я это и пытаюсь понять. Во всех приведенных системах размер буфера выбран конкретного размера по объективным причинам, ибо все эти системы могут работать нативно. И там это дает существенный профит.
А вот почему буфер ББ такой непонятно. И непонятно почему он фиксированный. Кроме того буфер ББ - это не то же самое, что буфер Оберона, т.к. нет прямого обмена с файловой системой. Т.е. в ББ буфер пользовательский и его размер по идее не зависит от файловой системы. Системный буфер windows делает за ББ ту работу, которая лежала на буфере оригинального Оберона.
-
И кроме того, valexey, управление файлами и буферами во всех Оберонах одинаковое. В этом то и странность.
ps А системный буфер windows вообще 256КБ...
-
И кроме того, valexey, управление файлами и буферами во всех Оберонах одинаковое. В этом то и странность.
ps А системный буфер windows вообще 256КБ...
Да ладно? А это что такое?
(http://cdn.imghack.se/images/142d19b8289f5ea243584e21417e4502.png)
-
Это весь кэш. А я говорю про отдельный буфер:
As depicted by the solid arrows in the previous figure, a 256 KB region of data is read into a 256 KB cache "slot" in system address space when it is first requested by the cache manager during a file read operation. A user-mode process then copies the data in this slot to its own address space. When the process has completed its data access, it writes the altered data back to the same slot in the system cache, as shown by the dotted arrow between the process address space and the system cache. When the cache manager has determined that the data will no longer be needed for a certain amount of time, it writes the altered data back to the file on the disk, as shown by the dotted arrow between the system cache and the disk.
http://msdn.microsoft.com/ru-ru/library/windows/desktop/aa364218(v=vs.85).aspx
-
По аналогии - в ББ весь кэш 8 КБ на каждый файл, т.к. состоит из 4 буферов по 2 КБ
-
И кроме того, valexey, управление файлами и буферами во всех Оберонах одинаковое. В этом то и странность.
Ну, а что такого странного? Что 1024 что 4096 буфер будет работать лучше чем без буфера вообще (в случае если мы работаем поверх ОС). Впрочем, также неплохо будет работать и буфер размером в 1234 байта (но немного хуже, но чтобы это самое немного хуже обнаружить, придется сильно извратиться). Ибо экономим на syscall'ах. То есть выбор не принципиален для очень многих задач.
-
Странно что он такой маленький и фиксированный.
Размер 2 КБ ни туда ни сюда. Не оставили же они 1 КБ как в оригинале...
-
Т.е. для уменьшения частоты обмена с системным буфером логичнее было бы делать пользовательский буфер больше (32КБ - 64КБ), но они этого не сделали, и даже сделали размер неизменным...
-
Странно что он такой маленький и фиксированный.
Размер 2 КБ ни туда ни сюда. Не оставили же они 1 КБ как в оригинале...
Изучая исключительно io-шедулер NT ты не поймешь почему в ББ сделано именно так. Просто потому, что ББ не затачивался под NT.
ББ затачивался под Win9x и MacOS (классику). Поэтому, видимо, следует смотреть что было с кешированием там, и что там было с FS.
То есть решение по этой константе было принято когда-то тогда. Решение не менялось просто потому, что ни разу не встретилась задача, где это выстрелило бы.
-
Т.е. для уменьшения частоты обмена с системным буфером логичнее было бы делать пользовательский буфер больше (32КБ - 64КБ), но они этого не сделали, и даже сделали размер неизменным...
Палка о двух концах. Во-первых 64 Кб это уже много. Это ж буфер на один файл. Открой 100 файлов, и получи 6.4 Мб затрат ОЗУ. Это дофига, между прочим. Это больше чем воткнуто в типичную машину!
Во-вторых большой буфер - это больше вероятность потерять данные в случае падения приложения.
-
Моё предположение таково: размер кеша имеет прямое отношение и к размеру кластера на ФС. Который может быть 1кб и выше - зависит от нужды хранить много мелких файлов, для наиболее эффективного использования дискового пространства. А системный буфер является прослойкой между разнящимися размерами кластера и API чтения с файла. То есть он заведомо больше размера сектора.
-
Моё предположение таково: размер кеша имеет прямое отношение и к размеру кластера на ФС. Который может быть 1кб и выше - зависит от нужды хранить много мелких файлов, для наиболее эффективного использования дискового пространства. А системный буфер является прослойкой между разнящимися размерами кластера и API чтения с файла. То есть он заведомо больше размера сектора.
Размер кластера может быть 512 байт.
Можно сделать буфер размером в 1234 байта, существенной разницы с 1024 не будет. Скорее всего будет быстрее. Впрочем, это легко проверить.
-
В ориг. Обероне будет существенная разница (в худшую сторону), а в ББ скорее всего нет (буфер то пользовательский). А при отключенном системном буфере венда вообще не даст так сделать :)
-
Romiras, а ты не сравнивал случайно FPC и ББ по скорости I/O?
-
Т.е. для уменьшения частоты обмена с системным буфером логичнее было бы делать пользовательский буфер больше (32КБ - 64КБ), но они этого не сделали, и даже сделали размер неизменным...
Палка о двух концах. Во-первых 64 Кб это уже много. Это ж буфер на один файл. Открой 100 файлов, и получи 6.4 Мб затрат ОЗУ. Это дофига, между прочим. Это больше чем воткнуто в типичную машину!
Во-вторых большой буфер - это больше вероятность потерять данные в случае падения приложения.
Ну да, многовато. Но это зависит от задачи. А в ББ свой размер нельзя задать. Что не есть гуд.
-
Romiras, а ты не сравнивал случайно FPC и ББ по скорости I/O?
Нет. А на примере чего?
-
Размер кластера может быть 512 байт.
Были и такие времена. Но уже, как правило, не меньше 4096 по умолчанию. Впрочем, это зависит от того какого размера данные будут на этой ФС и задаётся при форматировании раздела.
-
Моё предположение таково: размер кеша имеет прямое отношение и к размеру кластера на ФС. Который может быть 1кб и выше - зависит от нужды хранить много мелких файлов, для наиболее эффективного использования дискового пространства. А системный буфер является прослойкой между разнящимися размерами кластера и API чтения с файла. То есть он заведомо больше размера сектора.
Размер кластера может быть 512 байт.
Можно сделать буфер размером в 1234 байта, существенной разницы с 1024 не будет. Скорее всего будет быстрее. Впрочем, это легко проверить.
Все. Проверил. Результаты ровно те, которые я ранее предсказывал:
Код:
package main
import "fmt"
import "os"
import "bufio"
func main() {
file, err := os.Create("hello2.txt")
if err!= nil {
fmt.Print(err)
return
}
w := bufio.NewWriterSize(file,1234)
for i:=0; i<100000000; i++ {
fmt.Fprintf(w, "a")
}
}
Собственно тут 1234 -- это та самая циферька которую будем менять.
Провел эксперимент с размером буфера в 1024, 16, и 1234 байта.
Результаты:
1024:
ETime( 0:00:22.840 ) UTime( 0:00:22.448 ) KTime( 0:00:00.358 )
ITime( 0:00:00.000 )
16:
ETime( 0:00:39.146 ) UTime( 0:00:28.704 ) KTime( 0:00:10.405 )
ITime( 0:00:00.000 )
1234:
ETime( 0:00:22.647 ) UTime( 0:00:22.323 ) KTime( 0:00:00.265 )
ITime( 0:00:00.000 )
Ну и, до кучи, попробую сейчас 4096 сделать. Думаю что особой разницы с 1234 и 1024 не будет.
-
1234:
ETime( 0:00:22.647 ) UTime( 0:00:22.323 ) KTime( 0:00:00.265 )
ITime( 0:00:00.000 )
Ну и, до кучи, попробую сейчас 4096 сделать. Думаю что особой разницы с 1234 и 1024 не будет.
Ну, собственно вот:
ETime( 0:00:22.081 ) UTime( 0:00:21.855 ) KTime( 0:00:00.202 )
ITime( 0:00:00.000 )
А если не видно разницы, зачем платить больше? :-)
-
Romiras, а ты не сравнивал случайно FPC и ББ по скорости I/O?
Нет. А на примере чего?
Ну вот к примеру как valexey выше проверил на Гоше
-
valexey, твой аппликативный буфер не имеет прямого отношения к буферу API, который обращается напрямую к файловой системе. Следовательно, разницы в результатах быть и не должно.
-
Romiras, а ты не сравнивал случайно FPC и ББ по скорости I/O?
Нет. А на примере чего?
Ну вот к примеру как valexey выше проверил на Гоше
Не должно зависеть от выбранного ЯП. Только от реализации системных вызовов.
-
valexey, твой аппликативный буфер не имеет прямого отношения к буферу API, который обращается напрямую к файловой системе. Следовательно, разницы в результатах быть и не должно.
Напрямую к FS обращается вообще то драйвер. То есть это ровно то, что не имеет отношения к тому что в ББ проставлено.
Изначально мы говорили о буфере который сидит на стороне приложения а не ОСи. Советую внимательно перечитать дискуссию.
-
А если не видно разницы, зачем платить больше? :-)
Я на BB тоже вчера проверял (только на чтение 1,8ГБ) :)
2 - 28876 миллисек
4 - 27846 миллисек
8 - 27815 миллисек
-
Не должно зависеть от выбранного ЯП. Только от реализации системных вызовов.
Дьявол в нюансах.
-
А если не видно разницы, зачем платить больше? :-)
Я на BB тоже вчера проверял (только на чтение 1,8ГБ) :)
2 - 28876 миллисек
4 - 27846 миллисек
8 - 27815 миллисек
2 - это 2 Кб, или таки 2 байта?
И как читалось - побайтово, или как-то иначе? Тут есть разница. Если не сложно, то неплохо бы привести код (чтобы другие у себя могли проверить)
-
Надо понимать, что между этапом где данные, находящиеся на жестком диске, и этапом их проекции в память лежат несколько прослоек, начиная от драйвера ФС, обращающегося к ФС и до аппликации, использующей системные вызовы. А программный буфер - это штука чисто виртуальная. Она должна влиять лишь на объём потребляемой памяти программой.
Таковы мои соображения.
-
Кстати, свой эксперимент я признаю неудовлетворительным. Fprintf слишком долго думает самостоятельно. Заменил на тупой вызов WriteByte - стало работать в 20 раз быстрее.
Так что сейчас будет серия новых экспериментов.
-
А программный буфер - это штука чисто виртуальная. Она должна влиять лишь на объём потребляемой памяти программой.
Таковы мои соображения.
Оставленное в цитате - не правильные соображение. Програмный буфер значительно влияет на производительность за счет снижения числа системных вызовов - системный вызов это большие тормоза.
-
В 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
-
Програмный буфер значительно влияет на производительность за счет снижения числа системных вызовов
Под системным вызовом я понимаю вызов системного API. Он, в свою очередь, должен обеспечивать чтение блоков данных без задержек, при помощи внутреннего (системного) буфера.
Далее. Касательно побайтового чтения. Это лишь установка позиции в программном буфере. Так что не должно влиять на скорость чтения данных.
-
Далее. Касательно побайтового чтения. Это лишь установка позиции в программном буфере. Так что не должно влиять на скорость чтения данных.
+1
-
2 - это 2 Кб, или таки 2 байта?
КБ
-
А программный буфер - это штука чисто виртуальная. Она должна влиять лишь на объём потребляемой памяти программой.
Таковы мои соображения.
Оставленное в цитате - не правильные соображение. Програмный буфер значительно влияет на производительность за счет снижения числа системных вызовов - системный вызов это большие тормоза.
Исправляюсь. Теперь эксперимент более чистый. Причем на чтение.
Буфер у меня, как понимаете, програмный. На стороне приложения. К системе отношения не имеющий.
Исходник такой:
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 байт. И само это число не обязано быть степенью двойки. Единственное - следует избегать простых чисел.
Что-то осталось не ясным? :-)
-
А самое оптимальное таки 4096 :D
-
Что-то осталось не ясным? :-)
Ну в общем то все так, как и ожидалось.
-
А самое оптимальное таки 4096 :D
Ну, оно столь же оптимально, как и 4000 или там, не знаю, 4123 :-)
Ну и опять таки - все ж зависит от задачи. Кому то может 64К понадобиться. Все же оно дает выигрыш.
Вообще, мне нравится подход Go, точнее принятый в библиотеке Go'шной - вот есть файл, если нужен буферизированный ввод-вывод - вот тебе еще одна либа, прикручивай (в одну строчку) сверху буферизирующую насадку.
Таким образом каждая либа делает только что-то узкое (например работает с файлом, или занимается буферизацией), при этом их можно скрещивать получая что-то новое (например буферизированный ввод-вывод для файлов). Причем для каждого файла с которым ты работаешь можно все это проделать индивидуально. И размеры буфера будет индивидуально для каждого свой (если нужно).
-
Ну и чтобы была понятна разница между наличием системного буфера и его отсутствием, отрезаем буферизацию полностью. Ну, то есть не полностью, но заставляем систему в буфере ничего не держать, и сразу писать на диск:
Буфер 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.
-
Также видим, что собственный буфер спасает (разница на два порядка) даже если fsync работает (то есть системный буфер по сути не используется). А системный буфер спасает еще на пару порядков.
Ошибочка. Собственный буфер дает, в данном случае, выигрыш на порядок а не на два. А если его сделать нормального размера, то на полтора порядка.
-
А как ты отключал системный буфер? Через WinAPI?
И почему 16 байт?
-
Java tip: How to read files quickly (http://nadeausoftware.com/articles/2008/02/java_tip_how_read_files_quickly)
-
А как ты отключал системный буфер? Через WinAPI?
И почему 16 байт?
16 байт, потому что иначе программа будет работать слишком быстро на мегабайтном файле. :-)
Как отключил - я же написал все подробно. fsync.
-
Код (буфер 16 + fsync):
func main() {
file,err := os.Create("100Mb.txt")
if err!= nil {
fmt.Print(err)
return
}
w := bufio.NewWriterSize(file,16)
for i:=0; i<1000000; i++ {
w.WriteByte(byte(32))
file.Sync() // fsync
}
}
Строчку file.Sync() если закомментить - будет с системным буфером. Грубо говоря.
-
А file.Sync() делает обмен с диском только если буфер заполнен? Или как?
ps И с чего ты взял что оно в обход системного буфера работает?
-
Вот пример прямого обмена с диском в винде:
http://www.codeproject.com/Articles/51678/Improve-responsiveness-in-Windows-with-the-FILE_FL
-
А file.Sync() делает обмен с диском только если буфер заполнен? Или как?
ps И с чего ты взял что оно в обход системного буфера работает?
Я ничего ниоткуда не взял. fsync заставляет систему если что и было в буфере, скинуть из буфера это дело на диск. Если там ничего не было, то конечно ничего скинуто и не будет (но время на syscall конечно все равно будет потрачено).
Соответственно что происходит - кладем байт в системный буфер, затем говорим системе - сбрось все из буфера на диск. Этот байт пишется на диск. Это если без нашего буфера работать.
Если с нашим, то кладем байт в наш буфер, если он наполнился, то скидываем его содержимое в системный буфер, иначе ничего не делаем + в любом случае просим систему сбросить содержимое системного буфера на диск.
Факт, что система, когда её буфер пуст, при вызове fsync не обращается к диску, виден в отношении времен 4 и 2 строчки таблицы. Отношение равно 12,7, что хорошо согласуется (по порядку величины) с теоретическим отношением равным 16.
Понятно что с FILE_FLAG_NO_BUFFERING, но без fsync результаты будут несколько лучше. Но совсем немного. Не на порядок.
-
Я наверно торможу, но из твоего сообщения так и не понял... fsync сбрасывает пользовательский буфер в системный или системный на диск?
-
Оно?
http://msdn.microsoft.com/ru-ru/library/windows/desktop/aa364439(v=vs.85).aspx
PROCEDURE CloseFile (f: File; VAR res: INTEGER);
VAR s: INTEGER;
BEGIN
IF f.state = exclusive THEN
f.Flush;
res := WinApi.FlushFileBuffers(f.ref)
END;
s := f.state; f.state := closed;
CloseFileHandle (f, res);
IF (s IN {temp, new, hidden}) & (f.name # "") THEN
res := WinApi.DeleteFileW(f.name)
END
END CloseFile;
-
Я наверно торможу, но из твоего сообщения так и не понял... fsync сбрасывает пользовательский буфер в системный или системный на диск?
Системный на диск, если он не пуст. Если пуст, то обращения к диску не будет.
А содержимое пользовательского буфера будет насильно сброшено в системный с помощью fflush (в данном случае w.Flush())
Две забавные штучки fflush и fsync :-) Советую таки посмотреть видео доклада Аксенова по MySql - он это все очень хорошо описывает.
-
Оно?
http://msdn.microsoft.com/ru-ru/library/windows/desktop/aa364439(v=vs.85).aspx
PROCEDURE CloseFile (f: File; VAR res: INTEGER);
VAR s: INTEGER;
BEGIN
IF f.state = exclusive THEN
f.Flush;
res := WinApi.FlushFileBuffers(f.ref)
END;
s := f.state; f.state := closed;
CloseFileHandle (f, res);
IF (s IN {temp, new, hidden}) & (f.name # "") THEN
res := WinApi.DeleteFileW(f.name)
END
END CloseFile;
Да, оно.
func (f *File) Sync() (err error) {
if f == nil {
return syscall.EINVAL
}
if e := syscall.Fsync(f.fd); e != nil {
return NewSyscallError("fsync", e)
}
return nil
}
...
func Fsync(fd Handle) (err error) {
return FlushFileBuffers(fd)
}
...
func FlushFileBuffers(handle Handle) (err error) {
r1, _, e1 := Syscall(procFlushFileBuffers.Addr(), 1, uintptr(handle), 0, 0)
if int(r1) == 0 {
if e1 != 0 {
err = error(e1)
} else {
err = EINVAL
}
}
return
}
...
procFlushFileBuffers = modkernel32.NewProc("FlushFileBuffers")
-
Теперь вроде понятно. :)
ps Делись ссылкой
-
Теперь вроде понятно. :)
ps Делись ссылкой
Дык я делился же уже :-)
Вот: http://addconf.ru/event.sdf/ru/add_3/authors/AndrewAksyonoff/732
-
Сенкс.
На всякий случай, чтобы избежать недоразумений:
http://oberspace.dyndns.org/index.php/topic,431.msg13629.html#msg13629
Картинка с цитатой из "Проект Оберон" - это конкретно про файлы в памяти (MFiles). Но думаю понятно что - это в равной степени справедливо и для файлов на диске. Тем более что реализация файлов на диске отличается лишь способом транспортировки байтов :) Для файлов в памяти SYSTEM.MOVE, а для файлов на диске Kernel.PutSector
-
Вот: http://addconf.ru/event.sdf/ru/add_3/authors/AndrewAksyonoff/732
Прикольный чувак. Спасибо. Интересный доклад.