Delphi 的并行计算

所谓并行计算,可以让一段代码让 CPU 的多个核同时开跑,非常明显地提高代码执行速度。

所谓“程序”,这个中文单词,严格意义上来说,就是按照特定顺序,一步一步地执行一些指令。这是标准的串行计算。串行计算的好处是有上下文依赖关系的事情,不会搞错顺序。好比先洗碗,再打饭,程序这样写了,计算机绝对不会搞错成先打了饭吃完了才发现碗先没洗导致肚子疼。


但很多时候,我们有大量数据需要计算,并且数据之间没有前后依赖关系。比如:图片处理。我需要逐个像素处理,但处理第一个像素和处理第二个像素,没有依赖关系。处理第一行和处理第二行,也没有依赖关系。这时候,可以同时并行地处理。传统做法是,我们写一个循环,一行一行的逐行处理;在一行中,我们写个循环,一个像素一个像素地逐个处理。最终结果就是一张图片,我们是逐个像素地处理。如果图片有 100 万个像素,就要循环 100 万次。很消耗时间。


现代的 CPU,都是多核的。一个循环跑下来,只使用了一个核。其它几个核都空闲着。并行计算的概念就是,把上面说的那种没有先后依赖关系的数据处理,分到几个核里面同时处理。就好比有 12 个碗要洗,找一个人来一只一只地洗,需要12分钟;找4个人来一起洗,每个人只需要洗三个碗,洗完 12 个碗只需要3分钟。


Delphi 已经支持并行计算。语法上,该怎么写呢?我们来看一个例子。这个例子的代码,是我自己写了以后测试过的(在英巴的官方论坛上请教了一下,得到 Delphi 官方人员的帮助下写出来的代码)。测试程序是一个跑在手机里的 APP,测试 Delphi 的并行计算代码跑安卓手机上面,是否能利用手机 CPU 的多个核。这个代码完成一个图片逐个像素的处理:将 RGB565 格式的图片转为 RGB888 的图片,每个像素单独计算。注意这是 Delphi 写的手机程序,是基于 FireMonkey 框架的,里面的 TBitmap 和 VCL 底下的 TBitmap 有点不一样。


看代码,第一个函数,对图片像素的一行,做一个循环,将一行里面的每个像素拿出来,做一次转换计算:

//这是计算一行的颜色值。其中 InputPtr 是 RGB565 的数据在内存里面的指针;ColorPtr 是 TBitmap 的 TBitmapData 的指针。 
//BTW: 以下代码有点问题:颜色值可能搞错,出来的结果,灰色正确,黄色变成了蓝色。图像清晰度没问题。

procedure TForm1.ConvertOneLine(const BmpWidth: Integer; InputPtr: PWord;
  ColorPtr: PByte);
var
  Col: Integer;
  Color: Word;
begin
  for Col := 1 to BmpWidth do
  begin
    Color := InputPtr^;
    Inc(InputPtr);

    ColorPtr^ := (( Color         and $1F) * $FF) div $1F;
    Inc(ColorPtr);

    ColorPtr^ := (((Color shr  5) and $3F) * $FF) div $3F;
    Inc(ColorPtr);

    ColorPtr^ := (((Color shr 11) and $1F) * $FF) div $1F;
    Inc(ColorPtr);

    ColorPtr^ := $FF;
    Inc(ColorPtr);;
  end;
end;


第二个函数,是对图片的行数,做一个循环,逐行处理。这个函数里面调用上面那个一行里面逐点处理的方法。实际上就是两层循环:


//以下代码,有串行计算的代码,也有并行计算的代码,都测试通过。

procedure TForm1.ConvertRGB565ToRGB8888(const BmpWidth, BmpHeight: Integer; RGB565DataPtr: PWord; BmpData: TBitmapData);
var
  InputPitch: Integer;
  Row, Col: Integer;
  Color: Word;
  ColorPtr: PByte;
  InputPtr: PWord; // Pointer to RGB565 data
begin
{------------------------------------------------------------------------
  把 RGB565 的数据转换到 FireMonkey 的 TBitmap 里面去。算法来自 Delphi 官方网站论坛里面的一个程序员对我的问题的回复。
------------------------------------------------------------------------}
  InputPitch:= BmpWidth*2;


  //将下面的串行循环,改为并行循环,在4核手机上测试通过。
  //大概比串行循环的时间少一半(并没有少 1/4)。
  TParallel.For(1, BmpHeight, procedure(Row: Integer)
  begin
    InputPtr:= PWord(PByte(RGB565DataPtr) + (Row-1)*InputPitch);
    ColorPtr:= PByte(BmpData.Data) + (BmpHeight-Row)*BmpData.Pitch;

    Self.ConvertOneLine(BmpWidth, InputPtr, ColorPtr);
  end

  );

  { 测试结果:以下串行计算代码执行正确。
  for Row := 1 to BmpHeight do
  begin
    InputPtr:= PWord(PByte(RGB565DataPtr) + (Row-1)*InputPitch);
    ColorPtr:= PByte(BmpData.Data) + (BmpHeight-Row)*BmpData.Pitch;

    Self.ConvertOneLine(BmpWidth, InputPtr, ColorPtr);
    //TParallel.&For
  end;
  }
end;


下面是主程序,输入一张图片,调用上面那个函数,完成转换:

//以下代码是调用 ConvertRGB565ToRGB8888 的主程序:

procedure TForm1.DrawRGB656OntoBMP;
var
  //直接将 RGB656 的数据画到 BITMAP 上去试试看:
  ABitmap: TBitmap;
  BmpData: TBitmapData;
  BmpWidth: Integer;
  BmpHeight: Integer;
  RGB565DataPtr, InputPtr: PWord; // Pointer to RGB565 data
  AMemoryStream: TMemoryStream;
  Fn: string;
  T: TDateTime;
  InputPitch: Integer;
  Row, Col: Integer;
  Color: Word;
  ColorPtr: PByte;
begin
// 这段代码的测试结果:1. 在安卓下,直接出现浮点运算异常错误;
//2. 在 WINDOWS 底下,执行完成,但没有图像显示。并且转换过程耗时 200MS.跟踪 ScanlineToAlphaColor 函数内部,实际上是逐个点转换为 RGB888
  Fn := Memo1.Lines.Strings[0];

  AMemoryStream := TMemoryStream.Create;
  AMemoryStream.LoadFromFile(Fn);

  try

    //ABitmap := Image1.Bitmap;

    BmpWidth:= 1280;
    BmpHeight:= 720;

    Image1.Bitmap.SetSize(BmpWidth, BmpHeight);
    RGB565DataPtr := PWord(NativeInt(AMemoryStream.Memory) + 66);
    InputPitch:= BmpWidth*2; // Bytes by row

    T := Now;
    if Image1.Bitmap.Map(TMapAccess.Write, BmpData) then  //Map 方法取得 Bitmap 内部的数据结构,然后才能直接操作数据结构内部的指针。
    try

      Self.ConvertRGB565ToRGB8888(BmpWidth, BmpHeight, RGB565DataPtr, BmpData);
    finally
      Image1.Bitmap.Unmap(BmpData);
      Image1.UpdateEffects;
    end;

    Memo1.Lines.Add('time consuming = ' + IntToStr(MilliSecondsBetween(Now, T)));
  finally
    AMemoryStream.Free;
  end;
end;


测试结果:

对720P 的 RGB565 转码为 RGBA888,串行计算在 Find5 手机上耗时58MS,并行计算耗时 32MS。


结论:Delphi 的并行计算代码,确实可以让多个核同时跑起来,缩短计算时间。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值