{"id":263,"date":"2008-08-28T22:03:17","date_gmt":"2008-08-28T10:03:17","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=263"},"modified":"2009-08-07T14:56:53","modified_gmt":"2009-08-07T02:56:53","slug":"exchange-epilogue-and-smoketest","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/263\/","title":{"rendered":"Exchange() &#8211; Epilogue and Smoketest"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">5<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p>A colleague of mine directed me to a further minor refinement of the &#8216;final&#8217; <strong>Exchange()<\/strong> code I posted the other day.  The change is minor but yields a worthwhile performance improvement, but my main reason for bothering to post (yet!) another update is an excuse to introduce the testing framework I developed that allowed me to quickly assess any benefit.<\/p>\n<p><!--more--><\/p>\n<h3>First The Epilogue<\/h3>\n<p>The suggestion made was to replace the triplet of <strong>CopyMemory()<\/strong> calls with a byte-wise copy routine, so this code replaces that in the <strong>case else<\/strong> :<\/p>\n<pre class=\"delphi\">      ap8 := PByte(@A);\r\n      bp8 := PByte(@B);\r\n      i32 := aSize;\r\n      while (i32 &gt; 0) do\r\n      begin\r\n        i8   := ap8^;\r\n        ap8^ := bp8^;\r\n        bp8^ := i8;\r\n        Inc(ap8);\r\n        Inc(bp8);\r\n        Dec(i32);\r\n      end;<\/pre>\n<p>And the declaration of the <strong>p: Pointer<\/strong> variable is replaced by two variables:<\/p>\n<pre class=\"delphi\">  ap8, bp8: PByte;<\/pre>\n<p>I suspect that for extremely large amounts of data <strong>CopyMemory()<\/strong> is likely to prove faster but I think the likelihood that this routine will see extensive use in such cases is remote enough not to worry.<\/p>\n<h3>Assessing The Impact<\/h3>\n<p>To see what difference the code change had I ran some performance test cases, installed the suggested replacement code and then re-ran the tests. The results of the original code, using <strong>CopyMemory()<\/strong> came out as follows:<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" loading=\"lazy\" class=\"size-medium wp-image-265 aligncenter\" title=\"Exchange() Performance Before\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/exchange_before.gif?resize=300%2C169&#038;ssl=1\" alt=\"\" width=\"300\" height=\"169\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/exchange_before.gif?resize=300%2C169&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/exchange_before.gif?w=459&amp;ssl=1 459w\" sizes=\"(max-width: 300px) 100vw, 300px\" data-recalc-dims=\"1\" \/><\/p>\n<p>Which also demonstrates the significant performance advantage that those local variable special cases provide.\u00a0 Replacing the <strong>CopyMemory()<\/strong> code with the byte-wise copy obviously had no impact on the fixed-size test cases, but led to a doubling of efficiency in the case of the record data type (12 bytes in size in the test case):<\/p>\n<p style=\"text-align: center;\"><img decoding=\"async\" loading=\"lazy\" class=\"size-medium wp-image-266 aligncenter\" title=\"Exchange() Performance - Byte-wise Copy\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/exchange_after.gif?resize=295%2C300&#038;ssl=1\" alt=\"\" width=\"295\" height=\"300\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/exchange_after.gif?resize=295%2C300&amp;ssl=1 295w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/exchange_after.gif?w=440&amp;ssl=1 440w\" sizes=\"(max-width: 295px) 100vw, 295px\" data-recalc-dims=\"1\" \/><\/p>\n<p>The slight variation in results in cases 3.1 thru 3.5 were typical variances seen from one run of tests to another.\u00a0 The doubling of the result for the 3.6 case though showed that the improvement was real.\u00a0 So this is the version that will now take up residence in my library.<\/p>\n<p>In addition to the performance test results, this second image also shows the output confirming that the changes had not broken the implementation in in the case of a large record type (<em>2.6 Exchange_Record<\/em>).<\/p>\n<p>I should mention that the routine in it&#8217;s final state I think now very closely resembles a Pascal version of the ASM routine that PhiS kindly posted in the comments.<\/p>\n<p>But as Joe mentioned, also in the comments, this routine, whilst useful, is hardly something around which entire systems are built, and for the sorts of uses to which I typically put it I have now spent enough time trying to eek CPU cycles out of it.<\/p>\n<p>So instead I thought I&#8217;d quickly introduce SmokeTest &#8211; my own testing framework.<\/p>\n<h3>Oh No, Not Another Testing Framework ?<\/h3>\n<p>Yes, I&#8217;m afraid so.<\/p>\n<p>What was wrong with <a href=\"http:\/\/dunit.sourceforge.net\/\" target=\"_blank\"><strong>DUnit<\/strong><\/a>, for example?\u00a0 Nothing, probably.\u00a0 Except that the last time I looked at it it all looked a little too complicated to get going for something that should have been &#8211; or should at least have <em>seemed<\/em> &#8211; a lot more straightforward than it appeared it was.<\/p>\n<p>Implementing my own framework was also going to provide me with a reason to work on some other areas of interest &#8211; Threading and RTTI &#8211; and I could immediately see how my multi-cast events were going to come into the picture.<\/p>\n<p>As it turned out, even the simple GUI was to be the source of some inspiration.<\/p>\n<h3>Skip To The End<\/h3>\n<p>I said that getting started with <strong>DUnit<\/strong> seemed to complicated to me, so after all my effort how did my results compare?\u00a0 This is jumping ahead quite a bit, to a finished testing framework but it shows the level of simple, intuitive code that I expected of a testing framework.\u00a0 It was my goal, as well as my end result.<\/p>\n<p>Here&#8217;s the source for my <strong>Exchange()<\/strong> test project:<\/p>\n<pre class=\"delphi\">program Smoketest.Exchange;\r\n\r\nuses\r\n  Deltics.SmokeTest,\r\n  Test.Exchange;\r\n\r\nbegin\r\n  TestSuite.Initialize;\r\n  TExchangeTest.Create;\r\n  TExchangePerformance.Create(2, pmSeconds);\r\n  TestSuite.Ready;\r\nend.<\/pre>\n<p>Nothing too controversial here.  We initialize the <em>TestSuite<\/em> (there are parameters we can use to tailor a particular suite, but in this case I&#8217;m using defaults).<\/p>\n<p>One big difference though is that I then create my test cases directly, no need to create suites to contain them.\u00a0 The framework will take care of that for me.<\/p>\n<p>The first real and significant difference is the availability to me of performance tests, as well as regular tests.\u00a0 These tests do require constructor parameters where I provide some number <em><strong>N<\/strong><\/em> and indicate how that <em><strong>N<\/strong><\/em> is to be treated, allowing me to specify whether the methods in that performance test case should run for <em><strong>N<\/strong> <\/em>seconds or for <em><strong>N<\/strong><\/em> iterations.<\/p>\n<p>The call to <strong>TestSuite.Ready<\/strong> summons my GUI console from where I may then run the tests and view results etc.<\/p>\n<h3>The Test Cases<\/h3>\n<p>So much for the project.\u00a0 What about the test cases themselves?<\/p>\n<pre class=\"delphi\">  unit Test.Exchange;\r\n\r\n interface\r\n\r\n  uses\r\n    Deltics.SmokeTest;\r\n\r\n  type\r\n    TExchangeTest = class(TTestCase)\r\n      procedure Exchange_Byte;\r\n      procedure Exchange_Int64;\r\n      procedure Exchange_Integer;\r\n      procedure Exchange_Word;\r\n      procedure Exchange_Extended;\r\n      procedure Exchange_Record;\r\n    end;\r\n\r\n    TExchangePerformance = class(TPerformanceCase)\r\n      procedure Exchange_Byte;\r\n      procedure Exchange_Int64;\r\n      procedure Exchange_Integer;\r\n      procedure Exchange_Word;\r\n      procedure Exchange_Extended;\r\n      procedure Exchange_Record;\r\n    end;<\/pre>\n<p>Again, nothing much to discuss here, except that as with the project itself there is nothing extraneous.  There are test cases and test methods and that&#8217;s all.  No housekeeping, no infrastructure.  In short, no clutter.<\/p>\n<p>As you might expect, the implementation of each of the test case methods are very similar to each other, so just to give an idea here are one test case method and one performance case.  First a test case method:<\/p>\n<pre class=\"delphi\">  procedure TExchangeTest.Exchange_Integer;\r\n  var\r\n    iA, iB: Integer;\r\n  begin\r\n    iA := INT_A;\r\n    iB := INT_B;\r\n\r\n    Exchange(iA, iB, sizeof(iA));\r\n\r\n    Test['iA'].Expect(iA, vrEqual, INT_B);\r\n    Test['iB'].Expect(iB, vrEqual, INT_A);\r\n  end;<\/pre>\n<p>A pretty predictable test.  First I place some recognisable values in two integer variables (INT_A and INT_B are unit constants).  I then call Exchange(), the method under test, and finally test for the expected results.\u00a0 The details of testing results can wait for another time.<\/p>\n<p>A performance case is even simpler since there is no test framework code involved, just code whose performance I wish to test:<\/p>\n<pre class=\"delphi\">  procedure TExchangePerformance.Exchange_Integer;\r\n  var\r\n    iA, iB: Integer;\r\n  begin\r\n    Exchange(iA, iB, sizeof(iA));\r\n  end;<\/pre>\n<p>Notice that I don&#8217;t even initialise the variables in this case let alone test the results.  My <strong>TExchangeTest<\/strong> is designed to test correctness. <strong>TExchangePerformance<\/strong> is exercising code to determine it&#8217;s efficiency so there needs to be a minimum of &#8220;fuss&#8221; to muddy the results.<\/p>\n<p>There&#8217;s also no timing or measurement code &#8211; that&#8217;s all taken care of by the framework.<\/p>\n<h3>Where&#8217;s This All Going?<\/h3>\n<p>As I said previously, implementing my own testing framework gave me a platform on which to explore a number of areas I&#8217;d been mulling over, and talking about the testing framework will lead us into those areas.<\/p>\n<p>Even if the testing framework isn&#8217;t directly of interest to you, either because you already use another framework or just don&#8217;t do such tests, the areas that it takes us into may be.<\/p>\n<p style=\"padding-left: 30px;\"><strong>Threading<\/strong> &#8211; The test suite runs in it&#8217;s own thread, separate from the GUI.  Rather than use the VCL <strong>TThread<\/strong>, I created for myself a wholly new encapsulation of threading.\u00a0 The threading implementation uses states and multicast notifications.\u00a0 In this implementation a simple thread is restartable without involving a thread pool.\u00a0 This is not the <strong>TMotile<\/strong> that I have previously mentioned &#8211; that evolved some time later.<\/p>\n<p style=\"padding-left: 30px;\"><strong>Published Methods<\/strong> &#8211; Extracting RTTI from classes is straightforward, but can be a bit messy especially in the case of published methods.<\/p>\n<p style=\"padding-left: 30px;\"><strong>GUI Consistency<\/strong> &#8211; Not as powerful as skinning, but ensuring a consistent look across a GUI and enabling that look to be changed easily.\u00a0 It involves multicast events (again), and devises a way to develop VCL controls that are able to synchronize cosmetic properties with each other globally or in groups.<\/p>\n<p><strong><\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">5<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> A colleague of mine directed me to a further minor refinement of the &#8216;final&#8217; Exchange() code I posted the other day. The change is minor but yields a worthwhile performance improvement, but my main reason for bothering to post (yet!) another update is an excuse to introduce the testing framework I developed that allowed me [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":[]},"categories":[4,48],"tags":[292,50,51,295,49],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-4f","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":254,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/254\/","url_meta":{"origin":263,"position":0},"title":"An Exchange() For All","date":"26 Aug 2008","format":false,"excerpt":"Following on from yesterday's post, Barry Kelly (CodeGear engineer) kindly clarified a few points, one of which was that Generics support in Delphi 2009 won't extent to unit procedures, only class methods, so speculation about a possible generic implementation of a Swap()\/Exchange() routine was rendered largely academic. Not to be\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2344,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2344\/","url_meta":{"origin":263,"position":1},"title":"A Deeper Dive into Range Checking","date":"08 Jun 2015","format":false,"excerpt":"Yesterday I posted about an issue with type checking in Delphi (and other Pascal) compilers. As mentioned in that post, range checking is fundamentally flawed as a supposed solution to the problem for reasons that are explored further in this post. To recap: Range checking does not test the types\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":375,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/375\/","url_meta":{"origin":263,"position":2},"title":"Delphi 2009 &#8211; StringPerformance Redux","date":"22 Sep 2008","format":false,"excerpt":"It looks like I may have jumped the gun with my conclusions from the previous exercise to benchmark string performance in Delphi 2009.\u00a0 Following a useful exchange in the comments with Kryvich I corrected a small discrepancy in the tests and made some changes to the performance testing subsystem within\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/delphi2009-stringperformance-chart.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":284,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/284\/","url_meta":{"origin":263,"position":3},"title":"This Week&#8217;s Poll, Delphi at Tech Ed and 2009 Ship Date","date":"01 Sep 2008","format":false,"excerpt":"Since I'm preparing a series of posts about (and eventual publication of) Smoketest, my own testing framework, I thought I'd test the water with this weeks poll and see what - if anything - people are already using as far as unit testing goes. Also I thought I'd briefly mention\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2185,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2185\/","url_meta":{"origin":263,"position":4},"title":"Smoketest &#8211; Performance Case Visualisations","date":"22 Nov 2013","format":false,"excerpt":"This post is a peek behind the curtain of the next major update to Smoketest which I hope to have completed shortly: Performance Case visualisations. Smoketest has always had two types of test case that you could implement by deriving from two distinct base classes: TTestCase is the base class\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-22-at-09.57.52.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2956,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2956\/","url_meta":{"origin":263,"position":5},"title":"Introducing: Smoketest 2.0","date":"24 Sep 2019","format":false,"excerpt":"Meet the new Smoketest. Nothing like the old Smoketest. A new, ultra-lightweight unit testing framework with xUnit 2.x result output (and the ability to support other output formats as needed).","rel":"","context":"In &quot;automation&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/vape_2.jpg?fit=1200%2C307&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/263"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=263"}],"version-history":[{"count":8,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/263\/revisions"}],"predecessor-version":[{"id":473,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/263\/revisions\/473"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=263"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=263"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=263"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}