Конференция "WinAPI" » Использование SetFilePointer и SetEndOfFile.
 
  • Riply © (03.02.08 18:35) [0]
    Здравствуйте !
    При "разборе полетов" возник следующий вопрос:
    Стоит ли применять SetFilePointer, SetEndOfFile для задания размера
    перед записью файлов, когда этот размер известен, а запись делается в
    несколько WriteFile ?
    Мой собеседник выдвинул предположение,
    что на NTFS это может дать серьезные плюсы, в то же время,
    существенно замедлит сохранение большого файла, например, на FAT.
    Вот что он пишет:
    "Преимущества такие:
    1. Антифрагментация. Ты требуешь от системы выделения всего места сразу, и у
    системы есть шанс выбрать блок походящего размера, используя $Bitmap.
    2. Свободное место. Ты захватываешь требуемое свободное место сразу,
    исключая этим ситуацию, когда нехватка свободного места выявляется в
    процессе, и операцию приходится отменять, при этом время потраченое на
    пройденую часть сохранения потрачено зря.
    3. [NTFS specific] Производительность может быть немного выше, т.к.
    SetFilePointer+SetEndOfFile не пишут в добавленые кластеры данные, при этом
    операций с $Bitmap намного меньше, также меньше записей в данные атрибута в
    MFT (все записи в $Bitmap и в MFT осуществляются с участием журналирования,
    поэтому это не такие уж быстрые операции). Но я думаю что чтобы увидеть
    разницу, надо смотреть при небольшом буфере, при этом разница не долна быть
    большой.

    Почему-то этим мало кто пользуется. Возможно, тут есть проблемы, которые я
    не вижу. Поэтому не спешу применять метод."


    Кто что думает о целесообразности применеия этих ф-ий ?

    P.S.
    Если будет необходима проверка типа: "Если NTFS делаем так, а если FAT то так",
    то это @не очень красиво@, лучше тогда обойтись без использовани такой возможности
    совсем.
  • KilkennyCat © (03.02.08 18:41) [1]
    > Если будет необходима проверка


    может, я чего не понимаю, но учитывая ограничения FAT по размерности, проверку делать придется.
  • ketmar © (03.02.08 18:46) [2]
    собственно, винда и сама умеет кэшировать несколько WriteFile(). плюс — ей можно дополнительно прояснить моск, если сказать, что файл будет с sequental access.

    в общем, глубокого практического смысла скорее мало, чем много, по-моему. разве только пункт про «захват места». однако это справедливо только для SetEndOfFile(), потому что SetFilePointer() сколько не двигай — пока WriteFile() не вызовешь, место не выделят.
  • ketmar © (03.02.08 18:48) [3]
    вдогон: а если писать в зашифрованый/пожатый файл, то и вообще становится очень вопросительно, стоит ли заниматься подобными телодвижениями. там всё сложнее, чем на самом деле. %-)
  • Anatoly Podgoretsky © (03.02.08 19:08) [4]
    На FAT тоже ничего не пишется, просто выделяются сектора. Это очень быстро.
    Насчет фрагментации, никак не влияет, это несвязаные процессы. Место может быть выделено как целиком, так и кусками. Эти операции на это не влияют.

    Не уверен, но вроде бы на NTFS сектора могут и не выделяться, а будет разреженный файл. Реально сектора выделятся только в момент записи. Это требует проверки.
  • ketmar © (03.02.08 19:27) [5]
    >[4] Anatoly Podgoretsky ©(03.02.08 19:08)
    >вроде бы на NTFS сектора могут и не выделяться, а будет разреженный файл
    неа. sparse file надо отдельно создавать, хитрыми манипуляциями. всё выделяется.
  • Игорь Шевченко © (03.02.08 19:28) [6]
    SetFileValidData тогда уж.

    Remarsk:

    "When creating large files where performance is an issue. This avoids the time it takes to fill the file with zeroes when the file is created or extended. "

    Я к чему - если хочется сразу выделять место, то стоит помнить, что в NT все вновь выделенные кластеры для файла заполняются нулями из соображений безопасности.
  • guav © (03.02.08 19:45) [7]
    > [4] Anatoly Podgoretsky ©   (03.02.08 19:08)
    > Насчет фрагментации, никак не влияет, это несвязаные процессы.

    Влияет. Если требовать кусками по 64 кб, то драйвер ФС будет искать кускипо 64 кб, если требовать сразу всесь требуемый размер, драйвер ФС попытается выделить этот размер одним куском.
    Про sparse - да, не хитрые, но всё же манипуляции требуются.

    > [6] Игорь Шевченко ©   (03.02.08 19:28)

    Не так.
    SetEndOfFile после SetFilePointer увеличит размер. Новые кластеры физически не будут заполнены нулями, но читатся будут нули.

    После этого может быть вызвана SetFileValidData с параметром между инициализированным размером и полным размером, и тогда те "виртуальные нули" станут читатся как то что реально там записано (т.е.было до выделения). Вот из справки про SetFileValidData:
    This parameter must be a positive value that is greater than the current valid data length, but less than the current file size.
  • ketmar © (03.02.08 19:50) [8]
    >[7] guav ©(03.02.08 19:45)
    >не хитрые, но всё же манипуляции требуются.
    ну, «хитрость» — дело относительное. в win32.hlp от Delphi не описано. %-)
  • guav © (03.02.08 20:06) [9]
    > [2] ketmar ©   (03.02.08 18:46)
    > собственно, винда и сама умеет кэшировать несколько WriteFile()

    Зависит от размера, для больших файлов всё равно будет записывать промежуточные результаты.


    > однако это справедливо только для SetEndOfFile(),

    А разве SetEndOfFile может увеличить размер без использования SetFilePointer ?
  • ketmar © (03.02.08 20:15) [10]
    >[9] guav ©(03.02.08 20:06)
    >А разве SetEndOfFile может увеличить размер без использования
    >SetFilePointer ?

    я намекал, что одного SetFilePointer() недостаточно. выразился криво, извиняюсь.
  • guav © (03.02.08 21:29) [11]
    > [0] Riply ©   (03.02.08 18:35)


    Советую тебе таки провести свои опыты, если тебя интересует подобная оптимизация.
    Как видишь, подобной оптимизацией никто не занимается.
    Поэтому я и не создавал тему здесь.

    Насчёт FAT - только что проверил - выделение кластеров осуществляется в SetEndOfFile, а запись нулей осуществляется при закрытии файла. Т.е. похоже, что лишней записи не будет, если все потребованные данные записаны до закрытия файла.
    Успешно вызвать SetFileValidData на FAT у меня не получилось. Учитывая, что в самой системе FAT информация о валидной длине файла не поддерживается - думаю, что и нельзы вызвать.

    Избегание фрагментации - действительно наблюдалось, но надо потщательнее это проверить. Можно используя FSCTL_GET_RETRIEVAL_POINTERS, FSCTL_MOVE_FILE, FSCTL_GET_VOLUME_BITMAP заранее подготовить соотвествующую фрагментацию тома по которой будет видно, насколько оптимально система выбирает расположение для файла.
  • Evgeny V © (04.02.08 06:13) [12]

    > Riply ©   (03.02.08 18:35)


    По теме вопроса -
    "Программирование серверных приложений для Microsoft Windows 2000"
    Рихтер Д., Кларк Д. Д.  Насколько помнится видел книгу и в электронном виде в интернете, но может и ошибаюсь.  Написано очень хорошо.
  • Riply © (04.02.08 15:48) [13]
    Спасибо всем.

    P.S.
    Эх, не удалось улепетнуть от тестировки :)
  • Anatoly Podgoretsky © (04.02.08 15:55) [14]
    Тебе пора винчестер иметь на пару террабайт.
  • Riply © (16.02.08 08:07) [15]
    > [7] guav ©   (03.02.08 19:45)
    > SetEndOfFile после SetFilePointer увеличит размер.
    > Новые кластеры физически не будут заполнены нулями, но читатся будут нули.

    > После этого может быть вызвана SetFileValidData с параметром между инициализированным размером и полным размером,
    > и тогда те "виртуальные нули" станут читатся как то что реально там записано (т.е.было до выделения).

    > Насчёт FAT - только что проверил - выделение кластеров осуществляется в SetEndOfFile,
    > а запись нулей осуществляется при закрытии файла. Т.е. похоже, что лишней записи не будет,
    > если все потребованные данные записаны до закрытия файла.

    На NTFS все так и обстоит. Только я ожидала, что после(при) закрытии файла "лишние" кластеры заполняться нулями.
    Оказывается нет. Возможны варианты что там останется мусор.
    Эту ситуацию удавалось воспроизводить неоднократно.
    Механика проста:
    Берем пустую директорию, копируем в нее кучу файлов. Уничтожаем их. Чистим корзину.
    Далее:
    NtCreate
    NtSetInformationFile(...FILE_END_OF_FILE_INFORMATION...)
    NtSetInformationFile(...FILE_ALLOCATION_INFORMATION...)
    NtWriteFile
    NtClose

    При сравнении файлов я использовала проверку "хвостика" на заполненность нулями.
    Оказывается, что в общем случае это не годится.

    P.S.
    Вот где надо хранить секретную любовную переписку :)
  • guav © (16.02.08 13:53) [16]
    > [15] Riply ©   (16.02.08 08:07)

    Не совсем понятно, можешь изложить выводы более последовательно ? С указанием точной последовательности действий что читается, что фактически записано и т.д.

    Кроме того больше интересы результаты насчёт возможных пользы/вреда указания размера перед записью.
  • Riply © (16.02.08 15:14) [17]
    >  [16] guav © (16.02.08 13:53)
    > Не совсем понятно, можешь изложить выводы более последовательно ?
    Sorry. Не выспалась, вот и сумбурно.

    > С указанием точной последовательности действий что читается, что фактически записано и т.д.
    Попробую.
    Танцы с копированием и удалением файлов были нужны для заполнения кластеров мусором.
    С большой долей вероятности для создания нашего файла будут использоваться именно они.
    Создаем стандартный файл. Устанавливаем его размер (например 50 MB).
    Как я устнавливала размер см. выше.
    Записываем данные. (Я записывала DataSize = от 0.5xBytesPerCluster до 16xBytesPerCluster байт).
    Закрываем файл.
    Я предполагала, что при закрытии файла все забъется нулями - оказывается нет.
    Смотрим что находится в нашем файле. API ф-ии - все что вне DataSize читают, как нули.
    На самом деле нулями заполняется только только "последний сектор",
    куда реально велась запись. (Почти всегда кластер, но пару раз видела что только сектор.)
    В "лишних" кластерах я находила куски из файлов, записанных сюда нами ранее.
    Иными словами, при подобном подходе (выделение места заранее),
    мы не производим лишних операций по очистке кластеров.
    И, соответственно, в случае не совпадения заказанного размера с реально записанным,
    надо вручную вызывать что-то типа SetFileValidData после записи.
    (Если конечно, мы так же аккуратны как и система :)

    > Кроме того больше интересы результаты насчёт возможных пользы/вреда указания размера перед записью.

    Такие выводы, да еще как руководство к действию, делать пока рано.
    Я еще не смотрела как выделяется место на фрагментированном диске в обоих случаях.
    (И вообще советовать ничего не буду. Расскажу только о том, что видела собственными глазами,
    а выводы делайте сами, под свою ответственность :)

    P.S.
    Случаев последующего изменения системой информации в "лишних" кластерах не наблюдала.
    Исключение - копирование этого файла на другой диск.
    Вот при этой, операции все забивается нулями.

    P.P.S.
    Все сказанное - для NTFS
  • Riply © (16.02.08 15:17) [18]
    >  [17] Riply ©   (16.02.08 15:14)

    Ошибка:
    (Я записывала DataSize = от 0.5xBytesPerCluster до 16xBytesPerCluster байт).
    читать как  (Я записывала DataSize = от 0.5xBytesPerSector до 16xBytesPerSector байт).
  • guav © (16.02.08 16:13) [19]
    Так, теперь понял. Ну это мне уже было ивестно - я ж уже давно отправлял пример ValidDataLength.dpr.
    Для меня вопрос в том как оно работает на системах где информация ValidDataLength не поддерживается.

    > И, соответственно, в случае не совпадения заказанного размера
    > с реально записанным,
    > надо вручную вызывать что-то типа SetFileValidData после
    > записи.

    А вот не надо. Если записали больше, то незачем вызывать SetFileValidData. Если записали меньше, то вообще логичнее теперь укоротить файл, а если длина устраивает то не всё равно ли что в конце - виртуальные нули или мусор ?
    При том SetEndOfFile как и другие требуют обычные права а SetFileValidData требует особых привилегий
    Единственный случай когда я вижу пользу в SetFileValidData - если мы дописываем в файл в котором есть большие пропуски, в которых всё равно что записано. Тогда можно эти пропуски делать через SetFilePointer+SetEndOfFile, и, чтобы не тратилось время на запись реальных нулей при WriteFile после пропуска, вызывать SetFileValidData.


    > Случаев последующего изменения системой информации в "лишних"
    > кластерах не наблюдала.

    Ну так, это же часть файла :) Я думаю что даже при дефрагментации (начиная с ХР, понятно, 2000 просто не дефрагментировала за ValidDataLength) неинициализированные кластера будут поддерживаться. Что интересно - не "зная" об этой фичи, MFT_Scan читает эти "секретные" данные, а если бы знал можно было бы сделать чтобы не читал.
  • Riply © (16.02.08 16:41) [20]
    > [19] guav © (16.02.08 16:13)
    > А вот не надо. Если записали больше, то незачем вызывать SetFileValidData.
    > Если записали меньше, то вообще логичнее теперь укоротить файл,
    > а если длина устраивает то не всё равно ли что в конце - виртуальные нули или мусор ?

    С первыми утверждениями сложно не согласиться :)
    А вот последнее - это смотря как читаешь.
    И потом, сама система забивает нулями.
    Наверное у нее есть веские соображения в пользу этого,
    раз она идет на лишние телодвижения :)

    > Ну так, это же часть файла :) Я думаю что даже при дефрагментации
    > (начиная с ХР, понятно, 2000 просто не дефрагментировала за ValidDataLength)
    > неинициализированные кластера будут поддерживаться.
    Ну и что, что часть файла ? Ведь система считает, что все равно выдаст нули пользователю.
    Значит может и изменить эти никому не нужные данные.

    > Что интересно - не "зная" об этой фичи, MFT_Scan читает эти "секретные" данные,
    > а если бы знал можно было бы сделать чтобы не читал.

    Саш, у тебя сохранилась та старая версия MFT_Scan - а ?
    Сотри ее немедленно ! Мне за нее стыдно.
    Скоро, в замен, другая будет готова :)

    P.S.
    А секретными он (MFT_Scan) априори считает все данные,
    которые предоставляются пользователю в "ненастоящем виде" :)
    Тем более мы убедились, что они и вправду "секретные" :)
  • Riply © (21.02.08 10:08) [21]
    Появилось время - попробовала проверить предположения из этой ветки.
    Результаты предварительных опытов:
    Тестировалось на двух фрагментированных дисках
    IOMEGA ZIP 100    PartitionLength = 100 646 912
    SUMSUNG SP2001H   PartitionLength = 1 077 479 424
    Файловая система и там и там: NTFS v 3.1
    Работаем под WinXP SP2
    Предварительно на каждом размещалось по три (не фрагментированных) файла,
    со следующими характеристиками:

    IOMEGA ZIP 100:
    FILE_NAME_WIN32 = 1_1048576.txt
    FileSize  1048576
    RETRIEVAL_POINTERS
    ExtentCount 1
    Lcn 92674 Count 2048

    FILE_NAME_WIN32 = 16_1048576.txt
    FileSize  1048576
    RETRIEVAL_POINTERS
    ExtentCount 1
    Lcn 26640 Count 2048

    FILE_NAME_WIN32 = 32_1048576.txt
    FileSize  1048576
    RETRIEVAL_POINTERS
    ExtentCount 1
    Lcn 98687 Count 2048

    SUMSUNG SP2001H:
    FILE_NAME_WIN32 = 0_8388608.txt
    FileSize  8388608
    RETRIEVAL_POINTERS
    ExtentCount 1
    Lcn 241293 Count 4096

    FILE_NAME_WIN32 = 16_8388608.txt
    FileSize  8388608
    RETRIEVAL_POINTERS
    ExtentCount 1
    Lcn 45060 Count 4096

    FILE_NAME_WIN32 = 32_8388608.txt
    FileSize  8388608
    RETRIEVAL_POINTERS
    ExtentCount 1
    Lcn 110596 Count 4096



    Далее, на них дважды поблочно (BytesPerBlock = MAXWORD - 1) копировалася файл.
    В первом случае - с предварительным использованим (на весь требуемый размер),
     NtSetInformationFile(...FILE_END_OF_FILE_INFORMATION...)
     NtSetInformationFile(...FILE_ALLOCATION_INFORMATION...)
    во втором - "обычное" копирование  
     
    В результате были получены следующие файлы:
    IOMEGA ZIP 100:
    FILE_NAME_WIN32 = FragmentFile_37748736.txt - "SetEnd" копирование
    TypeCode AT_DATA
    RecordLength 72
    FormCode NONRESIDENT
    Flags  STANDART
    Instance 4
    LowestVcn 0
    HighestVcn 73727
    MappingPairsOffset 64
    _CompressionUnit 0
    AllocatedLength  37748736
    FileSize  37748736
    ValidDataLength 37748736
    RETRIEVAL_POINTERS
    ExtentCount 1
    Lcn 100735 Count 73728

    FILE_NAME_WIN32 = FragmentFile_37748736.txt - "обычное" копирование
    TypeCode AT_DATA
    RecordLength 96
    FormCode NONRESIDENT
    Flags  STANDART
    Instance 4
    LowestVcn 0
    HighestVcn 73727
    MappingPairsOffset 64
    _CompressionUnit 0
    AllocatedLength  37748736
    FileSize  37748736
    ValidDataLength 37748736
    RETRIEVAL_POINTERS
    ExtentCount 5
    Lcn 90626 Count 2048
    Lcn 94722 Count 3565
    Lcn 16 Count 26624
    Lcn 28688 Count 32725
    Lcn 100735 Count 8766

    SUMSUNG SP2001H:
    FILE_NAME_WIN32 = FragmentFile_301989888.txt - "SetEnd" копирование
    TypeCode AT_DATA
    RecordLength 72
    FormCode NONRESIDENT
    Flags  STANDART
    Instance 4
    LowestVcn 0
    HighestVcn 147455
    MappingPairsOffset 64
    _CompressionUnit 0
    AllocatedLength  301989888
    FileSize  301989888
    ValidDataLength 301989888
    RETRIEVAL_POINTERS
    ExtentCount 1
    Lcn 263178 Count 147456

    FILE_NAME_WIN32 = FragmentFile_301989888.txt - "обычное" копирование
    TypeCode AT_DATA
    RecordLength 96
    FormCode NONRESIDENT
    Flags  STANDART
    Instance 4
    LowestVcn 0
    HighestVcn 147455
    MappingPairsOffset 64
    _CompressionUnit 0
    AllocatedLength  301989888
    FileSize  301989888
    ValidDataLength 301989888
    RETRIEVAL_POINTERS
    ExtentCount 5
    Lcn 261775 Count 1281
    Lcn 245389 Count 16384
    Lcn 4 Count 45056
    Lcn 114692 Count 57019
    Lcn 49156 Count 27716



    Т.е. нам удалось выяснить, что в условиях проведения эксперимента,
    при поблочном копировании и использовании сабжевых ф-ий
    1. Кластеры выделяются в SetEndOfFile (у мнея не совсем SetEndOfFile, но что-то типа того :)
    2. "лишних" действий по "забиванию" кластеров нулями ни при SetEndOfFile, ни при записи,
      ни при закрытии файла не производиться. (Реально в кластерах мусор, хоть и читаются нули).
    3. Если позволяет место, то создается дефрагментированный файл, в отличие от "обычного" копирования.

    На этот момент все.
    Выводы делайте сами :)

    P.S.
    Тестировалось на устаревших дисках. Может, для других, и не так все будет.
    Была бы рада, если бы кто-то попробовал и рассказал о результатах :)
 
Конференция "WinAPI" » Использование SetFilePointer и SetEndOfFile.
Есть новые Нет новых   [134431   +15][b:0][p:0.003]