【问题标题】:What is the reason of the huge difference of looping time between tFileStream read and write [duplicate]tFileStream读写之间循环时间差异巨大的原因是什么[重复]
【发布时间】:2017-11-23 07:19:24
【问题描述】:

我尝试读取股票数据并将其写入文件。

相同的循环计数会产生巨大的差异。实际的写作循环大约需要 30 分钟。

这仅仅是因为驱动器写入速度的物理限制吗?

有什么方法可以改善写作过程吗?

uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, System.Generics.Collections;

type
  tSymbol = record
    CloseList: TList<Integer>;
    OpenList: TList<Integer>;
    VolumeList: TList<Integer>;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    ProgressBar1: TProgressBar;
    Memo1: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    ReadList, WriteList: TList<tSymbol>;
  end;

procedure TForm1.Button1Click(Sender: TObject); // it takes 45 seconds.
var
  _FileStream: TFileStream;
  i, j: Integer;
begin
  Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));

  ProgressBar1.Min := 0;
  ProgressBar1.Max := 999;

  _FileStream := TFileStream.Create('test', fmCreate);

  for i := 0 to 999 do
  begin
    for j := 0 to 999 do
    begin
      _FileStream.Write(WriteList.List[i].CloseList.List[j], 4);
      _FileStream.Write(WriteList.List[i].OpenList.List[j], 4);
      _FileStream.Write(WriteList.List[i].VolumeList.List[j], 4);
    end;

    ProgressBar1.Position := i;
  end;

  _FileStream.Free;

  Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
end;

procedure TForm1.Button2Click(Sender: TObject); // it takes 6 seconds.
var
  _FileStream: TFileStream;
  _Close, _Open, _Volume: Integer;
  _Symbol: tSymbol;
  i, j: Integer;
begin
  Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));

  ProgressBar1.Min := 0;
  ProgressBar1.Max := 999;

  _FileStream := TFileStream.Create('test', fmOpenRead);

  for i := 0 to 999 do
  begin
    _Symbol.CloseList := TList<Integer>.Create;
    _Symbol.OpenList := TList<Integer>.Create;
    _Symbol.VolumeList := TList<Integer>.Create;

    for j := 0 to 999 do
    begin
      _FileStream.Read(_Close, 4);

      _Symbol.CloseList.Add(_Close);

      _FileStream.Read(_Open, 4);

      _Symbol.OpenList.Add(_Open);

      _FileStream.Read(_Volume, 4);

      _Symbol.VolumeList.Add(_Volume);
    end;

    ReadList.Add(_Symbol);

    ProgressBar1.Position := i;
  end;

  _FileStream.Free;

  Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  _Symbol: tSymbol;
  i, j: Integer;
begin
  ReadList := TList<tSymbol>.Create;
  WriteList := TList<tSymbol>.Create;

  _Symbol.CloseList := TList<Integer>.Create;
  _Symbol.OpenList := TList<Integer>.Create;
  _Symbol.VolumeList := TList<Integer>.Create;

  for i := 0 to 999 do
  begin
    for j := 0 to 999 do
    begin
      _Symbol.CloseList.Add(0);
      _Symbol.OpenList.Add(0);
      _Symbol.VolumeList.Add(0);
    end;

    WriteList.Add(_Symbol);
  end;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  ReadList.Free;
  WriteList.Free;
end;

【问题讨论】:

    标签: delphi


    【解决方案1】:

    使用缓冲文件 I/O。以较大的块读取/写入文件,根据需要管理每个块中的各个值。 Delphi 甚至在柏林 10.1 及更高版本中都有 TBufferedFileStream 类。

    此外,在填充列表时,请提前预先分配列表的容量,以避免在向列表中添加新项目时必须重新分配列表的内部数组的开销。

    试试这样的:

    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, System.Generics.Collections;
    
    type
      tSymbol = record
        CloseList: TList<Integer>;
        OpenList: TList<Integer>;
        VolumeList: TList<Integer>;
        constructor Create(InitialCapacity: Integer);
        procedure Cleanup;
      end;
    
      TForm1 = class(TForm)
        Button1: TButton;
        ProgressBar1: TProgressBar;
        Memo1: TMemo;
        Button2: TButton;
        procedure Button1Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
        procedure Button2Click(Sender: TObject);
      private
        ReadList, WriteList: TList<tSymbol>;
      end;
    
    constructor tSymbol.Create(InitialCapacity: Integer);
    begin
      CloseList := TList<Integer>.Create;
      CloseList.Capacity := InitialCapacity;
    
      OpenList := TList<Integer>.Create;
      OpenList.Capacity := InitialCapacity;
    
      VolumeList: TList<Integer>.Create;
      VolumeList.Capacity := InitialCapacity;
    end;
    
    procedure tSymbol.Cleanup;
    begin
      CloseList.Free;
      OpenList.Free;
      VolumeList.Free;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      FS: TFileStream;
      i, j, idx: Integer;
      Block: array of Int32;
    begin
      Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
    
      ProgressBar1.Position := 0;
      ProgressBar1.Min := 0;
      ProgressBar1.Max := 1000;
      ProgressBar1.Step := 1;
    
      FS := T{Buffered}FileStream.Create('test', fmCreate);
      try
        SetLength(Block, 3 * 1000);
    
        for i := 0 to WriteList.Count-1 do
        begin
          with WriteList[i] do
          begin
            idx := 0;
            for j := 0 to 999 do
            begin
              Block[idx+0] := CloseList[j];
              Block[idx+1] := OpenList[j];
              Block[idx+2] := VolumeList[j];
              Inc(idx, 3);
            end;    
          end;
    
          FS.WriteBuffer(Block[0], SizeOf(Int32) * Length(Block));
          ProgressBar1.StepIt;
        end;
        //FS.FlushBuffer;
      finally
        FS.Free;
      end;
    
      Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
    end;
    
    procedure TForm1.Button2Click(Sender: TObject);
    var
      FS: TFileStream;
      Symbol: tSymbol;
      i, j, idx: Integer;
      Block: array of Int32;
    begin
      Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
    
      for I := 0 to ReadList.Count-1 do
        ReadList[I].Cleanup;
      ReadList.Clear;
    
      ProgressBar1.Position := 0;
      ProgressBar1.Min := 0;
      ProgressBar1.Max := 999;
      ProgressBar1.Step := 1;
    
      FS := T{Buffered}FileStream.Create('test', fmOpenRead or fmShareDenyWrite);
      try    
        SetLength(Block, 3 * 1000);
        ReadList.Capacity := 1000;
    
        for i := 0 to 999{(FS.Size div 12000) - 1} do
        begin
          FS.ReadBuffer(Block[0], SizeOf(Int32) * Length(Block));
    
          Symbol := tSymbol.Create(1000);
          try
            idx := 0;
            for j := 0 to 999 do
            begin
              Symbol.CloseList.Add(Block[idx+0]);
              Symbol.OpenList.Add(Block[idx+1]);
              Symbol.VolumeList.Add(Block[idx+2]);
              Inc(idx, 3);
            end;
    
            ReadList.Add(Symbol);
          except
            Symbol.Cleanup;
            raise;
          end;
    
          ProgressBar1.StepIt;
        end;
      finally
        FS.Free;
      end;
    
      Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    var
      Symbol: tSymbol;
      i, j: Integer;
    begin
      ReadList := TList<tSymbol>.Create;
    
      WriteList := TList<tSymbol>.Create;    
      WriteList.Capacity := 1000;
    
      for i := 0 to 999 do
      begin
        Symbol := tSymbol.Create(1000);
        try
          for j := 0 to 999 do
          begin
            Symbol.CloseList.Add(0);
            Symbol.OpenList.Add(0);
            Symbol.VolumeList.Add(0);
          end;
    
          WriteList.Add(Symbol);
        except
          Symbol.Cleanup;
          raise;
        end;
      end;
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    var
      i: Integer;
    begin
      if ReadList <> nil then
      begin
        for i := 0 to ReadList.Count-1 do
          ReadList[i].Cleanup;
        ReadList.Free;
      end;
    
      if WriteList <> nil then
      begin
        for i := 0 to WriteList.Count-1 do
          WriteList[i].Cleanup;
        WriteList.Free;
      end;
    end;
    

    话虽如此,您可能会考虑将您的 3 个整数值合并到另一个 record 中,这样您就不会浪费时间和资源来分配这么多单独的列表:

    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, System.Generics.Collections;
    
    type
      tSymbolValues = record
        Close: Integer;
        Open: Integer;
        Volume: Integer;
      end;
    
      tSymbol = record
        Values: TList<tSymbolValues>;
        constructor Create(InitialCapacity: Integer);
        procedure Cleanup;
      end;
    
      TForm1 = class(TForm)
        Button1: TButton;
        ProgressBar1: TProgressBar;
        Memo1: TMemo;
        Button2: TButton;
        procedure Button1Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
        procedure Button2Click(Sender: TObject);
      private
        ReadList, WriteList: TList<tSymbol>;
      end;
    
    constructor tSymbol.Create(InitialCapacity: Integer); 
    begin
      Values := TList<tSymbolValues>.Create;
      Values.Capacity := InitialCapacity;
    end;
    
    procedure tSymbol.Cleanup;
    begin
      Values.Free;
    end;
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
      FS: TFileStream;
      i, j, idx: Integer;
      Block: array of Int32;
    begin
      Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
    
      ProgressBar1.Position := 0;
      ProgressBar1.Min := 0;
      ProgressBar1.Max := 1000;
      ProgressBar1.Step := 1;
    
      FS := T{Buffered}FileStream.Create('test', fmCreate);
      try
        SetLength(Block, 3 * 1000);
    
        for i := 0 to WriteList.Count-1 do
        begin
          with WriteList[i] do
          begin
            idx := 0;
            for j := 0 to Values.Count-1 do
            begin
              with Values[j] do
              begin
                Block[idx+0] := Close;
                Block[idx+1] := Open;
                Block[idx+2] := Volume;
                Inc(idx, 3);
              end;
            end;    
          end;
    
          FS.WriteBuffer(Block[0], SizeOf(Int32) * Length(Block));
          ProgressBar1.StepIt;
        end;
        //FS.FlushBuffer;
      finally
        FS.Free;
      end;
    
      Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
    end;
    
    procedure TForm1.Button2Click(Sender: TObject);
    var
      FS: TFileStream;
      Symbol: tSymbol;
      Values: tSymbolValues;
      i, j, idx: Integer;
      Block: array of Int32;
    begin
      Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
    
      for I := 0 to ReadList.Count-1 do
        ReadList[i].Cleanup;
      ReadList.Clear;
    
      ProgressBar1.Position := 0;
      ProgressBar1.Min := 0;
      ProgressBar1.Max := 999;
      ProgressBar1.Step := 1;
    
      FS := T{Buffered}FileStream.Create('test', fmOpenRead or fmShareDenyWrite);
      try    
        SetLength(Block, 3 * 1000);
        ReadList.Capacity := 1000;
    
        for i := 0 to 999{(FS.Size div 12000) - 1} do
        begin
          FS.ReadBuffer(Block[0], SizeOf(Int32) * Length(Block));
    
          Symbol := tSymbol.Create(1000);
          try
            idx := 0;
            for j := 0 to 999 do
            begin
              Values.Open := Block[idx+0];
              Values.Close := Block[idx+1];
              Values.Volume := Block[idx+2];
              Symbol.Values.Add(Values);
              Inc(idx, 3);
            end;
    
            ReadList.Add(Symbol);
          except
            Symbol.Cleanup;
            raise;
          end;
    
          ProgressBar1.StepIt;
        end;
      finally
        FS.Free;
      end;
    
      Memo1.Lines.Add(FormatDateTime('hh:mm:ss', Time));
    end;
    
    procedure TForm1.FormCreate(Sender: TObject);
    var
      Symbol: tSymbol;
      Values: tSymbolValues;
      i, j: Integer;
    begin
      ReadList := TList<tSymbol>.Create;
    
      WriteList := TList<tSymbol>.Create;    
      WriteList.Capacity := 1000;
    
      for i := 0 to 999 do
      begin
        Symbol := tSymbol.Create(1000);
        try
          for j := 0 to 999 do
          begin
            Values.Open := 0;
            Values.Close := 0;
            Values.Volume := 0;
            Symbol.Values.Add(Values);
          end;
    
          WriteList.Add(Symbol);
        except
          Symbol.Cleanup;
          raise;
        end;
      end;
    end;
    
    procedure TForm1.FormDestroy(Sender: TObject);
    var
      i: Integer;
    begin
      if ReadList <> nil then
      begin
        for I := 0 to ReadList.Count-1 do
          ReadList[i].Cleanup;
        ReadList.Free;
      end;
    
      if WriteList <> nil then
      begin
        for I := 0 to WriteList.Count-1 do
          WriteList[i].Cleanup;
        WriteList.Free;
      end;
    end;
    

    【讨论】:

    • Delphi TBufferedFileStream 只适合在流中向前读写。向后阅读时,您获得的性能甚至比无缓冲的更差。对于随机访问,它不会有太大的不同。
    • @dummzeuch:也许是这样,但在这个特定的例子中这些都不适用。读写是线性的,向前的。
    猜你喜欢
    • 1970-01-01
    • 2013-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-01
    • 1970-01-01
    • 2012-03-10
    相关资源
    最近更新 更多