DrawLine和DrawLines之间的性能差异?

时间:2009-12-02 12:00:46

标签: visual-c++ gdi+

我在C ++中使用GDI +来绘制图表控件。我想知道上述两个函数之间是否存在任何性能差异。我并不懒于为 DrawLines()编写代码,但这样做会使我的代码变得复杂。因此,我要权衡是否以更低的可读性和可能增加的错误和错误为代价来加快代码执行的速度。

任何帮助都值得赞赏。 Eraj。

1 个答案:

答案 0 :(得分:6)

对于大多数绘图活动,两者之间应该没有显着差异,但可以肯定的是,我编写了一个测试项目来比较它们之间的差异(实际上,其中有3个)。

对于我机器上的大量行(x25000),DrawLines()(640ms)比DrawLine()(420ms)快约50%。说实话,我第一次误读了这个问题并在C#中编写了我的初始测试。两者之间的性能大致相同,这是因为.NET Graphics基于GDI +。

出于好奇,我尝试了常规的GDI,我希望它会更快。使用win32 PolyLine()(530ms)功能的速度提高了约20%,拥有45000行。这比使用GDI + DrawLines()快116%。或许更令人惊叹的是,使用win32 LineTo()代替GDI + DrawLine()会导致125ms以下的时间。假设时间为125毫秒和45000行,此方法至少 800%更快。 (计时器分辨率和线程时序使得很难在不使用QueryPerformanceCounter和更高频率的其他计时方法的情况下测量此阈值中的性能。)

但是,我应该提醒我不要假设这是绘制代码的一个重要瓶颈。可以进行的许多性能改进与必须绘制的对象无关。我猜你的要求可能会要求在你的控制正常操作中可能需要绘制几百个项目。在这种情况下,我建议您尽可能简单地编写绘图代码并且没有错误,因为调试绘图问题可能需要花费大量时间,而且可能不太有益于改进控件或应用程序的其余部分。

此外,如果您需要主动更新数千个项目,那么通过转向后备缓冲解决方案,您将看到更高的性能提升。除了管理屏幕外缓冲区外,这还应该可以更容易地开发代码来绘制控件。

以下是我的源代码示例。它们中的每一个都处理鼠标点击,以便在使用批量绘图和逐项绘制之间交替使用。

GDI +,托管在准系统MFC SDI App

这假设某人已经声明了GDI +标题并编写了代码来初始化/拆除GDI +。

在ChildView.h中

// Attributes
public:
  bool m_bCompositeMode;

// Operations
public:
  void RedrawScene(Graphics &g, int lineCount, int width, int height);
  PointF *CreatePoints(int lineCount, int width, int height);
  void ReportTime(Graphics &g, int lineCount, DWORD tickSpan);

public:
  afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

在ChildView.cpp中,添加到PreCreateWindow()

m_bCompositeMode = false;

ChildView.cpp的剩余,包括OnPaint()和消息映射更改。

BEGIN_MESSAGE_MAP(CChildView, CWnd)
  ON_WM_PAINT()
  ON_WM_LBUTTONUP()
END_MESSAGE_MAP()

void CChildView::OnPaint() 
{
  CPaintDC dc(this); // device context for painting
  RECT rcClient;
  ::GetClientRect(this->GetSafeHwnd(), &rcClient);

  Graphics g(dc.GetSafeHdc());
  g.Clear(Color(0, 0, 0));

  RedrawScene(g, 25000, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);
}

void CChildView::RedrawScene(Graphics &g, int lineCount, int width, int height)
{
  DWORD tickStart = 0;
  DWORD tickEnd = 0;

    Pen p(Color(0, 0, 0x7F));
    PointF *pts = CreatePoints(lineCount, width, height);
    tickStart = GetTickCount();
    if (m_bCompositeMode)
    {
        g.DrawLines(&p, pts, lineCount);
    }
    else
    {
        int i = 0;
        int imax = lineCount - 1;
        for (i = 0; i < imax; i++)
        {
            g.DrawLine(&p, pts[i], pts[i + 1]);
        }
    }
    tickEnd = GetTickCount();
  delete[] pts;
    ReportTime(g, lineCount, tickEnd - tickStart);
}

void CChildView::ReportTime(Graphics &g, int lineCount, DWORD tickSpan)
{
  CString strDisp;
  if(m_bCompositeMode)
  {
    strDisp.Format(_T("Graphics::DrawLines(Pen *, PointF *, INT) x%d took %dms"), lineCount, tickSpan);
  }
  else
  {
    strDisp.Format(_T("Graphics::DrawLine(Pen *, PointF, PointF) x%d took %dms"), lineCount, tickSpan);
  }

    // Note: sloppy, but simple.
  Font font(L"Arial", 14.0f);
  PointF ptOrigin(0.0f, 0.0f);
  SolidBrush br(Color(255, 255, 255));
    Status s = g.DrawString(strDisp, -1, &font, ptOrigin, &br);
}

PointF* CChildView::CreatePoints(int lineCount, int width, int height)
{
  if(lineCount <= 0)
  {
    PointF *ptEmpty = new PointF[2];
    ptEmpty[0].X = 0;
    ptEmpty[0].Y = 0;
    ptEmpty[1].X = 0;
    ptEmpty[1].Y = 0;
    return ptEmpty;
  }

  PointF *pts = new PointF[lineCount + 1];
  int i = 1;
  while(i < lineCount)
  {
    pts[i].X = (float)(rand() % width);
    pts[i].Y = (float)(rand() % height);
    i++;
  }
  return pts;
}

void CChildView::OnLButtonUp(UINT nFlags, CPoint point)
{
  m_bCompositeMode = !m_bCompositeMode;
  this->Invalidate();

  CWnd::OnLButtonUp(nFlags, point);
}

C#.NET,托管在基础WinForms应用程序中,默认类为Form1

如果要比较两者,请为表单设置默认大小,等于MFC版本的大小。也可以添加尺寸更改处理程序。

public Form1()
{
    InitializeComponent();
    bCompositeMode = false;
}

bool bCompositeMode;

private void Form1_Paint(object sender, PaintEventArgs e)
{
    e.Graphics.Clear(Color.Black);
    RedrawScene(e.Graphics, 25000, this.ClientRectangle.Width, this.ClientRectangle.Height);
}

private void RedrawScene(Graphics g, int lineCount, int width, int height)
{
    DateTime dtStart = DateTime.MinValue;
    DateTime dtEnd = DateTime.MinValue;
    using (Pen p = new Pen(Color.Navy))
    {
        Point[] pts = CreatePoints(lineCount, width, height);
        dtStart = DateTime.Now;
        if (bCompositeMode)
        {
            g.DrawLines(p, pts);
        }
        else
        {
            int i = 0;
            int imax = pts.Length - 1;
            for (i = 0; i < imax; i++)
            {
                g.DrawLine(p, pts[i], pts[i + 1]);
            }
        }
        dtEnd = DateTime.Now;
    }
    ReportTime(g, lineCount, dtEnd - dtStart);
}

private void ReportTime(Graphics g, int lineCount, TimeSpan ts)
{
    string strDisp = null;
    if (bCompositeMode)
    {
        strDisp = string.Format("DrawLines(Pen, Point[]) x{0} took {1}ms", lineCount, ts.Milliseconds);
    }
    else
    {
        strDisp = string.Format("DrawLine(Pen, Point, Point) x{0} took {1}ms", lineCount, ts.Milliseconds);
    }

    // Note: sloppy, but simple.
    using (Font font = new Font(FontFamily.GenericSansSerif, 14.0f, FontStyle.Regular))
    {
        g.DrawString(strDisp, font, Brushes.White, 0.0f, 0.0f);
    }
}

private Point[] CreatePoints(int count, int width, int height)
{
    Random rnd = new Random();
    if (count <= 0) { return new Point[] { new Point(0,0), new Point(0,0)}; }
    Point[] pts = new Point[count + 1];
    pts[0] = new Point(0, 0);
    int i = 1;
    while (i <= count)
    {
        pts[i] = new Point(rnd.Next(width), rnd.Next(height));
        i++;
    }
    return pts;
}

private void Form1_Click(object sender, EventArgs e)
{
    bCompositeMode = !bCompositeMode;
    Invalidate();
}

常规GDI,托管在准系统MFC SDI App

在ChildView.h中

// Attributes
public:
  bool m_bCompositeMode;

// Operations
public:
  void RedrawScene(HDC hdc, int lineCount, int width, int height);
  POINT *CreatePoints(int lineCount, int width, int height);
  void ReportTime(HDC hdc, int lineCount, DWORD tickSpan);

public:
  afx_msg void OnLButtonUp(UINT nFlags, CPoint point);

在ChildView.cpp中

更新PreCreateWindow(),就像在GDI +示例中一样。

BEGIN_MESSAGE_MAP(CChildView, CWnd)
  ON_WM_PAINT()
  ON_WM_LBUTTONUP()
END_MESSAGE_MAP()

void CChildView::OnPaint() 
{
  CPaintDC dc(this); // device context for painting
  HDC hdc = dc.GetSafeHdc();

  HBRUSH brClear = (HBRUSH)::GetStockObject(BLACK_BRUSH);
  RECT rcClient;
  ::GetClientRect(this->m_hWnd, &rcClient);
  ::FillRect(hdc, &rcClient, brClear);
  ::DeleteObject(brClear);

  RedrawScene(hdc, 45000, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top);
}

void CChildView::RedrawScene(HDC hdc, int lineCount, int width, int height)
{
  DWORD tickStart = 0;
  DWORD tickEnd = 0;

  HPEN p = ::CreatePen(PS_SOLID, 1, RGB(0, 0, 0x7F));
  POINT *pts = CreatePoints(lineCount, width, height);
  HGDIOBJ prevPen = SelectObject(hdc, p);
  tickStart = GetTickCount();
  if(m_bCompositeMode)
  {
    ::Polyline(hdc, pts, lineCount);
  }
  else
  {
    ::MoveToEx(hdc, pts[0].x, pts[0].y, &(pts[0]));
    int i = 0;
    int imax = lineCount;
    for(i = 1; i < imax; i++)
    {
      ::LineTo(hdc, pts[i].x, pts[i].y);
    }
  }
  tickEnd = GetTickCount();
  ::SelectObject(hdc, prevPen);
  delete pts;
  ::DeleteObject(p);

  ReportTime(hdc, lineCount, tickEnd - tickStart);
}

POINT *CChildView::CreatePoints(int lineCount, int width, int height)
{
  if(lineCount <= 0)
  {
    POINT *ptEmpty = new POINT[2];
    memset(&ptEmpty, 0, sizeof(POINT) * 2);
    return ptEmpty;
  }

  POINT *pts = new POINT[lineCount + 1];
  int i = 1;
  while(i < lineCount)
  {
    pts[i].x = rand() % width;
    pts[i].y = rand() % height;
    i++;
  }
  return pts;
}

void CChildView::ReportTime(HDC hdc, int lineCount, DWORD tickSpan)
{
  CString strDisp;
  if(m_bCompositeMode)
  {
    strDisp.Format(_T("PolyLine(HDC, POINT *, int) x%d took %dms"), lineCount, tickSpan);
  }
  else
  {
    strDisp.Format(_T("LineTo(HDC, HPEN, int, int) x%d took %dms"), lineCount, tickSpan);
  }

  HFONT font = (HFONT)::GetStockObject(SYSTEM_FONT);
  HFONT fontPrev = (HFONT)::SelectObject(hdc, font);

  RECT rcClient;
  ::GetClientRect(this->m_hWnd, &rcClient);
  ::ExtTextOut(hdc, 0, 0, ETO_CLIPPED, &rcClient, strDisp.GetString(), strDisp.GetLength(), NULL);
  ::SelectObject(hdc, fontPrev);
  ::DeleteObject(font);
}

void CChildView::OnLButtonUp(UINT nFlags, CPoint point)
{
  m_bCompositeMode = !m_bCompositeMode;
  this->Invalidate();

  CWnd::OnLButtonUp(nFlags, point);
}