Results 1 to 17 of 17

Thread: [RESOLVED] What happened to simple speed?

  1. #1

    Thread Starter
    Addicted Member cheesebrother's Avatar
    Join Date
    Jul 2011
    Posts
    153

    Resolved [RESOLVED] What happened to simple speed?

    #1. Write code
    #2. Debug
    #3. Make twice as fast
    #4. Make even faster...

    Anyway that's the way it usally is for me when i'm drawing stuff on a form. Now i've come to quite a different problem - that of drawing a rectangle at a reasonable speed, this time i have no idea how to make it faster. For my speed experimenting i'm covering a form with recangles but it's REALLY SLOW. I'm hoping that i'm missing a major concept in graphics drawing in vb2010 so here's my code so that you can tell me what's wrong:


    Code:
    Public Class Form1
        Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
            Dim s = Microsoft.VisualBasic.Timer
            Dim x As Integer, y As Integer, r As Rectangle, g As Graphics = Me.CreateGraphics
            g.Clear(Me.BackColor)
            For x = 0 To Me.Width Step 30
                For y = 0 To Me.Height Step 15
                    r = New Rectangle(x, y, 30, 15)
                    g.DrawRectangle(Pens.Black, r)
                Next
            Next
            Debug.Print(Microsoft.VisualBasic.Timer - s)
        End Sub
    End Class
    Guess what? That takes about .15 seconds to draw each time!

    I did a direct comparison to vb6 using this code:

    Code:
    Private Sub Form_Paint()
      Dim start
      start = Timer
      Cls
      For X = 0 To Me.ScaleWidth Step 30
          For Y = 0 To Me.ScaleHeight Step 15
              Me.Line (X, Y)-(X + 30, Y + 30), vbBlack, B
          Next
      Next
      Debug.Print Timer - start
    End Sub
    Guess what? That takes about .015625 seconds to draw each time!

    Something has got to be really wrong with my method in .net. I'm just waiting to kick myself
    Hooked for good.

  2. #2
    Super Moderator jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    110,299

    Re: What happened to simple speed?

    First things first, if you ever create a Graphics object, always make sure you dispose it. Secondly, never call CreateGraphics. Have you read the documentation for the Paint event? I can only assume not if you don't know that it provides its own Graphics object.

    As for the question, the fact that you are calling DrawRectangle means that you are drawing four times as many lines as you need to. What you're doing is basically drawing grid lines on the form, so why not do just that: draw lines? If you simply draw each vertical line with a single DrawLine call and the same for each horizontal line then I would expect things to be faster.

    That said, if you're not changing any of the colours or dimensions then I would think that it would be more efficient to simply create an Image, set that as the form's BackgroundImage and tile it.

  3. #3
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: What happened to simple speed?

    It's one thing to teach a man to fish, it's another to gesture at a lake and a fishing pole Personally I remember trying to learn the ins and outs of System.Graphics from documentation and it was overwhelming and bewildering. I learned tons more from blog and forum posts like this.

    Here's some snippets incorporating JMC's advice and some of my own. Let's start with a scratch form:
    Code:
    Public Class Form1
    
        Public Sub New()
            InitializeComponent()
    
            Me.Width = 800
            Me.Height = 600
        End Sub
    
        Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
            Dim sw = Stopwatch.StartNew()
    
            Text = String.Format("{0:N}", sw.ElapsedMilliseconds)
        End Sub
    
        Protected Overrides Sub OnClick(ByVal e As System.EventArgs)
            MyBase.OnClick(e)
    
            Me.Invalidate()
        End Sub
    
    End Class
    This lets us redraw by clicking so we can try multiple times and places the number of milliseconds per draw cycle in the caption bar. I set the width and height to a constant number because the bigger the form is the more we draw and the slower it will get. Run this and click a few times to get a feel for the baseline delay for painting a form. Shows up as 0ms for me.

    Now, a direct translation of your attempt:
    Code:
    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        Dim sw = Stopwatch.StartNew()
    
        Dim g = e.Graphics
        g.Clear(BackColor)
        For x As Integer = 0 To Me.ClientRectangle.Width Step 30
            For y = 0 To Me.ClientRectangle.Height Step 15
                e.Graphics.DrawRectangle(Pens.Black, x, y, 30, 15)
            Next
        Next
    
        Text = String.Format("{0:N}", sw.ElapsedMilliseconds)
    End Sub
    PaintEventArgs already provides a Graphics object for the form. In GDI terms, this is like an HDC. They're expensive, and limited by the system. Worse, if you create one yourself you're responsible for disposing it, but your code doesn't do that. This code doesn't worry about it because the Graphics provided by PaintEventArgs isn't yours to dispose. It hovers around 300ms on my machine, 0.3s. But as JMC pointed out, you're drawing many, many redundant lines.

    I don't understand how the VB6 Line function worked, but here's how I would do gridlines in .NET if I wanted to do them by hand:
    Code:
    Dim sw = Stopwatch.StartNew()
    
    Dim g = e.Graphics
    g.Clear(BackColor)
    For x As Integer = 0 To Me.ClientRectangle.Width Step 30
        g.DrawLine(Pens.Black, x, 0, x, Me.ClientRectangle.Height)
    Next
    
    For y = 0 To Me.ClientRectangle.Height Step 15
        g.DrawLine(Pens.Black, 0, y, Me.ClientRectangle.Width, y)
    Next
    
    Text = String.Format("{0:N}", sw.ElapsedMilliseconds)
    This hovers around 30ms, 0.03s. I can't compare with the VB6 because your machine is different, the form size might be different, and I don't fully understand the Line() function, but this is a hundredfold improvement over using DrawRectangle(). If your numbers are correct it is on par with VB6 assuming the ratio is constant.

    All I can say to learn more is read. A lot. It took me the better part of two years to understand enough about graphics in .NET to be dangerous. Don't read just the documentation, find tutorials and samples. Not all of them use good practices, but if you read enough of them you'll figure out which ones are bad.

    *edit*
    And if you want to make things really complicated, set your form's DoubleBuffered property to true. I get times ~2ms at this point. I'm not sure if offscreen drawing is faster or if it's excluding the screen drawing, but I'm sure someone else can explain.
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  4. #4

    Thread Starter
    Addicted Member cheesebrother's Avatar
    Join Date
    Jul 2011
    Posts
    153

    Re: What happened to simple speed?

    I've reworked my code with a buffer, using a graphics path(i found this slightly faster than drawing rectangles), and with proper disposal and came up with this:

    Code:
    Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
            REM Declare variables
            Dim s = Microsoft.VisualBasic.Timer
            Dim x As Integer, y As Integer
    
            Dim Buffer As Bitmap = New Bitmap(Me.Width, Me.Height)
    
            Dim g As Graphics = Graphics.FromImage(Buffer)
    
            Dim p As Drawing2D.GraphicsPath = New Drawing2D.GraphicsPath
    
    
            REM Build the path
            For x = 0 To Me.Width Step 30
                For y = 0 To Me.Height Step 15
                    p.AddRectangle(New Rectangle(x, y, 30, 15))
                Next
            Next
    
            REM Draw everything
            g.Clear(Me.BackColor)
            g.DrawPath(Pens.Black, p)
    
            REM Put the buffer onto the form
            e.Graphics.DrawImageUnscaled(Buffer, 0, 0)
    
            REM Clean up
            Buffer.Dispose()
            g.Dispose()
            p.Dispose()
    
            REM Print the time taken
            Debug.Print(Microsoft.VisualBasic.Timer - s)
        End Sub
    I've got an entirely new question (of course i expect you to criticize the code just seen). This works a lot faster with a buffer than without one - fast enough to satisfy myself. This is a really weird problem - in my program I need to call my paint event quite often in the mouse event. No, i wouldn't call it every time the mouse moves but yes, it does need to be snappy enough to update immediately. For testing purposes I call it like this:

    Code:
    Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
            Refresh()
        End Sub
    For some reason this will make the drawing process to take twice as long as it would when the paint event is fired as a result of resizing the form. This is a noticeable delay and can't exist. Really weird!!! Please help.
    Hooked for good.

  5. #5
    Stack Overflow mod​erator
    Join Date
    May 2008
    Location
    British Columbia, Canada
    Posts
    2,824

    Re: What happened to simple speed?

    The fact that it works faster with a buffer seems very odd, as it's just unnecessary overhead. Anyway, the reason for the speed improvement is that, when resizing, Windows repaints only the areas that actually need to be repainted (i.e. the side/s that was/were resized) whereas you're repainting the entire form. You can have the same behaviour by using Me.Invalidate() and passing a Rectangle or Region (for example) representing the area that needs repainting.

    Here's a rewrite of your code that may or may not be faster, too:

    Code:
        Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
            ' Start timing
            Dim s As New Stopwatch()
            s.Start()
    
            ' Get a copy of the graphics object for convenience
            Dim g As Graphics = e.Graphics
    
    
            ' Draw everything
            g.Clear(Me.BackColor)
    
            For x As Integer = 0 To Me.Width Step 30
                For y As Integer = 0 To Me.Height Step 15
                    g.DrawRectangle(Pens.Black, x, y, 30, 15)
                Next
            Next
    
            ' Stop timing
            s.Stop()
    
            ' Print the time taken
            Debug.Print(s.Elapsed.ToString())
        End Sub
    And, since you're drawing over the entire graphics surface, you can add this code into your constructor:

    Code:
    Public Sub New()
        Me.InitializeComponent()
    
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or
                   ControlStyles.UserPaint Or
                   ControlStyles.Opaque Or
                   ControlStyles.OptimizedDoubleBuffer, True)
    End Sub
    What that does is it tells the form not to paint its background because you're already doing it, plus it enables double-buffering.

  6. #6
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: What happened to simple speed?

    Wow, that first example looks zeta familiar, minitech. I wonder why

    Of course it gets slower when you "use a buffer", cheesebrother. Your code snippet is doing way too much work. The concept of a buffer isn't "create a new bitmap and draw to it every frame", it's "draw to a bitmap and display that bitmap." There's quite a few things in that code that could be improved. For example, the bitmap's going to be the same each time; why draw on it every time? Or maybe you have plans to change it but the path will be the same each time; why not cache that? Still, since it's a static image, it'd be smarter to draw the grid once in the constructor/load event and set that to the form's BackgroundImage property. Then you don't have to worry about drawing it. There's always lots of options; don't reach for the most complicated tools!
    This answer is wrong. You should be using TableAdapter and Dictionaries instead.

  7. #7
    Stack Overflow mod​erator
    Join Date
    May 2008
    Location
    British Columbia, Canada
    Posts
    2,824

    Re: What happened to simple speed?

    techgnome?

  8. #8
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: What happened to simple speed?

    Code:
     
            ' Draw everything
            g.Clear(Me.BackColor)
    This is superfluous and probably reduces performance. The form client area has already been cleared to the Backcolor in MyBase.OnPaintBackground. There is no need to do it again in the OnPaint sub or in the Paint event handler. It forces the entire client area to be repainted instead of just the lines to be drawn.

    Code:
    Public Sub New()
        Me.InitializeComponent()
    
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or
                   ControlStyles.UserPaint Or
                   ControlStyles.Opaque Or
                   ControlStyles.OptimizedDoubleBuffer, True)
    End Sub
    This is obsolete and possibly superfluous. The SetStyles statements have been replaced by the DoubleBuffered property since VB2005. You can set it in the Designer or for example in the Load event sub. Besides, it doesn't affect the speed of painting but prevents flickering if the image changes rapidly.

    Replacing the Paint event handler by an OnPaint sub obscures matters. Normally you would use OnPaint in a class definition but handle the Paint Event in an instance. Both of these are possible for a form added in Visual Studio because it is both a class and a default instance of that class. MyBase.Paint in the OnPaint sub raises the Paint event, so it is not going to make the least difference to performance.

    BB

  9. #9
    Stack Overflow mod​erator
    Join Date
    May 2008
    Location
    British Columbia, Canada
    Posts
    2,824

    Re: What happened to simple speed?

    Quote Originally Posted by boops boops View Post
    Code:
     
            ' Draw everything
            g.Clear(Me.BackColor)
    This is superfluous and probably reduces performance. The form client area has already been cleared to the Backcolor in MyBase.OnPaintBackground. There is no need to do it again in the OnPaint sub or in the Paint event handler. It forces the entire client area to be repainted instead of just the lines to be drawn.

    Code:
    Public Sub New()
        Me.InitializeComponent()
    
        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or
                   ControlStyles.UserPaint Or
                   ControlStyles.Opaque Or
                   ControlStyles.OptimizedDoubleBuffer, True)
    End Sub
    This is obsolete and possibly superfluous. The SetStyles statements have been replaced by the DoubleBuffered property since VB2005. You can set it in the Designer or for example in the Load event sub. Besides, it doesn't affect the speed of painting but prevents flickering if the image changes rapidly.
    Me.SetStyle isn't entirely obsolete. Setting DoubleBuffered doesn't also set Opaque, for example, and that's the important part of that. I just wanted to set the rest of the styles through SetStyle because it's cleaner (IMO). So the Opaque was the important part of that.

    Since the OP used Color.Black as an example and changed to Me.BackColor, anything is possible

    Quote Originally Posted by Sitten Spynne View Post
    Wow, that first example looks zeta familiar, minitech. I wonder why
    I wonder, too. Maybe it's because it's a rewrite of the code above?

  10. #10
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: What happened to simple speed?

    Minitech, have you tried that combination of SetStyles? On my system setting ControlStyle.Opaque possibly gives a slight performance gain, but it clashes with DoubleBuffered (black background only). I did a grid-drawing test with the following code using a maximized form (1680*1080 screen) on my rather average twin-core PC.
    Code:
    	Private Sub Form1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    		Dim sw As Stopwatch = Stopwatch.StartNew
    		For x As Integer = 0 To Me.ClientSize.Width Step 30
    			e.Graphics.DrawLine(Pens.Black, x, 0, x, Me.ClientSize.Height)
    		Next
    		For y As Integer = 0 To Me.ClientSize.Height Step 15
    			e.Graphics.DrawLine(Pens.Black, 0, y, Me.ClientSize.Width, y)
    		Next
    		Console.WriteLine(sw.ElapsedMilliseconds.ToString)
    	End Sub
    The first run of a piece of code always produces deviant timing (maybe it's something to do with visual studio, or with JIT compiling). So I added a button to the form with Me.Refresh in its click event, in order to do repeated tests. The successive timings without ControlStyle.Opaque were:
    62, 26, 25, 26 milliseconds.
    With ControStyle.Opaque the result appeared to be very slightly quicker:
    60, 25, 25, 25 milliseconds.

    I also tried playing with the graphics smoothingmode, which in theory should affect line drawing. Adding e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality to the above code gave the following successive timings:
    71, 38, 37, 37 milliseconds.
    Paradoxically, setting the SmoothingMode to HighSpeed gave slightly slower times than the default!
    70, 27, 29, 27
    HighSpeed is supposed to be default anyway, so I wonder if just setting the SmoothingMode consumes a couple of milliseconds.

    The upshot is that I think the above code (excluding the stopwatch lines) is as fast as you are going to get in GDI+. I didn't test drawing the lines with GraphicsPath.Addline followed by Graphics.DrawPath, but I don't think that should make any difference. All this is a quicker than the OP's original 150 ms., but of course it depends on the speed of the hardware and above all on the size of the form.

    So cheesebrother, how big is your form client area (in pixels)? And did you test the timing just once or a number of times in succession?

    BB

  11. #11
    Stack Overflow mod​erator
    Join Date
    May 2008
    Location
    British Columbia, Canada
    Posts
    2,824

    Re: What happened to simple speed?

    Aren't those timings faster?

  12. #12
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: What happened to simple speed?

    Quote Originally Posted by minitech View Post
    Aren't those timings faster?
    Yes, that's what i said -- especially if you discount the first measurement. But I think cheesebrother needs to do a similar test on his own machine and with the same sized form to see how much it actually helps. It would also be interesting to compare the results with VB6 again; it does seem to have a distinct speed advantage over dotnet/GDI+ for drawing a simple grid. BB

  13. #13
    PowerPoster i00's Avatar
    Join Date
    Mar 2002
    Location
    1/2 way accross the galaxy.. and then some
    Posts
    2,388

    Re: What happened to simple speed?

    Without reading the other posts ... i would do it this way:

    vb Code:
    1. Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    2.         Using b As New Bitmap(30, 15) 'Create a bitmap for 1 cell of the grid
    3.             Using g = Graphics.FromImage(b) 'Create graphics so we can draw on our bitmap
    4.                 g.DrawLine(Pens.Black, 0, 0, b.Width, 0) 'Draw the x Line on it
    5.                 g.DrawLine(Pens.Black, 0, 0, 0, b.Height) 'Draw the y Line on it
    6.             End Using
    7.             Using tb As New TextureBrush(b) 'Create a texture brush to fill our form bg from the cell that we drew on b
    8.                 e.Graphics.FillRectangle(tb, Me.ClientRectangle) 'Fill the form with this brush
    9.             End Using
    10.         End Using
    11.     End Sub

    EDIT: Also on another note it may be even faster if you cache the texture brush:

    vb Code:
    1. Private Sub Form1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
    2.         Static tb As TextureBrush 'Create a texture brush to fill our form bg from - note this is static so we do not need to create it each time...
    3.         If tb Is Nothing Then 'If we haven't created the texture brush yet...
    4.             Using b As New Bitmap(30, 15) 'Create a bitmap for 1 cell of the grid
    5.                 Using g = Graphics.FromImage(b) 'Create graphics so we can draw on our bitmap
    6.                     g.DrawLine(Pens.Black, 0, 0, b.Width, 0) 'Draw the x Line on it
    7.                     g.DrawLine(Pens.Black, 0, 0, 0, b.Height) 'Draw the y Line on it
    8.                 End Using
    9.                 tb = New TextureBrush(b) 'Create the texturebrush from b
    10.             End Using
    11.         End If
    12.         e.Graphics.FillRectangle(tb, Me.ClientRectangle) 'Fill the form with this brush
    13.     End Sub

    Kris

  14. #14

    Thread Starter
    Addicted Member cheesebrother's Avatar
    Join Date
    Jul 2011
    Posts
    153

    Re: What happened to simple speed?

    Without reading the other posts ... i would do it this way
    Unfortunately the cells need to be different sizes.

    Ideally I'd like to be able to draw on an area that's 1000*800 pixels big.

    It would also be interesting to compare the results with VB6 again; it does seem to have a distinct speed advantage over dotnet/GDI+ for drawing a simple grid.
    Yes it does, I'm surprised because .net uses gdi. Perhaps if i need to i could switch to directx for drawing these lines . Bit of an overkill on speed but it would certainly do the job.

    Don't get me wrong, i won't go to dx unless i absolutely have to. I'm not a speed thirsty - unreasonable person. To see why this isn't fast enough for me try this code:

    Code:
    Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
            ' Get a copy of the graphics object for convenience
            Dim g As Graphics = e.Graphics
    
            ' Draw everything
            g.Clear(Me.BackColor)
    
            Dim SelectBounds As Point = New Point(Cursor.Position.X - Me.Left - 15, Cursor.Position.Y - Me.Top - 45)
    
            For x As Integer = 0 To Me.Width Step 30
                For y As Integer = 0 To Me.Height Step 15
                    If x > SelectBounds.X Or y > SelectBounds.Y Then
                        g.DrawRectangle(Pens.Black, x, y, 30, 15)
                    End If
                Next
            Next
    
            For x As Integer = 0 To SelectBounds.X Step 30
                For y As Integer = 0 To SelectBounds.Y Step 15
                    g.DrawRectangle(Pens.Orange, x, y, 30, 15)
                Next
            Next
        End Sub
    
        Private Sub Form1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
            Static PX, PY
    
            If Int(e.X / 30) <> PX Or Int(e.Y / 15) <> PY Then Me.Invalidate()
    
            PX = Int(e.X / 30) : PY = Int(e.Y / 15)
        End Sub
    I took out the timing code, one doesn't need to even think about speed. All the evidence for speed is right before you - flash, flash, flash, flash, flash... and all that on a form whose size is 300*300!
    Hooked for good.

  15. #15
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: What happened to simple speed?

    Hi cheesbro, I just tried your code without looking at it too much. After a few minor corrections (declare PX and PY as Integer, use Cint or integer division instead of Int -- probably required by Option Strict) it ran OK except for flickering. The flickering was very strong when I maximized the form. But then I set the form's DoubleBuffered property to true. The flickering was immediately cured.

    You can set DoubleBuffered in the designer or e.g. in the Load event.

    BB

  16. #16

    Thread Starter
    Addicted Member cheesebrother's Avatar
    Join Date
    Jul 2011
    Posts
    153

    Re: What happened to simple speed?

    Wow, sorry for being stupid everyone. (that i mean by not trying double buffering when you talked about it) Double buffering fixes everything! Now it not only doesn't flicker but it draws way faster. This is totally awesome! I just plugged the new code into my other project (my control) and it's drawing about 10 times as fast as it was before! I am really set to go now.
    Hooked for good.

  17. #17
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    3,201

    Re: [RESOLVED] What happened to simple speed?

    Well it turns out that all the alternative techniques suggested in this thread, including my own suggestions, don't make a significant difference. Drawing rectangles, drawing lines, using a graphics path or using a texture brush are all good alternatives and worth knowing about. But in the end the time to execute a Refresh depends almost entirely on the number of pixels whose colour has to be changed. Even re-clearing the background, while unnecessary, doesn't make much difference to performance because the pixels are the same. On top of that, timing code for performance remains a tricky business in DotNet and can easily lead to wrong conclusions.

    BB

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width