Tk Source Code

Artifact [404baea4]
Login

Artifact 404baea4578f3a7e0dca97211c84770a9c4358e2:

Attachment "2874226 - -joinstyle - performance checks.tcl" to ticket [2874226f] added by fvogel 2017-08-19 16:03:39.
# This is an analysis of the performance impact of the fix for ticket
#    2874226 - polygon don't honor -joinstyle on Windows and OS X
# Linux: no bug, no fix
# Windows: fix exists: [c441e73f]
# OS X: fix exists: [abd26a6e]
#
# This performacne impact analysis deals with Windows only.
# core-8-6-branch and the bugfix branch are compiled in release mode with no special option


####################################################
####################################################

With reference [c441e73f] (aka the "bugfix"), a candidate "optimized bugfix" for Windows only (no such need for OS X) could be:


C:\Users\francois\Documents\Development\tcltk-fossil\tk>f diff
Index: win/tkWinDraw.c
==================================================================
--- win/tkWinDraw.c
+++ win/tkWinDraw.c
@@ -739,15 +739,17 @@
 }
 ?
 /*
  *----------------------------------------------------------------------
  *
- * MakeAndStrokePath --
+ * DrawPoly --
  *
  *     This function draws a shape using a list of points, a stipple pattern,
- *     and the specified drawing function. It does it through creation of a
- *     so-called 'path' (see GDI documentation on MSDN).
+ *     and the specified drawing function. Closed polylines are drawn through
+ *     creation of a so-called 'path' (see GDI documentation on MSDN).
+ *     Open polylines and polygons are drawn a tiny bit quicker, without
+ *     resorting to a path.
  *
  * Results:
  *     None.
  *
  * Side effects:
@@ -754,36 +756,40 @@
  *     None.
  *
  *----------------------------------------------------------------------
  */
 static void
-MakeAndStrokePath(
+DrawPoly(
     HDC dc,
     POINT *winPoints,
     int npoints,
     WinDrawFunc func)        /* Name of the Windows GDI drawing function:
                                 this is either Polyline or Polygon. */
 {
-    BeginPath(dc);
-    func(dc, winPoints, npoints);
     /*
-     * In the case of closed polylines, the first and last points
-     * are the same. We want miter or bevel join be rendered also
-     * at this point, this needs telling the Windows GDI that the
-     * path is closed.
-     */
+    * In case of a closed polyline, where the first and last points are
+    * the same, we want miter or bevel join be rendered also at this point.
+    * To get this result, the Windows GDI needs to be told explicitely
+    * that the polyline is closed. This needs creation of a 'path'.
+    */
     if (func == Polyline) {
         if ((winPoints[0].x == winPoints[npoints-1].x) &&
                 (winPoints[0].y == winPoints[npoints-1].y)) {
+            BeginPath(dc);
+            func(dc, winPoints, npoints);
             CloseFigure(dc);
+            EndPath(dc);
+            StrokePath(dc);
+            return;
         }
-        EndPath(dc);
-        StrokePath(dc);
-    } else {
-        EndPath(dc);
-        StrokeAndFillPath(dc);
     }
+
+    /*
+     * In all other cases, a path is not needed (and is not used to avoid
+     * any performance loss, be it very small).
+     */
+    func(dc, winPoints, npoints);
 }
 ?
 /*
  *----------------------------------------------------------------------
  *
@@ -877,11 +883,11 @@
         */

        SetPolyFillMode(dcMem, (gc->fill_rule == EvenOddRule) ? ALTERNATE
                : WINDING);
        oldMemBrush = SelectObject(dcMem, CreateSolidBrush(gc->foreground));
-        MakeAndStrokePath(dcMem, winPoints, npoints, func);
+        DrawPoly(dcMem, winPoints, npoints, func);
        BitBlt(dc, rect.left, rect.top, width, height, dcMem, 0, 0, COPYFG);

        /*
         * If we are rendering an opaque stipple, then draw the polygon in the
         * background color and copy it to the destination wherever the
@@ -889,11 +895,11 @@
         */

        if (gc->fill_style == FillOpaqueStippled) {
            DeleteObject(SelectObject(dcMem,
                    CreateSolidBrush(gc->background)));
-            MakeAndStrokePath(dcMem, winPoints, npoints, func);
+            DrawPoly(dcMem, winPoints, npoints, func);
            BitBlt(dc, rect.left, rect.top, width, height, dcMem, 0, 0,
                    COPYBG);
        }

        SelectObject(dcMem, oldPen);
@@ -905,11 +911,11 @@
        oldBrush = SelectObject(dc, CreateSolidBrush(gc->foreground));
        SetROP2(dc, tkpWinRopModes[gc->function]);

        SetPolyFillMode(dc, (gc->fill_rule == EvenOddRule) ? ALTERNATE
                : WINDING);
-        MakeAndStrokePath(dc, winPoints, npoints, func);
+        DrawPoly(dc, winPoints, npoints, func);
        SelectObject(dc, oldPen);
     }
     DeleteObject(SelectObject(dc, oldBrush));
 }
 ?


####################################################
####################################################


####################################################
# performance non-regression testing: polygon

package require Tk
pack [canvas .c]

proc gencoords {n {maxx 400} {maxy 250}} {
    set res {}
    for {set i 1} {$i <= $n} {incr i} {
        set x [expr {round(rand() * $maxx)}]
        set y [expr {round(rand() * $maxy)}]
        lappend res $x $y
    }
    return $res
}

proc drawit {repeattest nbvertices} {
    for {set i 1} {$i <= $repeattest} {incr i} {
        set id [.c create polygon [gencoords $nbvertices]]
        .c itemconfigure $id -fill red -outline black -width 10 -joinstyle miter
        update
        .c delete $id
    }
}

proc timeit {repeatdraw nbvertices {nb 3}} {
    set tot 0
    for {set i 1} {$i <= $nb} {incr i} {
        incr tot [scan [time {drawit $repeatdraw $nbvertices}] %d]
    }
    puts "Mean time on $nb runs: [expr {$tot / $nb}] �s"
}

timeit 500 300 3
timeit 2000 30 3
timeit 5000 50 10

# results (in �s):       core-8-6-branch    bugfix branch    optimized bugfix    perf loss      perf loss       perf loss
#                          [6dc1b349]         [c441e73f]       [... ? ...]        86->bugfix     bugfix->opti    86->optim
# timeit 500 300 3  		5203843			5034738		5069571			-3,2%		0,7%		-2,6%
# timeit 2000 30 3		3726699			3433176		3432480			-7,9%		0,0%		-7,9%
# timeit 5000 50 10		10278187		9929803		10008726		-3,4%		0,8%		-2,6%

Therefore:
    Using paths is more performant than not using them.
    The optimized fix in fact does not optimize because it does not use a path for non-closed polylines.

BUT:
    All these figures are in the uncertainty margin. When running the tests later again figures were different. Depends on the computer use I guess.
    What's important is that the figures should be comparable since tests were run next to each other in a short timeframe, thus no performance loss.


####################################################
# performance non-regression testing: (poly)line

package require Tk
pack [canvas .c]

proc gencoords {n closeline {maxx 400} {maxy 250}} {
    set res {}
    for {set i 1} {$i <= $n} {incr i} {
        set x [expr {round(rand() * $maxx)}]
        set y [expr {round(rand() * $maxy)}]
        lappend res $x $y
    }
    if {$closeline} {
        # close the polyline
        lappend res [lindex $res 0]
        lappend res [lindex $res 1]
    }
    return $res
}

proc drawit {repeatdraw nbvertices closeline} {
    for {set i 1} {$i <= $repeatdraw} {incr i} {
        set id [.c create line [gencoords $nbvertices $closeline]]
        .c itemconfigure $id -fill red -width 10 -joinstyle miter
        update
        .c delete $id
    }
}

proc timeit {repeatdraw nbvertices {nb 3} {closeline true}} {
    set tot 0
    for {set i 1} {$i <= $nb} {incr i} {
        incr tot [scan [time {drawit $repeatdraw $nbvertices $closeline}] %d]
    }
    puts "Mean time on $nb runs: [expr {$tot / $nb}] �s"
}

timeit 500 300 3
timeit 500 300 3 0
timeit 2000 30 3
timeit 2000 30 3 0
timeit 5000 50 10
timeit 5000 50 10 0

# results (in �s):       core-8-6-branch    bugfix branch    optimized bugfix    perf loss      perf loss       perf loss
#                          [6dc1b349]         [c441e73f]       [... ? ...]        86->bugfix     bugfix->opti    86->optim
# timeit 500 300 3		4516433			4486570		4495222			-0,7%		0,2%		-0,5%
# timeit 500 300 3 0		4495286			4436930		4424865			-1,3%		-0,3%		-1,6%
# timeit 2000 30 3		3237309			3194199		3180000			-1,3%		-0,4%		-1,8%
# timeit 2000 30 3 0		3163584			3161066		3133370			-0,1%		-0,9%		-1,0%
# timeit 5000 50 10		9113923			9029612		9047344			-0,9%		0,2%		-0,7%
# timeit 5000 50 10 0		9020690			9005947		8934129			-0,2%		-0,8%		-1,0%

Therefore:
    Using paths is generally more performant than not using them.
    Slight additional performance gain with the optimized fix when the polyline is not closed.
    For closed polylines the optimized fix is in fact a very small loss of performance.

BUT:
    All these figures are in the uncertainty margin. When running the tests later again figures were different. Depends on the computer use I guess.
    What's important is that the figures should be comparable since tests were run next to each other in a short timeframe, thus no performance loss.


####################################################
GENERAL CONCLUSION:

    All in all the optimized fix is worse than non-optimized fix for polygons and non-closed polylines.
    Paths are more performant than not using them.
    The optimized fix does provide a small performance increase for non-closed polylines, but this is more than balanced by the performance loss with polygons and closed polylines, especially since the measured performance increase is marginal compared to the uncertainty of the measurement.

   --> 1. The fix does not create any loss of performance. It rather improves performance slightly.
       2. The optimized fix is rejected, and the bugfix [c441e73f] is kept as is.