{"id":478,"date":"2009-04-02T17:36:04","date_gmt":"2009-04-02T21:36:04","guid":{"rendered":"http:\/\/benjamin.smedbergs.us\/blog\/?p=478"},"modified":"2009-04-02T17:36:50","modified_gmt":"2009-04-02T21:36:50","slug":"pymake-25-faster-than-msys-make","status":"publish","type":"post","link":"http:\/\/benjamin.smedbergs.us\/blog\/2009-04-02\/pymake-25-faster-than-msys-make\/","title":{"rendered":"pymake: 25% faster than msys make"},"content":{"rendered":"<p>pymake news:<\/p>\n<ul>\n<li>Bad news: pymake is still 5x slower than GNU make on Linux\/Mac.\n<li>Good news: pymake is 25% faster than msys make (GNU make on Windows)!\n<li>Best news: there&#8217;s a lot of room to make performance better.\n<\/ul>\n<p>All measurements are do-nothing depend builds. Full rebuilds aren&#8217;t significantly affected because compiler speed overwhelms any time we spend in make.<\/p>\n<p>Creating Windows processes is more expensive than creating processes on a unix-like operating system. Creating MSYS processes is hugely more expensive. Windows I\/O in general is slow compared to Linux, at least for typical build tasks. Because pymake recurses in a single process, caches parsed makefiles such as rules.mk, and avoids many shell invocations, it can make up for slow parsing times by dramatically reducing time spent elsewhere.<\/p>\n<h3>How to use pymake on Windows<\/h3>\n<p>Don&#8217;t use pymake with client.mk on Windows, yet. pymake doesn&#8217;t understand MSYS-style paths, which is what configure substitutes for <tt>@srcdir@<\/tt> and <tt>@topsrcdir@<\/tt> when using client.mk. This will be fixed by the patches available from <a href=\"https:\/\/bugzilla.mozilla.org\/showdependencytree.cgi?id=485412&#038;hide_resolved=0\">this bug tree<\/a>.<\/p>\n<p>Configuring manually isn&#8217;t hard: to build Firefox in <tt>c:\/builds<\/tt>, follow this recipe:<\/p>\n<pre>$ mkdir \/c\/builds\r\n$ hg clone http:\/\/hg.mozilla.org\/mozilla-central \/c\/builds\/mozilla-central\r\n$ cd \/c\/builds\/mozilla-central\r\n$ autoconf-2.13 && (cd js\/src && autoconf-2.13)\r\n$ mkdir ff-debug\r\n$ cd ff-debug\r\n$ export MAKE='python -O c:\/builds\/mozilla-central\/build\/pymake\/make.py'\r\n$ ..\/configure --enable-application=browser --enable-debug --disable-optimize\r\n$ python -O ..\/build\/pymake\/make.py -j4\r\n<\/pre>\n<h3>How to use pymake on Linux\/Mac<\/h3>\n<p>Configure manually as above, or add the following flags to your mozconfig file:<\/p>\n<pre>export MAKE=\"python -O $topsrcdir\/build\/pymake\/make.py\"\r\nmk_add_options MAKE=\"python -O @TOPSRCDIR@\/build\/pymake\/make.py\"\r\n<\/pre>\n<p>Soon on all platforms this will be as simple as <tt>mk_add_options MOZ_ENABLE_PYMAKE=1<\/tt><\/p>\n<h3>Thank you!<\/h3>\n<p>Special thanks to Arpad Borsos who wrote tests and an implementation of &#8211;keep-going for pymake.<\/p>\n<h3>Next plans<\/h3>\n<p>Immediate future plans for pymake reduce the process count even further, especially for depend builds:<\/p>\n<p>Currently every invocation of nsinstall is a separate process, and we invoke nsinstall even when all its install targets are up to date. Simple tasks like this will instead be implemented as native python commands. Ted implemented a branch to do this, but the current implementation blocks the only thread. I think we&#8217;re going to switch and use shared-nothing threads and message passing to parallelize before making this the default behavior.<\/p>\n<p>Every time Mozilla processes a makefile the build system combines all the compiler-generated dependencies into a <a href=\"http:\/\/mxr.mozilla.org\/mozilla-central\/source\/config\/rules.mk#2064\">single .all.pp file<\/a> using mddepend.pl: this allows developers to move or remove header files without breaking depend builds. Running a perl script for every makefile invocation is silly, especially because all it does is parsing and rewrite makefile syntax. I will have pymake read these dependency files directly and ignore missing files (causing a rebuild without an error) using a syntax <tt>includedeps $(INCLUDEFILES)<\/tt><\/p>\n<p>Longer-term work that would make pymake much more useful:<\/p>\n<ul>\n<li>Build an object graph of the entire Mozilla tree recursively. I think I know how to do this, although there will be some issues with how to deal with local versus global variables.\n<li>Warn and eventually force a more rigorous dependency graph: warn if a dependent file &#8216;appears&#8217; without having a rule to create it.\n<li>Make parsing a lot faster using <a href=\"http:\/\/www.egenix.com\/products\/python\/mxBase\/mxTextTools\/\">mx.TextTools<\/a> instead of native python regular expressions. Keep the regular expressions as a slow path for developers who don&#8217;t have TextTools installed.\n<\/ul>\n<h3>Python Reference Cycles and Closures<\/h3>\n<p>While debugging pymake performance and memory usage I found an interesting fact, which in hind sight should have been obvious: functions which enclose themself in python create reference cycles which have to be cleaned up by the Python garbage collector:<\/p>\n<pre>def <span style=\"color: #090\">outerFunction<\/span>(outerCallback):\r\n  targetsToBuild = [1, 2, 3]\r\n  def <span style=\"color: #090\">innerCallback<\/span>():\r\n    if len(targetsToBuild):\r\n<span style=\"color: #333\">      # innerCallback closes on itself... this creates a reference cycle every time you call outerFunction\r\n      # if you call outerFunction 100000 times per build, this can add up really quickly and cause large GC pauses<\/span>\r\n      targetsToBuild.pop(0).build(<span style=\"color: #950\">innerCallback<\/span>)\r\n    else:\r\n      outerCallback()\r\n<\/pre>\n<p>After finding this problem, I refactored (<a href=\"http:\/\/hg.mozilla.org\/users\/bsmedberg_mozilla.com\/pymake\/rev\/219e5be50724\">1<\/a>, <a href=\"http:\/\/hg.mozilla.org\/users\/bsmedberg_mozilla.com\/pymake\/rev\/fe8f657b547f\">2<\/a>, <a href=\"http:\/\/hg.mozilla.org\/users\/bsmedberg_mozilla.com\/pymake\/rev\/3dc32b90e152\">3<\/a>) the pymake code to use objects instead of closures to save asynchronous state while rebuilding. Also, OptionParser instances create cycles by default. There is a lightly-documented method OptionParser.destroy which can be used to <a href=\"http:\/\/hg.mozilla.org\/users\/bsmedberg_mozilla.com\/pymake\/rev\/9cbc577ac665\">manually break<\/a> these cycles (thanks to Ted for finding it). pymake now runs without creating any reference cycles and I disabled the python garbage collector.<\/p>\n<h3>Environment Munging in MSYS<\/h3>\n<p>When MSYS goes from an MSYS process to a Windows process, and vice-versa, it munges certain environment variables to account for the path styles. I previously thought that it only munged PATH, but I discovered today that I was wrong: MSYS was munging the MAKEFLAGS environment variable in odd ways.<\/p>\n<p>If MAKEFLAGS in the MSYS process was &#8216; -j1 &#8212; PATH=e:\/builds\/mozilla-central&#8217; it would be munged into &#8216; -j1 &#8212; PATH=e;c:\/mozilla-build\/msys\/builds\/mozilla-central&#8217; in a non-MSYS process. Without the leading space the value was not touched. I don&#8217;t know why this is, but I <a href=\"http:\/\/hg.mozilla.org\/users\/bsmedberg_mozilla.com\/pymake\/rev\/dccfe657f9d2\">altered the pymake code slightly<\/a> so that MAKEFLAGS would never start with a space (and would be more compatible with gmake in the process).<\/p>\n","protected":false},"excerpt":{"rendered":"<p>pymake news: Bad news: pymake is still 5x slower than GNU make on Linux\/Mac. Good news: pymake is 25% faster than msys make (GNU make on Windows)! Best news: there&#8217;s a lot of room to make performance better. All measurements are do-nothing depend builds. Full rebuilds aren&#8217;t significantly affected because compiler speed overwhelms any time [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2,6],"tags":[210,32],"class_list":["post-478","post","type-post","status-publish","format-standard","hentry","category-mozilla","category-untagged","tag-pymake","tag-python"],"_links":{"self":[{"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/posts\/478","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/comments?post=478"}],"version-history":[{"count":10,"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/posts\/478\/revisions"}],"predecessor-version":[{"id":488,"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/posts\/478\/revisions\/488"}],"wp:attachment":[{"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/media?parent=478"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/categories?post=478"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/benjamin.smedbergs.us\/blog\/wp-json\/wp\/v2\/tags?post=478"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}