Enormity

(This blog post is a more firm version of a series of tweets and forum posts I made a few weeks ago. This should also be considered a refresh of this post by Mirco Bauer a few years ago)

It has been said that Mono is bloated, and that people should use “lighter” frameworks that don’t pull in “hundreds of meg”.

How much basis in reality is there for those comments?

Well, let’s do a little thought exercise. How much space is the minimum space needed for several languages to work?

For this experiment, we will be looking at the required installation size on disk of several language frameworks, each time installing the bare minimum for a command-line “hello world” app in that language to run. For example, the Ruby result is the minimum install required to run:

puts 'Hello world'

and the Mono result is the minimum install required to run the following code, which has been compiled on a different machine using “dmcs”:

public class Hello1
{
   public static void Main()
   {
      System.Console.WriteLine("Hello, World!");
   }
}

Obviously, for dynamic languages like Ruby and Python, there is no compiler step needed. For languages like C# and Java, it is fair to compile these on a different machine as end-users of packages running these frameworks receive binaries, not source – they do not need a development environment.

So, on to the test environment. This is a bare bootstrapped AMD64 Debian Unstable system, as of today (i.e. “debootstrap sid /tmp/badger”). Its install size is 275MB. So… how much space?

Ruby 1.9

Test code is as follows:

puts 'Hello world'

Install requirements are as follows:

root@dream:/# aptitude install ruby1.9.1
The following NEW packages will be installed:
  libffi5{a} libruby1.9.1{a} libyaml-0-2{a} ruby1.9.1 
0 packages upgraded, 4 newly installed, 0 to remove and 0 not upgraded.
Need to get 4703 kB of archives. After unpacking 13.1 MB will be used.

OpenJDK 7

Test code is as follows:

class HelloWorldApp {
    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}

Install requirements are as follows:

root@dream:/# aptitude install openjdk-7-jre-headless
The following NEW packages will be installed:
  ca-certificates{a} ca-certificates-java{a} dbus{a} fontconfig-config{a} 
  icedtea-7-jre-cacao{a} icedtea-7-jre-jamvm{a} java-common{a} 
  krb5-locales{a} libavahi-client3{a} libavahi-common-data{a} 
  libavahi-common3{a} libcap2{a} libcups2{a} libdbus-1-3{a} libexpat1{a} 
  libffi5{a} libfontconfig1{a} libfreetype6{a} libglib2.0-0{a} 
  libglib2.0-data{a} libgssapi-krb5-2{a} libjpeg8{a} libk5crypto3{a} 
  libkeyutils1{a} libkrb5-3{a} libkrb5support0{a} liblcms2-2{a} libnspr4{a} 
  libnss3 libnss3-1d{a} libpcre3{a} libpcsclite1{a} libsystemd-login0{a} 
  libxml2{a} openjdk-7-jre-headless openjdk-7-jre-lib{a} openssl{a} 
  sgml-base{a} shared-mime-info{a} ttf-dejavu-core{a} tzdata-java{a} ucf{a} 
  xml-core{a} 
0 packages upgraded, 43 newly installed, 0 to remove and 0 not upgraded.
Need to get 51.5 MB of archives. After unpacking 137 MB will be used.

Python 3.2

Test code is as follows:

print ("Hello world")

Install requirements are as follows:

root@dream:/# aptitude install python3.2-minimal
The following NEW packages will be installed:
  file{a} libexpat1{a} libffi5{a} libmagic1{a} mime-support{a} python3.2{a} python3.2-minimal 
0 packages upgraded, 7 newly installed, 0 to remove and 0 not upgraded.
Need to get 4853 kB of archives. After unpacking 17.7 MB will be used.

Mono 2.10 (C#)

Test code is as follows:

public class Hello
{
   public static void Main()
   {
      System.Console.WriteLine("Hello world");
   }
}

Install requirements are as follows:

root@dream:/# aptitude install mono-runtime
The following NEW packages will be installed:
  binfmt-support{a} cli-common{a} libmono-corlib4.0-cil{a} libmono-i18n-west4.0-cil{a} libmono-i18n4.0-cil{a} 
  libmono-security4.0-cil{a} libmono-system-configuration4.0-cil{a} libmono-system-security4.0-cil{a} 
  libmono-system-xml4.0-cil{a} libmono-system4.0-cil{a} mono-4.0-gac{a} mono-gac{a} mono-runtime 
0 packages upgraded, 13 newly installed, 0 to remove and 0 not upgraded.
Need to get 4383 kB of archives. After unpacking 11.5 MB will be used.

Of course, every time I try to make the argument that Mono is much leaner than what people propose as an alternative, the anti-Mono brigade go on the defensive and insist that nobody in their right minds uses Python or Ruby either – everything ever should be written in Qt.

So, hypothetically…

Qt 4.8 (C++)

Test code is as follows:

#include <QTextStream>

int main(int argc, char **argv)
{
   QTextStream s(stdout);
   s << "Hello, world!n";
   return 0;
}

Install requirements are as follows:

root@dream:/# aptitude install libqtcore4
The following NEW packages will be installed:
  libffi5{a} libglib2.0-0{a} libglib2.0-data{a} libpcre3{a} libqtcore4 
  libxml2{a} sgml-base{a} shared-mime-info{a} xml-core{a} 
0 packages upgraded, 9 newly installed, 0 to remove and 0 not upgraded.
Need to get 10.3 MB of archives. After unpacking 27.8 MB will be used.

Presented without comment for your consideration.

Edit: There are complaints that these numbers are misleading, because they show default package manager behaviour, rather than what happens when passing extra flags or config file settings to Apt. When disabling installation of “Recommends:” packages, the numbers change as follows:

  • openjdk-7-jre-headless 137 MB -> 116 MB
  • ruby1.9.1 13.1 MB -> 13.1 MB
  • python3.2-minimal 17.7 MB -> 6.1 MB
  • mono-runtime 11.5 MB -> 10.9 MB
  • libqtcore4 27.8 MB -> 13.7 MB

69 Responses to “Enormity”

  1. […] source: Go to Source Feed source: http://planet.debian.org/deriv/rss20.xml License: The original licenses are retained […]

  2. Is there any reason your Qt code is so convoluted?

  3. @Albert, It’s the example I found on the intertubes for a simple command-line Qt app. And it doesn’t make a difference, since the only dynamically linked library from the example beyond the norm is libQtCore.so.4

  4. You have recommends turned on, which significantly increases the size of several of your install sets (mono doesn’t need binfmt-support or cli-common, libqtcore4 doesn’t need the xml or sgml bits, python3.2-minimal doesn’t need python3.2, and java doesn’t need half the packages listed there).

  5. @Anonymous,

    If Anonymous is right, I really think you should write a new post containing exactly the same experiment, but without the recommends turned on. Be a real man and post the new result even if your language is not the real winner in the end 🙂

    Still, I think even without the “recommends” your benchmark is not good because it really depends on how well packaged your dependencies are… Some may include the documentation directly in the package, some others may have docs in a separate “-docs”, package, which totally changes the total sizes and add messes with the benchmark. Seeing the same benchmark in different distributions might lead to totally different results. If you have a lot of time, it would be nice to see the same experiment at least in Fedora.

    Another nice test would be “time ./hello_world” (if you’re on a laptop, don’t forget to change your processor governor to “performance” instead of “ondemand”).

  6. @Anonymous Two, the recommends are enabled by default, I didn’t do anything to gain favour with those numbers.

    But here are your figures:

    openjdk-7-jre-headless 137 MB -> 116 MB
    ruby1.9.1 13.1 MB -> 13.1 MB
    python3.2-minimal 17.7 MB -> 6.1 MB
    mono-runtime 11.5 MB -> 10.9 MB
    libqtcore4 27.8 MB -> 13.7 MB

  7. @Anonymous Two,

    Benchmarking “time ./hello_world” would NOT be a useful comparison, at all. It would tell you next to nothing about the running performance of any app and certainly do nothing to draw comparisons between frameworks.

  8. @Jon, it might indicate framework startup time, though. i.e. penalize JITters like Java and Mono

  9. @directhex, yeah, but in my experience python is the worst offender here with its overly complex search path. However, thanks for the post!

  10. A comparison between Qt and Mono isn’t really fair, in my opinion. QtCore is a third-party framework on the level of Glib/GObject for C++, whereas Mono is a language runtime, that just happens to have a large standard library.

    If we’re talking about just writing “Hello world” to standard out, then we should be talking about just using the standard C++ runtime:

    #include
    int main ()
    {
    std::cout << "Hello world!" << std::endl;
    return 0;
    }

    Which happens to be installed by default on Debian, and is 332kB.

  11. @Chow Loong Jin, you don’t think libQtCore.so.4 is comparable to mscorlib.dll?

  12. But why do you need Qt at all if you’re comparing programming languages.

    int main()
    {
    cout << "Hello World" << endl;
    }

    works fine too for c++ and only needs c++.

    But why do you even care about disk space and not about performance?

  13. @Kai Mast, because for anything more complex than Hello World, the usual cry is “C++ is hard, you need to do all your bullshit manually”, to which the reply is “who uses C++ by hand, you should use a framework like Qt”.

  14. @Kai Mast, you’re right, you don’t need Qt to develop in C++ and it that case of course you get the best space footprint. But I think @directhex was comparing language environment and Qt is a common runtime used by many developers. It just happen that C++ has many runtime available (e.g. stdc++, boost, Qt).

    It’s the same reasoning that goes with C/Glib: you can limit yourself to what libc provides, but with Glib much pain goes away. (And of course, using libc and nothing else is even more space efficient 😉

    Space efficiency is interesting for some use-cases like small utilities: a colored command-line calendar written in java that requires you to install an hundred of megs is not a great idea.

  15. @directhex, QtCore is a third-party, unintegrated external library that strongly disagree over what C++ should be. Remember that it has a code generator (moc) to implement languages features, but the latest C++11 standard does not help moc in any way.

    If anything, Boost should be more comparable to mscorlib.dll that QtCore is. A part of Boost is even integrated in the latest C++11 standard.

    But even if you wanted to use Boost to write an ASCII, uninternationalized “hello world” static string to the unfettered, unfiltered standard output using the default system locale, it won’t be any different from the standard library way to do it, because Boost extends, but doesn’t replace, the C++ standard library.

  16. @directhex, Nope. mscorlib.dll is to Mono what libstdc++ is to C++, really. The fact that mscorlib.dll contains many more classes than libstdc++ is irrelevant.

    You could just as well swap QtCore out in favour of Glibmm, which happens to be 670kB worth of extra packages on top of the base set of packages.

  17. And for the record, screw frameworks (and Qt). I write my C++(11) by hand. It’s not hard.

  18. Actually the Qt version of the example problem looks more like this:

    #include

    int main(int argc, char **argv)
    {
    QTextStream s(stdout);
    s << "Hello, world!n";
    return 0;
    }

  19. @Kevin Krammer, doesn’t change the result, since in both cases, only libQtCore.so.4 is linked in.

  20. @directhex, right, that’s why I provided a comparable code snippet to help with the actual issue at hand: dependencies.

    Having an unnecessary complex piece of code for one investigated option can easily detract from the intended statement.

    I understand it can be difficult for people without C++ experience but with Java/C# background to understand that C++ does not require a new class definition for such a simple task as writing to stdout.

    As somebody else has already pointed out using C++ without Qt makes both code and dependency tree even smaller, but I guess the idea was to compare enviroments with large class libraries.

    P.S.: the include in my previous reply is obviously for QTextStream

  21. @Kevin Krammer, I’ve swapped out the code sample for yours, which is infinitely more readable, and *does* avoid side-tracking the reader. Thanks.

  22. So it’s only Java that installs the kitchen sink when all you really wanted is a blunt knife?

    Thanks; useful investigation. If anything, I’d expect more bloat in C++ libraries than Mono, partly because of maturity but more because of reimplementation and lack of standardisation. (Why does Qt depend on Glib?)

  23. @dhardy, Qt can be built with support for GLib based event loops, i.e. in order to make it easier to use libraries based on such event loops when there is no suitable low-level implementation available.

    Since this doesn’t introduce runtime overhead when not needed (and can be deactivated using environment variables) it is the default build configuration for Qt shipped by Linux distribution

  24. Weird comparison. It is easy to come up with a test given that you want a certain result.

  25. @q, why do you think is weird? It’s just data, you can get any conclusion you see fit. Thanks @directhex for taking the time to run the experiment.

  26. For python, shouldn’t it be python2.7 ?

  27. @mickabouille, movements within Ubuntu to become Python 3 only on the core lead me to believe that Python 2’s future isn’t rosy. Would you encourage new app development with Python 2 rather than 3?

  28. Not really, I was just supposing you would be using default versions in Sid for each language. But that’s not really important.

  29. I think a “hello world” is not the best comparison, or in such case, maybe we can add two languages more with a low cost:

    Perl:
    $ perl -E ‘say “Hello world”;’
    Hello world

    $ dpkg-query –show
    –showformat=’${Installed-Size} ${Priority}n’ perl-base
    4804 required

    As it’s required, no need for additional space.

    Dash:
    $ echo Hello world
    Hello world

    $ dpkg-query –show
    –showformat=’${Installed-Size} ${Priority}n’ dash
    248 required

    The same.

    Of course, it’s weird to solve things as “a little cli app” using +300MB depends. Agree.

    But it’s something we have to live with. Foo does its task in python, Bar does it in haskell, Baz does it using ocalm or lua, Biz uses pure C, Cruch does a cool program but uses a new language that is not packaged and uses modules from a external repository, etc etc etc…

    From my view point, there are two sides for evaluating:

    1) Development view (syntax, tools, results, etc)

    2) Final user view (results, installation, needed space, depends, upgrades, performance, portability, added value, etc)

    It’s more hard to evaluate, than just a “hello world”…

    People uses tools, tools are built over tool chains, there are ecosystems, there are more aspects than “needed syntax”, etc…

    If I need to put “Hello world” in a terminal, maybe I go for sh, but if I’ve to do a GUI app that joins tables of a relational database the user may have over many engines, and then I’ve to produce XML and a graph, using a distributed network… maybe I don’t go for sh.

    That is the point of the “Hello world” comparison… most complex applications (being perl, python, ruby, etc) are not going to use the “one line” syntax… they will use objects, classes, message dispatchers, streams, queues, events, etc.

    And yes, maybe there is a DSL that eases things, but then the real code used will be behind the module providing the DSL.

  30. @Iñigo, you’re right, of course – but there are still a significant number of people who insist that Mono is a significant on-disk burden. The numbers as presented say they’re liars.

  31. @directhex: They might also refer to the fact that it’s not commonly installed and then needs to be pulled in to support that application. However that’s sort of chicken and egg. SCNR.

  32. @Philipp Kern, yeah, it’s just that installing a little more than ten megs is not really a big effort.

  33. Given that you updated your numbers on how much disk space it language uses to be:

    openjdk-7-jre-headless 137 MB -> 116 MB
    ruby1.9.1 13.1 MB -> 13.1 MB
    python3.2-minimal 17.7 MB -> 6.1 MB
    mono-runtime 11.5 MB -> 10.9 MB
    libqtcore4 27.8 MB -> 13.7 MB

    Shouldn’t you update the post to say that mono comes after python?

  34. @Christian, the Recommends: figure (as per the original post) is more useful since it reflects the default situation. Sadly, packages are gradually becoming less usable in different ways as time goes on when recommends are not installed, since recommends became a default.

  35. And yet people ask me why i don’t like Java 🙂

  36. No, the –no-install-recommends numbers (and you really ought to be using apt, not aptitude) should be used. Everyone who uses their Debian with recommends installed by default is crazy in my book.

    Useful snippets for /etc/apt/apt.conf are:

    Acquire::PDiffs “false”;
    Acquire::CompressionTypes::Order { “xz”; “gz”; };
    Acquire::http::Pipeline-Depth “0”;
    Debug::pkgProblemResolver “true”;
    APT::Install-Recommends “0”;
    APT::Install-Suggests “0”;

  37. Uhm, why your blog is fucking up quotation marks?

  38. @mirabilos, helpful default WordPress feature.

  39. @mirabilos, if you don’t like default settings, bring it up with the people whose tools are using “wrong” defaults.

    Shouting at the universe that it’s wrong doesn’t change it.

  40. I have to ask this: you install the base packages for each language, which is still _more_ than what is required for the program to run. What is the _absolute_ minimum file size? Not just what debian/ubuntu think you need?

  41. @TGM, ehm… well, you’re right, but there’s a limit to when things stop being useful.

    The *absolute* bare minimum to run a Mono hello.exe is the runtime (3MB) and mscorlib.dll (2.8MB). Our Mono packages are the most minimal set of libraries installed for /usr/bin/gacutil to work (which brings in Mono.Security.dll, which brings in System.dll, which brings in System.Configuration.dll and System.Xml.dll, which brings in System.Security.dll)

    root@dream:/tmp# MONO_PATH=. ./mono hello.exe 
    Hello, World!
    root@dream:/tmp# ls -hl mono mscorlib.dll 
    -rwxr-xr-x 1 1000 1000 3.0M Jun 19 15:42 mono
    -rw-r--r-- 1 1000 1000 2.8M Jun 19 15:42 mscorlib.dll

    I’m not sufficiently invested in the other languages to know how minimal one can get with them.

  42. I have to ask this: you install the base packages for each language, which is still _more_ than what is required for the program to run. What is the _absolute_ minimum file size? Not just what debian/ubuntu think you need?

    Frankly, I don’t think any of the above are that bloated compared to PC builds these days.

  43. Ah… so Mono’s not bloated – it’s obviously just poorly coded, since it runs slow as molasses compared to all of the other options despite a smaller package size.

    I only have a few Mono apps installed, like Keepass2 – and they all run noticeably slower (i.e. I can see incomplete window draws as they start up) than any other apps on my system.

  44. @3vi1, Keepass is a WinForms app. Mono’s WinForms implementation is… unmaintained, and sub-optimal. It’s an attempt to clone a terrible, terrible API.

  45. @directhex, to be more correct, although WinForms is no longer maintained by the Mono team, it’s supposed to be maintained by the community. This may be close to unmaintained, but not quite. 🙂
    A couple of WinForms patches still make their way for each new version of Mono.

    That said, I agree with your point: judging Mono based on a WinForms app is not a good idea.

  46. @Stifu, the community has been pretty poor at propping up the pieces of Mono under its remit. Because new blood is scared away. By being told lies about bloat and patents. Ho hum.

  47. The artificially bloated Qt snippet does not really help this post look smart. You really should change it to Kevin Krammers suggestion.

  48. @Alessandro, by “artificial” you mean “default package manager behaviour”, yes?

  49. @directhex, No, I just mean the code snippet. I know thought that this is unrelated to the main message of this post (size of package dependencies).

  50. @Alessandro, code sample changed. It’s *much* nicer. Same requirements though:

    	linux-vdso.so.1 =>  (0x00007fff3c96c000)
    	libQtCore.so.4 => /usr/lib/x86_64-linux-gnu/libQtCore.so.4 (0x00007f7b01ee1000)
    	libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f7b01be1000)
    	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f7b019ca000)
    	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f7b0160d000)
    	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f7b013f0000)
    	libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f7b011d8000)
    	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f7b00fd4000)
    	librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f7b00dcc000)
    	libglib-2.0.so.0 => /lib/x86_64-linux-gnu/libglib-2.0.so.0 (0x00007f7b00ad6000)
    	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f7b007dc000)
    	/lib64/ld-linux-x86-64.so.2 (0x00007f7b023de000)
    	libpcre.so.3 => /lib/x86_64-linux-gnu/libpcre.so.3 (0x00007f7b0059e000)
  51. @directhex, Thanks 🙂

  52. It would please me muchly to see lua5.1 added to this list and being victorious 🙂

  53. @Steven Chamberlain, I might add more languages to the list (and Lua won’t win, Perl will since it’s preinstalled on Debian), but I’d rather have a bunch to add rather than doing it one at a time.

  54. FWIW, while not preinstalled, I can get you a statically linked (against klibc) mksh on i386 in about 130K. Less if you disable optional features (like the Vi command line editing mode, or command line editing at all). Add Linux with devtmpfs.mount=1 on the kernel command line, et voilà…

    OK, dash is smaller. Not much, though. But it’s not noticeably faster (while mksh beats things like GNU bash by a factor of 3), and can do *much* less; it’s barely more than the POSIX minimum, and not even that. And it still uses strcpy. In 2012.

  55. bperry@bperry:~$ echo “System.Console.WriteLine(“hi”);quit;” | csharp
    hi
    bperry@bperry:~$

  56. Interesting, thanks for the post. I guess the takeaway is that they are all about the same size. But since you allowed the app to be compiled on a separate machine, couldn’t the app be ahead-of-time-compiled and wouldn’t that cut further down on the dependencies needed and perhaps even app size? (not arguing for apps to be distributed that way, just curious)

  57. @and, it would speed up the app startup time, but also use more space on disk for the AOT cache. We’re exploring doing this in future

  58. This got me wondering, if maybe someone, somewhere in the world is using mksh as a CGI webscripting language, say under nginx+FastCGI via spawn-fcgi or similar.

    There could be real uses cases on these embedded devices that configure their networking from a web-based panel, that only needs to execute shell commands anyway.

  59. @Steven Chamberlain, I think this (although with haserl as “webserver”) is used in the wild, probably sparingly though. And I’ve been contracted to write a proof-of-concept *WRT web interface using tntnet, which I had call mksh scripts as backends too.

    The MirBSD website is static HTML compiled from mksh scripts, so that doesn’t count, but I’ve been using mksh CGIs personally.

    Note that mksh-static (as shipped back then in Debian, it was compiled with -DMKSH_SMALL which disables Vi editing, and linked against dietlibc) won the nullperf tests from Iustin Pop recently. (And that test showed me places where it can be improved even further.)

    I don’t know FastCGI, but regular CGI works pretty decent with it.

  60. @mirabilos, Yeah, this post reminded me of Iustin’s perf-null tests and is what made me think of mksh, CGI, etc. (Actually it was me that asked him in the comments if he’d add mksh-static, and Lua).

    To get sort of back on-topic: apt-get install fcgiwrap mksh nginx-light spawn-fcgi => 1832kB of disk space, much smaller than anything on here yet it provides the HTTP service too.

    It worked, and could serve ‘hello world’ from an mksh CGI script at 200 requests per second (according to ApacheBench), per 2.5GHz core, using I think 8 MiB total resident memory inside a GNU/kFreeBSD jail, for fun.

  61. Hello,
    just a side note: Python3 print is not a keyword anymore, but a function so you have to use

    print (“Hello world”)

    to make it works.

    Cheers,
    Daniele Tricoli

  62. @Daniele Tricoli, updated, thanks!

  63. An interesting metric would be what the size of a single standalone binary for each of these would be. By standalone I mean all dependencies are packaged – so for Python you’d use freeze, for Mono mkbundles and so on.

    Ideally ldd on the produced ELF should reveal no dynamically linked libraries.

    Just a thought.

  64. @Luke GB, 6.0MB without any deps, 4.2M linked against zlib (compressed app)

  65. Have you looked at http://shootout.alioth.debian.org/
    It tries to provide some benchmarks (speed, memory use etc.)

  66. Curious how the numbers look when using GCC’s java runtime?

    gcj-jre-headless

  67. @Nick, You only need libgcj12 to run hello world, which is marginally smaller. 88.3MB by default, 74.2MB without recommends.

  68. Great post as usual @directhex.

  69. This is a good time to remind people of the commenting policy for my blog: http://apebox.org/wordpress/rants/219/

    I don’t censor all opposing opinion, but I do redact commenters who fail to meet sufficient a sufficient level of intelligence. To be clear: your comment is not stuck in a spam queue, I read it and consider you too stupid to allow my site to be used as a platform for your idiocy.

Leave a Reply