{"id":576,"date":"2009-10-06T17:24:57","date_gmt":"2009-10-06T05:24:57","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=576"},"modified":"2009-10-06T17:24:57","modified_gmt":"2009-10-06T05:24:57","slug":"commitment-issues","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/576\/","title":{"rendered":"Commitment Issues"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p>No, not a relationship blog and no, not a rant about the relationship between Embarcadero and the Delphi community. \u00a0This is a strictly and purely technical post about what &#8220;Committed&#8221; means in terms of Windows memory, and in particular a key aspect of how that applies to threaded applications.<\/p>\n<p><!--more--><\/p>\n<p>Last week a user of the software that I work on in my 9-to-5 role reported an issue with the system that they had been experiencing for a while. \u00a0A work-around had been devised but this was not particularly satisfying and it was felt that a more permanent solution should be within reach.<\/p>\n<p>The issue involved an operation in the software that was called on to process a number of records in a file. \u00a0For reasons peculiar to the particular user, in this case the process first involved separating each record in the file into a <em>number<\/em> of files, each containing just one record, and then processing each file in turn.<\/p>\n<p><em>I should say at this point that these choices in processing are made by the users of the software, not within the software itself, which has to handle whatever processing choices any given user might determine as most appropriate to their particular needs.<\/em><\/p>\n<p>If the file contained fewer than around 1500 records, everything was fine. \u00a0But once the number of records approached or exceeded 1500, this particular process in the system would halt, reporting &#8211; via logs recorded in Windows&#8217; Event Log &#8211; &#8220;insufficient storage&#8221;.<\/p>\n<p>A critical aspect of the implementation is that each file processed by the system is allocated a thread on which to perform the processing.<\/p>\n<p>1 file = 1 thread. \u00a01500 files = 1500 threads.<\/p>\n<p>That already looks like a worryingly high number, and when you take into account a further crucial factor, the reason for the failure becomes immediately obvious.<\/p>\n<h2>Make Way For The Stack<\/h2>\n<p>Every thread is, well, a thread of execution. \u00a0For that each thread requires a number of things, the important one for the purposes of this discussion is that it needs a stack.<\/p>\n<p>It just so happens that in Delphi code, the default maximum stack size is 1MB for an application. \u00a0Any thread created by that application process will inherit it&#8217;s own maximum stack size from the process unless otherwise specified (see &#8220;Shameless Plug&#8221; footnote, below).<\/p>\n<p>1500 files = 1500 threads = 1500MB of stack space! \u00a0(in very round numbers)<\/p>\n<p>Given that a 32-bit Windows process has only 2,000MB to play with, it&#8217;s little wonder that that number of threads in a process responsible for other processing and placing other demands on memory, should cause it to run out of storage.<\/p>\n<p>Except for one thing.<\/p>\n<p>If you monitor the process in Task Manager you will see that it&#8217;s memory usage never climbs above approx 200MB!<\/p>\n<p>What&#8217;s going on?!<\/p>\n<p>Commitment. \u00a0That&#8217;s what.<\/p>\n<h2>Old New Things<\/h2>\n<p>It just so happens that I visit <a href=\"http:\/\/blogs.msdn.com\/oldnewthing\/archive\/2009\/10\/02\/9902146.aspx\">Raymond Chen&#8217;s &#8220;Old New Thing&#8221;<\/a> blog pretty much every day. \u00a0I know that he writes a lot of his articles months in advance and they are auto-posted on a daily basis. \u00a0So it&#8217;s a phenomenal coincidence that the subject of his post that I just linked to, should happen to have come up in the past few days, precisely to answer that &#8220;What&#8217;s going on?!&#8221; question.<\/p>\n<p>I was confident that the thread count and the thread stacks were responsible for chewing up available memory, but the apparent reported memory usage just didn&#8217;t seem to support this. \u00a0Why not?<\/p>\n<p>The answer is, that each thread assuredly <em>does<\/em> consume a minimum of 1MB of memory, reserved for it&#8217;s stack. \u00a0But if that thread only consumes a 16KB stack, then 16KB is all the memory that it will <em>use<\/em>.<\/p>\n<p>But the 1MB is <em>reserved<\/em> regardless. \u00a0Just in case.<\/p>\n<p>This is that thread&#8217;s share of the &#8220;Commit Charge&#8221; you see referenced in Task Manager.<\/p>\n<p>The Commit Charge is the amount of memory that Windows is currently promising to make available, should the need arise. \u00a0That is, if every thread in every process were to suddenly require the actual use of all the memory that those threads and processes had indicated they might need, the &#8220;Commit Charge&#8221; is how much memory the system would be called upon to simultaneously provide. \u00a0That &#8220;provision&#8221; may be met from the page file as well as physical memory of course, which is why the Commit Charge can (and quite often does) exceed the physical RAM installed in your machine.<\/p>\n<p>So basically what happens when you create a thread is that Windows is asked to reserve 1MB of memory for that thread&#8217;s stack. \u00a0If the thread never starts executing then it will use very little of that stack &#8211; if any. \u00a0But Windows maintains it&#8217;s promise to provide that memory, <span style=\"text-decoration: underline;\">should the need arise<\/span>.<\/p>\n<p>But as well as Windows keeping it&#8217;s promises, your process has to have realistic expectations.<\/p>\n<p>In particular, in 32-bit Windows there is no point asking Windows to commit to reserving &gt; 2GB of RAM for your process. \u00a0If you do, Windows will politely apologise but explain that this is simply not possible.<\/p>\n<p>You have &#8220;insufficient storage&#8221;.<\/p>\n<h2>Handling Committment<\/h2>\n<p>Fortunately in the case I was confronted with the solution was relatively straightforward.<\/p>\n<p>Although the system was creating 1500+ threads, each thread ended up being executed sequentially. \u00a0There was really no need to create all these threads simultaneously at all. \u00a0Instead a work list could be created and a new thread created to work it&#8217;s way through that list, creating a single thread as required to process each item in that list in turn.<\/p>\n<p>The minimum Commit Charge for this aspect of the systems processing consequently fell from 1.5GB+\u00a0to just 2MB!<\/p>\n<p>More importantly, this meant that the process no longer came to a grinding halt when presented with a huge volume of work to perform. \u00a0The revised implementation can now handle an unlimited number of records.<\/p>\n<p>So before you jump on the parallel programming bandwagon ans start throwing threads at every piece of parallelable code you can find, you might wish to consider the impact this may have on the minimum commit charge for your application.<\/p>\n<p>At the very least you should perhaps start being a bit more thorough about determining, and requesting from the OS, an appropriate stack allocation for your threads.<\/p>\n<p><a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms682453(VS.85).aspx\">http:\/\/msdn.microsoft.com\/en-us\/library\/ms682453(VS.85).aspx<\/a><\/p>\n<h2 style=\"font-size: 1.5em;\">Shameless Plug<\/h2>\n<p>Unfortunately, the TThread class in Delphi does not provide an easy means for you to set a specific stack size for your threads\u00a0(to be honest, this is just covering myself &#8211; I&#8217;m fairly sure there is no way <em>at all<\/em> to set stack size using TThread, but some enterprising soul may have found some devious mechanism).<\/p>\n<p>My alternate TThread and TMotile classes on the other hand now do.<\/p>\n<p>Coming soon to a CodePlex project near you.<\/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\">4<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> No, not a relationship blog and no, not a rant about the relationship between Embarcadero and the Delphi community. \u00a0This is a strictly and purely technical post about what &#8220;Committed&#8221; means in terms of Windows memory, and in particular a key aspect of how that applies to threaded applications.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","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],"tags":[292,16,93,17],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-9i","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":725,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/725\/","url_meta":{"origin":576,"position":0},"title":"FinalBuilder and Delphi: 64-Bit Registry Gotcha","date":"02 Aug 2011","format":false,"excerpt":"Just a short post to share something I learned the hard way today when moving a FinalBuilder installation from a 32-bit Windows server to a shiny new 64-bit Windows 2008 server on our even shinier new HyperV platform: \u00a0The 64-bit registry can catch you out. The registry on 64-bit Windows\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2337,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2337\/","url_meta":{"origin":576,"position":1},"title":"A Silent Danger&#8230;","date":"07 Jun 2015","format":false,"excerpt":"A brief post on a long standing omission in type checking in Pascal and the limitations of Range Checking as applied to the problem. Consider this contrived example of a simple function: This very simple function accepts an explicitly 32-bit Integer parameter and simply returns TRUE if the value passed\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1670,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1670\/","url_meta":{"origin":576,"position":2},"title":"First Impressions Of XE5 for Android","date":"19 Sep 2013","format":false,"excerpt":"I thought I should at least take a look at the amazing Android support in XE5 so I decided to work through the tutorial that was brought to my attention recently. The first order of business of course, is getting installed. After making a cup of tea, reading a book,\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2861,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2861\/","url_meta":{"origin":576,"position":3},"title":"Azure DevOps &#8211; Building Some Code","date":"09 Sep 2019","format":false,"excerpt":"In this post we create a (very!) simple project, build it using Delphi (7) and run it. All with Azure DevOps.","rel":"","context":"In &quot;automation&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/pipelines-hero-code-1024x256.jpg?fit=1024%2C256&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":349,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/349\/","url_meta":{"origin":576,"position":4},"title":"Delphi 2009 &#8211; String Performance","date":"18 Sep 2008","format":false,"excerpt":"NOTE: Downloads are now fixed! Andreas Hausladen generously took the time to make some detailed comments on my previous post, one of which prompted me to throw together some further performance test cases for String types specifically.\u00a0 The results were something of a mixed bag and contained some surprises. The\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-resultscapture.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":1508,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1508\/","url_meta":{"origin":576,"position":5},"title":"An Exclusive Club and Reassuringly Expensive","date":"22 Aug 2013","format":false,"excerpt":"In the comments to a previous post there cropped up the complaint that people asking for a realistic Starter Edition are just looking for a cheaper edition of Delphi for themselves. Maybe some of us are, but even so we are - or at least I am - not primarily\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/576"}],"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=576"}],"version-history":[{"count":1,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/576\/revisions"}],"predecessor-version":[{"id":577,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/576\/revisions\/577"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=576"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=576"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=576"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}