小弟在一个项目中需要将一批JPEG图片大约2000张,进行处理(局部做上标记)。经过测试处理的过程时间很短,可以忽略不计,但读写文件的时候时间很长。其中处理一张图片需要3.8秒中,给图片加标记只需要120ms 读写JPEG却需要3秒多。显然时间都浪费在i/o上了。想问如何能提高读写文件的速度,也就是 I/O性能。有的人说可以用硬盘缓冲区,有的说用内存映射文件方式写,有点说用多线程的方式写入,现在求教正解。

解决方案 »

  1.   

    硬盘缓冲是操作系统的活儿,不是用户程序应该考虑的事
    file mapping理论上对提高效率能有些帮助,因为理论上操作系统会在适当的时候(一般会比用户更了解应该何时操作)将文件映射到内存和写入硬盘。
    多线程没用的,硬盘又不是内存,再多线程也得等硬盘寻道,而且一般也是等到硬盘缓冲区满了之后才写入
      

  2.   

    使用完成端口。参考文章:http://blog.chinaunix.net/u/11279/showart_95675.html
      

  3.   

    1,一次连续读取N个jpeg文件(到底读多少个却决于文件大小和自己实际实验)
    2,处理N个.
    3,一次连续写入.总体上的时间也许能少点,单个够呛.另一个不知道你的2000个文件是不是都在一个目录,可能是文件过多造成系统遍历文件早成的时间,分成子目录.
    要不然硬盘读10M的文件也用不上3秒啊.
      

  4.   

    谢谢大家的参与,说明一下:1、完成端口对我无用,我是本地处理图片,不需联网。
    2、多线程的话好像不行,同时两个线程进行I/O操作不见得比一个I/O快。
    3、我的目录下有5张图片的时候和2000张图片处理时间差不多,所以不是遍历文件耗费了时间
    4、每张图片的大小在800k到2M之间。不存在每次读写的字节数少的问题。
    5、一次连续读取N个jpeg文件,处理后,一次性写入硬盘。这个方法就是我所期待的。这个应该是建立缓冲的方法。请知道的指出具体实现方法,有关键代码就更好了。或者告诉我关键字,我自己去搜。
      

  5.   

    我处理图片的代码如下:var
      Readbmp: TBitmap;
      Memorybmp: TBitmap;
      ReadJPG: TJpegImage;
      writeJPG: TJpegImage;
    begin
      Readbmp:=TBitmap.Create;
      Memorybmp:=TBitmap.Create;
      ReadJPG := TJpegImage.Create;
      writeJPG := TJpegImage.Create;
      ReadJPG.LoadFromFile(PicName);
      ReadBmp.Assign(ReadJPG);
      ReadBmp.Assign(RightTopPoint(ReadBmp));
      writeJPG.Assign(LeftBottomPoint(ReadBmp));
      writeJPG.SaveToFile(SavePath+ExtractFileName(PicName));
      Readbmp.Free;
      Memorybmp.Free;
      ReadJPG.Free;
      writeJPG.Free;
    end;
      

  6.   


    我的意思是这样的,你先试试:
    比如建立5个jpeg文件格式对象(用数组),引用你的代码ReadJPG1,ReadJPG2,ReadJPG3,ReadJPG4,ReadJPG5,
    5个writeJPG.
    处理
    在按循序把5个写入文件.不要读一个处理一个再写入一个.
      

  7.   

    时间主要花在将Bitmap压缩编码成JPG。另外用Assign是比较慢的,你用了多个Assign
      

  8.   

    对你这个情况,Assign花的时间应该也能忽略。你可以考虑换别的JPG编码程序。
      

  9.   

    似乎你每处理一张图片都有动态生成和销毁好几个TBitmap对象。基本上都是不必要的。那些处理程序内直接在源图上处理就行了,不需要再生成Bitmap。下面这些东西,可以用全局变量。避免不断的生成与销毁。  Readbmp:=TBitmap.Create;
      Memorybmp:=TBitmap.Create;
      ReadJPG := TJpegImage.Create;
      writeJPG := TJpegImage.Create;
      

  10.   

    我用的就是全局变量,上面的程序省略了一些代码,所以你看不出来。
    若果不用Assign,如何来做呢
      

  11.   

    不应该这么慢的,用GDI+代替JPEG试试
    //加载一个JPG
    var
      Img: TGpBitmap;
    begin
      Img := TGpBitmap.Create('c:\a.jpg');
      Img.GetHBITMAP(0,Bmp.Handle);
      Img.Free;
    end;//保存一个JPG
    var
      Img: TGpBitmap;
      Clsid: TGUID;
    begin
      // 利用TImage.Picture.Bitmap的图像句柄Handle和调色板属性Palette建立一个GDI+位图
      with Bmp do
      Img := TGpBitmap.Create(Handle, Palette);
      // 转换为Jpg格式保存
      if GetEncoderClsid('image/Jpg', Clsid) then
        bmp.Save('c:\a.Jpg', Clsid);
      Img.free ;
    end;
      

  12.   

    1.完成端口不只是用于网络,文件IO也有相当的优势,相关的ReadFile + Overlapped I/O,应该有很多介绍。
    2.多线程的利用是当另一个线程在处理的时间来读取文件从而提升I/O系统的利用率,如果确实是卡在IO上面,这个作用也不会太大。
    3.如果同一个目录下文件过多,很明显文件的定位命中率会降低。
    4.如果文件系统(磁盘)碎片过多,导致文件几乎都不是连续存储的,即使是个10兆的文件也不会比零散的多个文件快。
    5.完成端口也许能够帮上一点点忙。
      

  13.   

    文件的IO应该不象楼主说的那么慢,而关键应该在于JPEG的解码。
      

  14.   

    ReadBmp.Assign(ReadJPG)换成ReadBmp.Width := ReadJPG.Width;
    ReadBmp.Height := ReadJPG.Height;
    ReadBmp.Canvas.Draw(0, 0, ReadJPG);
      

  15.   

    其实你可以测试一下,究竟是哪一步花的时间过多。var
      Readbmp: TBitmap;
      Memorybmp: TBitmap;
      ReadJPG: TJpegImage;
      writeJPG: TJpegImage;
      st, t1, t2, t3, t4, t5: integer;
    begin
      Readbmp:=TBitmap.Create;
      Memorybmp:=TBitmap.Create;
      ReadJPG := TJpegImage.Create;
      writeJPG := TJpegImage.Create;
      st := GetTickCount();
      ReadJPG.LoadFromFile(PicName); // 费时 t1
      t1 := GetTickCount();
      ReadBmp.Assign(ReadJPG); // 费时 t2
      t2 := GetTickCount();
      ReadBmp.Assign(RightTopPoint(ReadBmp)); // 费时 t3
      t3 := GetTickCount();
      writeJPG.Assign(LeftBottomPoint(ReadBmp)); // 费时 t4
      t4 := GetTickCount();
      writeJPG.SaveToFile(SavePath+ExtractFileName(PicName)); // 费时 t5
      t5 := GetTickCount();  Dec(t5, t4);
      Dec(t4, t3);
      Dec(t3, t2);
      Dec(t2, t1);
      Dec(t1, st);  Readbmp.Free;
      Memorybmp.Free;
      ReadJPG.Free;
      writeJPG.Free;
    end;
      

  16.   

    经过测试以下为10次测试的数据ms读取,解码,处理并编码,写入硬盘
    31,953,47,813
    0,875,62,954
    0,859,62,891
    0,891,47,1031
    0,875,47,828
    0,890,63,969
    31,859,47,781
    16,875,47,953
    0,859,63,797
    0,890,47,953目前我的主要代码如下://处理函数
    function Pnt(ReadBmp:TBitmap):TBitmap;
    var
      I,J,m,l: Integer;
      PRGB: pRGBTriple;
      Gray,Rcolor,Gcolor,Bcolor: Byte;
      str: string;
      min,max:integer;
    begin
      Rcolor:=GetRValue(form1.pnlColor.Color);
      Gcolor:=GetGValue(form1.pnlColor.Color);
      Bcolor:=GetBValue(form1.pnlColor.Color);
      min:=StrToInt(form1.Edit1.Text);
      max:=StrToInt(form1.Edit2.Text);
      for l := 0 to form1.ListBox1.Count - 1 do
      begin
        with arrRecShap[l] do
        for I := Y to Y+H do
        begin
          PRGB := ReadBmp.ScanLine[I];
          inc(PRGB,X);
          for J := X to X+W do
          begin
            Gray := (30*PRGB^.rgbtRed+59*PRGB^.rgbtGreen+11*PRGB^.rgbtBlue)div 100;
            if (Gray >= min)and(Gray <= max) then
            begin
              PRGB^.rgbtRed :=Rcolor;
              PRGB^.rgbtGreen :=Gcolor;
              PRGB^.rgbtBlue :=Bcolor;
            end;
            Inc(PRGB);
          end;
        end;
      end;
      Result := ReadBmp;
    end;//主过程
      self.getFilePath;///执行完保存对话框开始计时
      Readbmp1:=TBitmap.Create;
      ReadJPG1 := TJpegImage.Create;
      writeJPG1 := TJpegImage.Create;
      try
        M:=0;
        while m<=PicMemo.Items.Count - 1 do
        begin
          Application.ProcessMessages;
          if PicMemo.Items.Count >=2 then
          ProgressBar1.Position := Round(m*100/(PicMemo.Items.Count - 1));
          PicName1 := Picmemo.Items.Strings[m];
          st := GetTickCount();
          ReadJPG1.LoadFromFile(PicName1);
          t1 := GetTickCount();      ReadBmp1.Assign(ReadJPG1);
          t2 := GetTickCount();
          writeJPG1.Assign(Pnt(ReadBmp1));
          t3 := GetTickCount();      writeJPG1.SaveToFile(SavePath+ExtractFileName(PicName1));
          t4 := GetTickCount();
         Dec(t4, t3);
         Dec(t3, t2);
         Dec(t2, t1);
         Dec(t1, st);
         memo1.Lines.Add(inttostr(t1)+','+inttostr(t2)+','+inttostr(t3)+','+inttostr(t4));
        inc(m);
        end;
      finally
        Readbmp1.Free;
        ReadJPG1.Free;
        writeJPG1.Free;
      end;我性能的提高主要是减少了循环过程内的函数的调用。
      

  17.   

    我正在使用GDI+替换JPG解码过程,看看能不能提高。另外我想使用多线程,一边读,一边解码,一边写,具体如何做呢,请高手指点。还有“僵哥” 是不是暴牙兔
      

  18.   


    你所计算的写入应该同时包含了编码的过程。估计换用GDI Plus会好一些。
      

  19.   

    应该不包含编码
    这句是写入硬盘的
     writeJPG1.SaveToFile(SavePath+ExtractFileName(PicName1));
    编码的应该是这句吧
    writeJPG1.Assign(Pnt(ReadBmp1));gid plus的我正在试
    另外完成端口的以及多线程的是什么思路,谁给说下
      

  20.   

    procedure TGraphic.SaveToFile(const Filename: string);
    var
      Stream: TStream;
    begin
      Stream := TFileStream.Create(Filename, fmCreate);
      try
        SaveToStream(Stream);
      finally
        Stream.Free;
      end;
    end;procedure TJPEGImage.SaveToStream(Stream: TStream);
    begin
      JPEGNeeded;
      with FImage.FData do
        Stream.Write(Memory^, Size);
    end;procedure TJPEGImage.JPEGNeeded;
    begin
      if FImage.FData = nil then
        Compress;
    end;
      

  21.   

    原来如此
    多谢unsigned 
      

  22.   

    以下为10次测试的数据ms 读取图片所需时间,解码所需时间,处理所需时间,编码并写入硬盘所需时间 
    31ms           953ms       47ms       813ms 
    以下同,这下能看懂了吧
    0,875,62,954 
    0,859,62,891 
    0,891,47,1031 
    0,875,47,828 
    0,890,63,969 
    31,859,47,781 
    16,875,47,953 
    0,859,63,797 
    0,890,47,953 大概我搞错了 ,暴牙兔是另外一个DELPHI高手的id
      

  23.   

    经过不懈努力,使用GDI PLUS后,处理每张图片的时间缩短为0.89秒/张但现在是每循环一次都要创建一次Tbitmap,求教使用全局变量的方法。我现在的主要代码如下:
    var
      Readbmp: TBitmap;
      ReadJPG,writeJPG: TGPBitmap;
      picname,str: String;
      m,l: Integer;
      Date1:TDateTime;
      Clsid: TGUID;
    begin
    //********************************
      self.getFilePath;///执行完保存对话框开始计时
      Date1 := now;
      Readbmp:=TBitmap.Create;
      GetEncoderClsid('image/jpeg', Clsid);
      try
        for m := 0 to PicMemo.Items.Count - 1 do
        begin
          Application.ProcessMessages;
          if PicMemo.Items.Count >=2 then
          ProgressBar1.Position := Round(m*100/(PicMemo.Items.Count - 1));
          PicName := Picmemo.Items.Strings[m];
          //读JPEG
          ReadJPG := TGPBitmap.Create(PicName);
          //转BMP
          ReadBmp.Handle := ReadJPG.getHBITMAP(0);
          //处理BMP+转JPG
          ReadBmp.Assign(Pnt(ReadBmp));
          WriteJPG := TGPBitmap.Create(ReadBmp.Handle,ReadBmp.Palette);
          //保存JPEG
          WriteJPG.Save(SavePath+ExtractFileName(PicName), Clsid);
          ReadJPG.Free;
          writeJPG.Free;
        end;
      finally
        Readbmp.Free;
      end;
      ProgressBar1.Position := 0;
      ShowMessage('处理图片完成 用时:'+formatDateTime('hh:mm:ss zzz',now-date1));
      

  24.   

    另外多线程的如何做,我现在的CPU使用率才50%左右,想充分利用CPU,我为此换了
    4核CPU Q8200和4G内存
      

  25.   

    Delphi自带的Source的Internet目录当中有一个ISAPIThreadPool.pas,可以做参考.它使用的是完成端口,顺便也可以学着使用完成端口.
      

  26.   

    你的CPU多,解码与编码分开到两个线程里。
      

  27.   

    换四核机器后,单线程下处理每张图片0.62秒/张,CPU使用率才25%
      

  28.   

    现在是每循环一次都要创建一次Tbitmap,求教使用全局变量的方法。 
      

  29.   

    我又为本帖加了100分,还有两个问题没有解决,一个是多线程使用完成端口的问题,另一个就是
    现在是每循环一次都要创建一次Tbitmap,求教使用全局变量的方法
      

  30.   

    在TForm类里建个Tbitmap对象就可以了
      

  31.   

    1.你所需要的只是一个HBitmap,可以创建TBitmap实例,也可以复用同一个TBitmap实例。2.多线程不一定是完成端口,之所以推荐使用完成端口,是在于完成端口本身就是一个高效的队列,进行线程之间的数据传递会简单得多,而不需要自己再加上同步锁。
      

  32.   

    type
      TGDIThread = class(TThread)
        private
          FIOCPQueue: TIOCP ;
        protected
          procedure Execute; override;
        public
          constructor Create(CreateSuspended: Boolean; AIOCPQueue: TIOCP);
          destructor Destroy; override;
      end;var
      gCounter: Integer = 0;procedure ProcessImage(tempBMP: TBitmap; const ASourceFile, DestPath: AnsiString);inline;
    begin
      //这里处理
    end;constructor TGDIThread.Create(CreateSuspended: Boolean; AIOCPQueue: TIOCP);
    begin
      Inherited Create(true);
      AIOCPQueue.Attach;
      FIOCPQueue := AIOCPQueue;
      if Not CreateSuspended then Resume;
    end;procedure TGDIThread.Execute;
    var
      pSourceJpeg, pDestPath: PAnsiString;
      lpOverlapped: POverlapped;
      tmpBMP: TBitmap;
    begin
      tmpBMP := TBitmap.Create;
      try
        while FIOCPQueue.GetQueuedCompletionStatus(DWORD(pSourceJpeg), DWORD(pDestPath), lpOverlapped) do
        begin
          if DWORD(lpOverlapped) = SHUTDOWN_FLAG then break;
          try
            ProcessImage(tmpBMP, pSourceJpeg^, pDestPath^);
            InterlockedDecrement(gCounter);
          finally
            DisposeStr(pSourceJpeg);
            DisposeStr(pDestPath);
          end;
        end;
      finally
        tmpBMP.Free;
        Terminate;
      end;
    end;Destructor TGDIThread.Destroy;
    begin
      FIOCPQueue.Free;
      Inherited;
    end;
    procedure TForm1.Button1Click(Sender: TObject);
    var
      I: Integer;
      ProcessThread: TGDIThread;
      SystemInfo: TSystemInfo;
      PicName: AnsiString;
    begin
      date1 := now;
      Button1.Enabled := false;  GetSystemInfo(SystemInfo);
      for I := 0 to SystemInfo.dwNumberOfProcessors do
        begin
          ProcessThread := TGDIThread.Create(false, FIOCPQueue);
          ProcessThread.FreeOnTerminate := true;
          ProcessThread.Resume;
        end;  for I := 0 to PicMemo.Items.Count - 1 do
        begin
          PicName := Picmemo.Items.Strings[I];
          if PicName = '' then continue;
          if FIOCPQueue.PostQueuedCompletionStatus(LongWord(NewStr(PicName)), Longword(NewStr(SavePath)), 0) then InterlockedIncrement(gCounter);
        end;  Timer1.Interval := 100;
      Timer1.Enabled := true;
    end;procedure TForm1.FormCreate(Sender: TObject);
    begin
      FIOCPQueue := TIOCP.Create;
    end;
    procedure TForm1.FormDestroy(Sender: TObject);
    begin
      FIOCPQueue.ShutDownAllEx;
      FIOCPQueue.Free;
    end;procedure TForm1.Timer1Timer(Sender: TObject);
    begin
      if gCounter = 0 then begin
        Timer1.Enabled := false;
        FIOCPQueue.ShutDownAllEx;
        ShowMessage('处理图片完成 用时:'+formatDateTime('hh:mm:ss zzz',now-date1));
        Button1.Enabled := true;
      end;
    end;
      

  33.   

    TIOCP类参见:http://topic.csdn.net/u/20080527/12/ab4518d9-979f-468d-bdb5-8638b504de7a.html第五楼
      

  34.   

    直接四个线程,每个线程包含文件读取、图像处理及文件写入的全部代码,也就是说这样单独一个线程就能进行工作,为了提高CPU使用率,这样的线程创建四个,也就是可以同时处理4个文件。
    有三个关键的线程变量
    1. 工作状态: 空闲、正在处理
    2. 源文件名
    3. 目标文件名工作状态为空闲时,程序的主线程给该线程设置源文件名、目标文件名,置工作状态为“正在处理”,结程检测到工作状态为正在处理,就进行图像读取及处理保存,完成后再置线程状态为空闲。
      

  35.   

    to ahjoe ,上面的开四个线程的方法不行,我开两个线程的时候,每个线程都独立读取、处理、写入,总时间就已经增加了。
    其他的正在试
      

  36.   

    while FIOCPQueue.GetQueuedCompletionStatus(DWORD(pSourceJpeg), DWORD(pDestPath), lpOverlapped, INFINITE(*上面贴的代码当中少了这个参数*)) do
      

  37.   

    多线程通过更好地利用CPU等一些空闲资源而达到更高的(用户体验)效率。