Помощь - Поиск - Пользователи - Календарь
Полная версия: Ada BlockRead
Форум «Всё о Паскале» > Delphi, Assembler и другие языки. > Другие языки
TarasBer
Мне очень не нравится скорость побайтового чтения - 3МБ файл читается больше секунды, когда дельфовый аналог на BlockRead считывает его моментально.
Всё, что приходит в голову - открыть файл для типа byte, узнать размер, потом открыть файл для массива данного размера и считать за раз.

Byte_IO.Open(SF, Byte_IO.In_File, "test.txt");
declare
S: Byte_IO.Count := Byte_IO.Size(SF);
type Arr is array (1..S) of byte;
type AArr is access Arr;
package Arr_IO is new Ada.Direct_IO(Arr);
F: Arr_IO.File_Type;
A: AArr;
begin
A := new Arr;
Byte_IO.Close(SF);
Arr_IO.Open(F, Arr_IO.In_File, "test.txt");
Arr_IO.Read(F, A.all);
Arr_IO.Close(F);
end;


Но это выдаёт
raised STORAGE_ERROR : EXCEPTION_STACK_OVERFLOW
(даже после того, как я вместо заведения массива на стеке завёл указатель)
Да и мало ли размер файла изменится между первым и вторым открытиями.
И вообще много дёргать винт это плохо.

Последний вариант - через ВинАПИ, но мне неохота туда лезть.

ПС Настраиваемые пакеты во внутреннем блоке - это сильно. Реально круто, я про язык.
IUnknown
Цитата
Но это выдаёт
raised STORAGE_ERROR : EXCEPTION_STACK_OVERFLOW
Не подтверждается... В пустом проекте читает трехмегабайтный файл легко...

Цитата
Всё, что приходит в голову - открыть файл для типа byte, узнать размер, потом открыть файл для массива данного размера и считать за раз.
Чтобы узнать размер файла, совсем не обязательно его открывать. Есть пакет Directories, который предоставляет тебе информацию:

   procedure File_Read is
Name : String := "F:\Programs\Ada\forum\debug\test.txt";
File_Size : Integer := Integer (Ada.Directories.Size (Name));

type Byte_Buffer is array(1 .. File_Size) of Byte;
package Arr_IO is new Ada.Direct_IO (Byte_Buffer);
File : Arr_IO.File_Type;
Buffer : Byte_Buffer;
begin
Arr_IO.Open (File, Arr_IO.In_File, Name);
Arr_IO.Read (File, Buffer);
Arr_IO.Close (File);
Ada.Text_IO.Put_Line (Integer'Image (File_Size));
end File_Read;

шестимегабайтный файл прочитало меньше, чем за 0.1 секунды.
TarasBer
Так, я понял. При инициализации шаблона там, в модуле файлов, на стеке создаётся массив соответствующего размера.

> Не подтверждается... В пустом проекте читает трехмегабайтный файл легко...

А где в настройках компилятора размер стека правится?
Впрочем, это не решение.
А если я захочу в кучу прочитать полугиговый файл за раз?
Ну допустим, что это неправильно: я могу с той же скоростью читать порциями по 4096 байт, но это же привязывание к размеру кластера (т.е. к особенностям машины). Жаль, что стандартный файловый тип не умеет буферизовать (как я понял).

> Чтобы узнать размер файла, совсем не обязательно его открывать. Есть пакет Directories, который предоставляет тебе информацию:

Спасибо.
Всё-таки, каков оптимальный размер для считывания за раз? Может, есть стандартная функция, которая это говорит, по аналогии с функцией, говорящей оптимальный размер блока для выделения памяти в куче?
IUnknown
Цитата
Жаль, что стандартный файловый тип не умеет буферизовать
Работай с потоками, они умеют. Если правильно организовать ввод из файла, как из потока (правильно - это чтоб не было лишних копирований буфера с места на место, чтоб читать прямо туда, где физически буфер расположен в памяти), то работать будет очень быстро. Года полтора назад на comp.lang.ada некто Gautier (есть такой программист, широко известный в узких кругах) показывал разницу в скорости обработки потока: читал и перезаписывал содержимое одного файла в другой. Первый раз - обычными 'Read/'Write, второй раз - правильно работая с буфером. Так вот второй способ отработал чуть-ли не в 50 раз быстрее (подробностей не помню, но если надо - могу поискать ту ветку).

Цитата
Может, есть стандартная функция, которая это говорит, по аналогии с функцией, говорящей оптимальный размер блока для выделения памяти в куче?
Не встречал никогда.
TarasBer
>Работай с потоками, они умеют.

Да только я не умею...
Из описания в книге я так и не понял, как должен выглядеть полный пример использования потока. Я не понял, как и что необходимо закрывать. Ну завёл я F: File_Type, ну открыл файл, ну написал S := Stream(F); В конце что делать с этими S и F? Если бы я работал только с F, то я бы просто написал Close, а тут что надо? С этим S надо что-то делать?

И ещё. Я не люблю вручную писать закрытие файла по исключению, и просто писать закрытие в конце тоже не люблю, я вообще считаю, что все подобные вещи должны быть завёрнуты в контролируемый объект (и тут простой РАИИ позволяет делать то, где навороченные, но недетерминированные ГЦ не помогут никак), вплоть до жёсткого контроля на уровне запрета компилятора.

Так вот, нету ли стандартного контролируемого потока?
IUnknown
Цитата
Так вот, нету ли стандартного контролируемого потока?
Нет. Ни файлов, ни потоков контролируемых нет и не будет. Придется писать закрытие в конце работы с потоком. А работать с ним очень просто:

with Interfaces;
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Streams.Stream_IO;

with Ada.Direct_IO;

procedure Main is

subtype Byte is Interfaces.Unsigned_8;
type Byte_Buffer is array(Integer range <>) of aliased Byte;
type PByte_Buffer is access Byte_Buffer;

subtype sz_Test_A is Byte_Buffer(1 .. 19);
subtype sz_Test_B is Ada.Streams.Stream_Element_Array(1 .. 19);
Is_Workaround_Possible: constant Boolean :=
sz_Test_A'Size = sz_Test_B'Size and sz_Test_A'Alignment = sz_Test_B'Alignment;

procedure BlockRead(File : in Ada.Streams.Stream_IO.File_Type;
Buffer : out Byte_Buffer;
Was_Read: out Natural) is

use Ada.Streams, Ada.Streams.Stream_IO;
SE_Buffer : Stream_Element_Array (1 .. buffer'Length);
for SE_Buffer'Address use Buffer'Address;
pragma Import (Ada, SE_Buffer);
Last_Read : Stream_Element_Offset;

begin
if Is_Workaround_Possible then
-- Быстрый путь
Read (Stream (file).all, SE_Buffer, Last_Read);
Was_Read := Natural (Last_Read);
else
-- Медленный путь
if End_Of_File(file) then
Was_Read := 0;
else
Was_Read :=
Integer'Min (Buffer'Length, Integer (Size (File) - Index (File) + 1));
Byte_Buffer'Read (Stream (File),
Buffer (Buffer'First .. Buffer'First + Was_Read - 1));
end if;
end if;
end BlockRead;

File_Name : String := "F:\Programs\Ada\forum\debug\aga.txt";

SFIn : Ada.Streams.Stream_IO.File_Type;
Buffer : PByte_Buffer;
Buf_Size : Integer;
Bytes_Read : Integer;

begin
-- Открыл
Ada.Streams.Stream_IO.Open(SFIn, Ada.Streams.Stream_IO.In_File, File_Name);
-- Получил размер
Buf_Size := Integer (Ada.Streams.Stream_IO.Size (SFIn));
-- Выделил память
Buffer := new Byte_Buffer (1 .. Buf_Size);

-- Прочел (это - процедура Gautier)
BlockRead (SFIn, Buffer.all, Bytes_Read);

-- Проверил, все ли нормально
if Buf_Size /= Bytes_Read then
Ada.Text_IO.Put_Line ("Something goes wrong ...");
end if;
-- Закрыл
Ada.Streams.Stream_IO.Close(SFIn);
end Main;

Не дожидаясь вопроса, сразу объясню, что такое Is_Workaround_Possible. У компиляторов GNAT и ObjectAda есть некоторые проблемы со скоростью выполнения Read и Write в потоках. Так вот, если Stream_Element это и есть байт, и оба тестовых массива одинаково упакованы и одинаково выровнены - то можно значительно ускорить операции чтения/записи, работая напрямую с массивом Stream_Element-ов, наложенным на буфер (строка for SE_Buffer'Address use Buffer'Address этим занимается). Если же нет возможности работать с таким массивом - что ж поделаешь - приходится читать медленно в буфер, это действительно медленно: автор замерял, говорит о 50-кратном замедлении в среднем. Если надо, кстати, есть и BlockWrite для стримов...
TarasBer
А почему именно от 1 до 19? Специально, чтобы не делилось на 4?

Дальше, умеет ли компилятор определять значение Is_Workaround_Possible, убрав одну из ветвей

if Is_Workaround_Possible then
...
else
...
end if;

> Ни файлов, ни потоков контролируемых нет и не будет.

То есть в стандартной библиотеке не будет?
Себе я сделал так:


with Ada.Streams.Stream_IO;
with Ada.Finalization;

package Controlled_Streams is

type Controlled_Stream_IO is new Ada.Finalization.Limited_Controlled with record
F: Ada.Streams.Stream_IO.File_Type;
S: Ada.Streams.Stream_IO.Stream_Access;
end record;

function New_Stream_IO(Mode : Ada.Streams.Stream_IO.File_Mode;
Name : String;
Form : String := "") return Controlled_Stream_IO;

private

pragma Warnings(Off);

overriding procedure Initialize (CS_IO: in out Controlled_Stream_IO);
overriding procedure Finalize (CS_IO: in out Controlled_Stream_IO);

pragma Warnings(On);

end Controlled_Streams;

package body Controlled_Streams is

overriding procedure Initialize (CS_IO: in out Controlled_Stream_IO) is begin
null;
end;

overriding procedure Finalize (CS_IO: in out Controlled_Stream_IO) is begin
if Ada.Streams.Stream_IO.Is_Open(CS_IO.F) then
Ada.Streams.Stream_IO.Close(CS_IO.F);
end if;
end;

function New_Stream_IO(Mode : Ada.Streams.Stream_IO.File_Mode;
Name : String;
Form : String := "") return Controlled_Stream_IO is begin
return CS_IO: Controlled_Stream_IO do
CS_IO.S := null;
Ada.Streams.Stream_IO.Open(CS_IO.F, Mode, Name, Form);
CS_IO.S := Ada.Streams.Stream_IO.Stream(CS_IO.F);
end return;
end;

end Controlled_Streams;


Использование:

declare CS: Controlled_Stream_IO := New_Stream_IO(In_File, "test.txt");
begin
тут работаем, про исключения и необходимость закрыть не думаем
end;



Добавлено через 10 мин.
Ещё такой момент.
Я применил это для своего вектора.
Всё зашибись, но возник облом на файле 3МБ (какое-то исключение raised ADA.IO_EXCEPTIONS.DEVICE_ERROR : Invalid argument), причём именно в случае Is_Workaround_Possible. Я принудительно выставил Is_Workaround_Possible в false. И всё нормально прочиталось, при этом программа отработала за 0.21 секунд (я так понял, тут уже не время считывания, а время запуска, инициализаций итд).
IUnknown
Цитата
возник облом на файле 3МБ (какое-то исключение raised ADA.IO_EXCEPTIONS.DEVICE_ERROR : Invalid argument), причём именно в случае Is_Workaround_Possible. Я принудительно выставил Is_Workaround_Possible в false. И всё нормально прочиталось
Очень странно. Воообще-то такое поведение должно было иметь место в обратном случае: если Is_Workaround_Possible равно False, а ты насильно перекинул в True, и из-за несоответствия размеров и(ли) выравнивания процедура Read не смогла прочесть информацию в SE_Buffer. В таком случае Read бросает IO_Exceptions.Device_Error... Получается, что Is_Workaround_Possible вернула True, но буферные переменные не соответствуют друг другу?

Попробуй действовать наверняка: прямо внутри BlockRead опиши подтипы:
      subtype mysz_Test_A is Byte_Buffer(1 .. Buffer'Length);
subtype mysz_Test_B is Ada.Streams.Stream_Element_Array(1 .. buffer'Length);
, и проверяй размер/выравнивание этих подтипов. У меня при подключении твоего пакета без сбоя прочитались 3-х, 6-ти и 9-ти Мб файлы. И не только прочитались, а и скопировались в другой стрим (ну да, я добавил еще создание файла в твой код). Хотя, если тебя скорость устраивает... Может, в 2010 уже починили эту багу просто? в GNAT-2009 с этим невозможно было мириться.
TarasBer
Код такой:

procedure Load_From_File(V: out Vector; File_Name: String) is
use Ada.Streams;
use Ada.Streams.Stream_IO;

File : constant Controlled_Stream_IO := New_Stream_IO(In_File, File_Name);
El_Count : constant integer := integer(Size(File.F)) * 8 / Element'Size;

subtype byte is Interfaces.Unsigned_8;

subtype Test_SA is Stream_Element_Array
(1 .. Stream_Element_Offset(El_Count * Element'Size));
type Test_BA is array
(1 .. Stream_Element_Offset(El_Count * Element'Size)) of aliased byte;

Is_Workaround_Possible: constant Boolean := (Test_SA'Size = Test_BA'Size) and (Test_SA'Alignment = Test_BA'Alignment);

begin
V.Set_Length(0);
V.Set_Length(El_Count);
if Is_Workaround_Possible then
declare
Buffer : Test_SA;
for Buffer'Address use V.Elements.all'Address;
Last : Stream_Element_Offset; pragma Unreferenced(Last);
begin
Read(File.S.all, Buffer, Last);
end;
else
Element_Array'Read(File.S, V.Elements.all (V.First .. V.Last));
end if;
end;


Вылетает.

Если же Is_Workaround_Possible сделать в лоб false, то всё нормально.
________________________________
Правка:
Всё, я заменил Element'Size на Element'Size/8 и всё заработало не только на мелких файлах.
Ну и формулу
integer(Size(File.F)) * 8 / Element'Size;
поменял на
integer(Size(File.F)) / (Element'Size / 8);
(чтобы на больших файлах не было лишнего переполнения)

Всё-таки иногда забываю, что размер возвращается в битах, а не байтах.
Это текстовая версия — только основной контент. Для просмотра полной версии этой страницы, пожалуйста, нажмите сюда.