2006-11-03

Bazaar vs. Mercurial : An unscientific comparison

Two things are a constant with me: I have some personal programming project going at all times and that all of my coding takes place on my laptop. That means I have a need for a VCS (or a RCS, SCM system, or some other buzzword term) that I can use when I don't have a Net connection.

That has led me to look at distributed VCSs for personal use and do a comparison between two Python-implemented ones: Bazaar and Mercurial (a.k.a., bzr and hg). Since not everyone might know what the "distributed" part means but most likely knows how Subversion or CVS (and if you are in the latter camp but not the former, for the love of something you care about please upgrade!), I am going to start with a quick distributed VCS primer.

The biggest difference from a client/server VCS (ala svn) to a distributed one is the idea that branches are used a lot more. In svn, branches are used when you want to do some long-term development on something. But in a distributed VCS you create one for every individual bug that you are working on. This provides a nice level of isolation between disparate thing you might be working on. Because of all this branching, distributed VCSs try to make branching a cheap operation.

And one way to make branching cheap is to do it locally. The "distributed" part of these tools comes from the fact that they are designed to work offline. This lets you commit changes and such to your local disk without requiring Internet access to hit a central repository online (but you can commit your changes to an online repository later when you are ready to). This is really handy if you develop on a laptop; it really sucks to have to do a bunch of coding remotely and then commit one huge patch instead of committing a bunch of individual patches. You want the atomicity of commits that represent a single piece of semantic change to allow better tracking of how/why your code changed, not some blob of code that changes a bunch of things at once. Plus it allows for easier rollback if you accidentally introduced a bug.

Finally, the other big difference for these tools is that since they expect everyone to be working from a branch they use branches as a way to share code changes between each other. From the view of Python development, I might end up working on a branch that adds the new Whizbang feature to Python while some other developer does the Doohickey feature which interacts with my feature. We could share our work by telling each other where our repositories were online and then constantly pulling and pushing patches from each other (although having a repository online is not required).

This does lead to a shift in development compared to svn in terms of patches. You end up pushing what are called changesets between branches. You can view a changeset as basically a bundle of patches. This is rather different (at least for me in terms of Python development) when compared to using svn as I am used to getting a big diff file, looking at the diff, applying the diff, possibly looking at the patched source and tweaking it, running the test suite, and then committing. The distributed VCSs want you to view it more at a commit level and not as a huge diff file. This does change how one pushes around changesets, though, as not everyone has a place online to push a online version of a branch. Luckily both tools at the bare minimum support generating a diff file as needed.

Hopefully that explanation is clear. If not Bazaar has an explanation as does Mercurial from the perspective of a cvs/svn user. Once you have a handle of what these tools are trying to do, hopefully this comparison will make some sense. =)

To begin, let's introduce Bazaar (commonly known as bzr). Created by Martin Pool, it is now a project developed by Canonical (of Ubuntu fame) with Martin as the lead. It is written entirely in Python. This comparison was done against version 0.12.

Mercurial was started by Matt Mackall of Selenic (although personally I view Bryan O'Sullivan as co-front man as I have met him personally and he has always personally answered my emails). Mercurial is mostly written in Python with some key parts of the code written as an extension module. Version 0.9.1 was used in this comparison.

When comparing the two there is not much difference in terms of commands. The only blaring difference is what you do after you pull in a new changeset into a branch. In bzr it gets applied and everybody is happy. But in hg you have to do either an explicit update or merge step. I found out this is so that you can have more fine grained control over what version you update to. Say you have a branch at revision 100 and you pull in a changeset that has a commit against revision 101. By making the application of the changeset a separate step you can have only changesets applied to revision 100, make sure everything is happy, and then pull in the new stuff to help minimize possible change conflicts. There is an extension, though, that will automate this stuff for hg and act more like bzr (called 'fetch').

The other huge difference is how they handle pushing and pulling changesets to a remote repository. Hg has you install it on the server and then uses SSH to tunnel the data and commands across. There is also support for pulling over HTTP. Bzr, on the other hand, has no built-in support for pushing (yet), but they do support pulling over HTTP. If you look at the bzr web site you will notice they suggest rsync for a naive way of pushing changesets. They do have support using SFTP if you have Paramiko installed (which itself requires pycrypto). Lastly, there is support for using SSH, but it is undocumented (use the ``bzr+ssh`` protocol and then just follow the normal SFTP instructions) and it still requires Paramiko (although that is unneeded and should be removed as a dependency soon).

One last difference that is more a tech thing than how you use the tools is bzr's shared repository. Basically a shared repository stores common metadata between branches in a single location in order to cut down on disk usage. In order to use the feature you just need to run a single command in the directory above where you store your branches. Because of its simple usage I ended up adding a comparison with bzr using shared resources along with vanilla bzr.

To start this comparison I am going to talk about the benchmarking results first and then follow with a discussion of the warm, fuzzy stuff (docs, community, etc.).

First, a word about my benchmarking numbers: they are not exactly scientifically rigorous. I didn't run the benchmarks multiple times and do an average of them or try to set up my laptop in a clean room setting to that other apps were not running in the background. What I did do was run them back-to-back while I left my laptop alone (short of tapping the touchpad to prevent the screensaver from kicking in). And the timings are consistent from what I noticed when I constantly ran my tests.

To handle all of this I wrote a Python script that acted as a replay driver. Each VCS had a command module that listed the various commands required to get it to do what I needed it to do (e.g., commit, pull, etc.). I logged the info I cared about and dumped it all to pickle files. I then wrote another script to output a reST document for easy comparison of the results. All of this was run against a pydebug version of Python from svn. In other words all reported numbers should only be viewed in terms of relative performance comparison and not what absolute performance would be like.

The code I used to run this (and a copy of the reST document for my results) can be found here. You will need to change the server location since the address uses an SSH alias I have for my personal server on top of pointing to a specific location on the server. And you can say how I fouled something up and how something else can be done better, but I don't guarantee I am going to care after having put so much time and effort into this already. =)

Here are the basic steps I had each VCS do after cleaning up the environment from any previous run:
  1. Any prep step needed (only used by bzr's shared repository).
  2. Create and initialize a repository (named 'main').
  3. Add 100 files, each 1000 lines long and commit them.
  4. Append a line to all the files and commit.
  5. Create a repository on a server.
  6. Push the 'main' repository to the server.
  7. Create a 'pristine' repository from the remote repository.
  8. Create a 'branch' repository by cloning the 'main' repository locally.
  9. Append a line to every file in the 'branch' repository and commit.
  10. Push changeset from 'branch' repository to remote repository.
  11. Update 'pristine' repository by pulling latest changeset from the remote repository.
Yes, it is contrived as you usually don't update 100 files at once, but then again you do have a handful of files that do have a total over 100 changed lines easily. What it really comes down to is the above was just very easy to write and configure (getting all of the reporting to work the way I wanted, though, is something else =).

So, how did the two VCSs do? In terms of execution time for local disk commands, hg is the faster of the two by a good amount (I am only bothering to report the faster of the two bzr versions for brevity; download the tar.bz2 file to see the full output):
  • repository initialization: hg 2.8x faster
    • hg: 0.67 seconds
    • bzr: 1.93 seconds
  • adding files for committal: hg 2.4x faster
    • hg: 1.12 seconds
    • bzr shared repository: 2.73 seconds
  • committing new files: hg 3x faster
    • hg: 4.08 seconds
    • bzr: 12.36 seconds
  • commit an append line to every file: hg 2.3x faster
    • hg: 8.63 seconds
    • bzr: 20.1 seconds
  • clone a repository: hg 4x faster
    • hg: 3.23 seconds
    • bzr shared repository: 12.92 seconds
  • committing in the cloned branch: hg 2x faster
    • hg: 10.6 seconds
    • bzr shared repository: 21.79 seconds
But what really made the performance numbers a huge difference was anything dealing with networking. Now granted these numbers rely on a lot of variance in terms of network connectivity, if the server was being hit to serve a web page, etc. But the speed differences are not miniscule and seemed consistent:
  • Initialize remote repository: hg 2.8x faster
    • hg: 5.71 seconds
    • bzr: 16.43 seconds
  • Push local repository to remote repository: hg 7.1x faster
    • hg: 11.38 seconds
    • bzr shared repository: 80.99 seconds
  • Branch from remote repository: hg 1.9x faster
    • hg: 8.16 seconds
    • bzr shared repository: 15.62 seconds (vanilla was 52.85 seconds)
  • Push appended line change to server: hg 10.1x faster
    • hg: 5.36 seconds
    • bzr: 54.62 seconds
  • Pull appended line change from server: hg 1.8x faster
    • hg: 9.65 seconds
    • bzr shared repository: 17.69 seconds (46.89 seconds for vanilla bzr)
Something to realize about these network numbers are these are much better compared to what I originally was benchmarking. I originally was creating 1000 files of 100 lines each, but bzr was taking over 10 minutes to push the initial version of the server, and when you have to do that constantly because of bugs in your code from introducing new reporting stats and such, it gets old really fast.

One interesting thing is how much faster the shared repository run of bzr was compared to the vanilla one. The remote branching, for instance, was 3.3x faster with the shared repository than without it.

But speed is not everything; there is disk usage to consider since every change needs to be stored somewhere. Here I will report all three VCSs using ``du -k -s``:
  • Total size of all local disk repositories: bzr shared repository 1.07x smaller
    • hg: 2468 KB
    • bzr: 3932 KB
    • bzr shared repository: 2292 KB
As you can see, using the shared repository makes a big difference for bzr (1.7x smaller between the two versions). But compared to hg the difference is miniscule; I had to add decimal places I left out in the performance multipliers just so the difference showed up.

For expanding, both support plug-ins. I didn't really delve into either beyond noticing that they both take different approaches to the API required for a plug-in to work.

In terms of other techy stuff, both projects have their own trajectory. For instance Mercurial seems to have more support for packaging up changesets and shipping them around. This compares to Bazaar where they are working on a "smart server" to be more competitive with svn in terms of server capabilities.

OK, with the techy out of the way, let's talk about documentation. Neither is spectacular. Both use wikis and thus are kind of all over the place. Mercurial seems to have slightly better documentation that is more up-to-date. For instance, the bzr+ssh protocol is not documented in the Bazaar docs and it has been in the tool since version 0.11. What's worse is that the tutorial for Bazaar has not been updated since version 0.8.

But luckily both tools have helpful people. For Mercurial I emailed the general list and got help within a day to answer my question about what an update/merge step was required after pulling a changeset. For Bazaar community support, I went to the IRC channel since it is supposedly a "vibrant community" even though I personally dislike IRC. I wanted to see if rsync was really the only option I had for pushing/pulling a server short of setting up my web server to point to the repository directory or installing third-party software (having the batteries included is a big thing with me). There I had 'marienz' and 'j-a-meinel' help me out and tell me about the bzr+ssh protocol. They even generated a patch while I was talking with them to remove the Paramiko dependency for the protocol.

After all of this, I think I like Mercurial the most. But with both tools still feeling fairly beta (I think a real push to clean up and update their documentation would help) I am not about to say I am not willing to switch in the future; I would not be upset if I was forced to use Bazaar. But if I did, I would only use a shared repository since it makes such a difference. And I am sure someone is going to point out some feature that Bazaar supports that Mercurial doesn't that some people are going to consider crucial. But my gut says that I like Mercurial the most.

With that being said, there are other possibilities for distributed VCSs. You can always look at SVK, darcs, Git, or Monotone (and use Tailor to switch between them).

[update]
I originally said that I might be willing to evaluate another VCS. I got a good amount of responses recommending darcs. Problem is that installing darcs on my server for remote tests would be a huge inconvenience. I did look at it and it seems like a fine VCS, but not enough to motivate me to get ghc installed (never managed to get that program to install properly).

31 comments:

Mike Watkins said...

Not much to add here except that I find hg to be a decent tool to use that has so far not surprised me at an inopportune time. I ran into hg originally when I became a user of a tiling window manager (wmii then dwm) project which was using hg.
I decided to adopt it in part due to the simple ssh support for pushing.

I now maintain much more in small and large hg repos than I ever did before, partly due to the convenience of using hg. For instance I keep my production servers /etc and /usr/local/etc checked in; elements of my home directory; a local copy of an images directory which I push to a public web server, oh yeah and real code too!

Anonymous said...

I'd be curious to see how darcs measures up with your benchmarks. I personally find darcs easier to use than hg, cvs, or svn. There is a Mac binary available for download on the site.

-David

Aaron Bentley said...

Some small things:
- Mercurial's server is what Bazaar folk call a "smart server" (In contrast to, say, http, which is a "dumb protocol".) The bzr+ssh protocol you used is the Bazaar smart server.

- Hosts that support SSH usually support SFTP (OpenSSH provides it), so RSYNC is rarely your only option

- I'm not sure why you say Mercurial has more support for "packaging up changesets and shipping them around". Perhaps you weren't aware of Bazaar's "bundle" command?

Brett said...

Aaron:
1. Thanks for the clarification.

2. It isn't that rsync is your only option, period, it is just pushed as the only option you have if you don't install Paramiko and PyCrypto.

3. No, I wasn't aware of bundles because I didn't come across it in the docs. Can you point to some docs that explain how they are used?

olivier said...

As for the bundle feature of bzr, the online help is quite complete:

% bzr help bundle ~
usage: bzr bundle-revisions [BASE]
aliases: bundle

Generate a revision bundle.

This bundle contains all of the meta-information of a
diff, rather than just containing the patch information.

You can apply it to another tree using 'bzr merge'.

bzr bundle-revisions
- Generate a bundle relative to a remembered location
bzr bundle-revisions BASE
- Bundle to apply the current tree into BASE
bzr bundle-revisions --revision A
- Bundle to apply revision A to remembered location
bzr bundle-revisions --revision A..B
- Bundle to transform A into B

options:
--output=ARG write bundle to specified file
--remember Remember the specified location as a default.
-r ARG, --revision=ARG
-h, --help show help message
-v, --verbose display more information

This page on how tocontribute back to bzr provides a running example on how to use the bundle feature in the real life:

http://bazaar-vcs.org/BzrGivingBack

Aaron Bentley said...

If you're really averse to installing dependencies, you also have the option of using FTP. Obviously, we'd rather push rsync than an insecure protocol like that, but it is available, and all the batteries are included.

Many of the distros that ship Bazaar consider Paramiko a requirement, since SFTP is the recommended way of pushing branches. It certainly certainly doesn't make sense for us to provide our own SSH and SFTP implementation.

Brett said...

Oliver: Thanks for the info. Any plans to make bundles less obscure?

Aaron: I would never expect anyone to reimplement SSH or SFTP. This is why I am glad you guys added the bzr+ssh protocol and will be removing the Paramiko dependency as that gives me exactly what I want.

Karl Guertin said...

I've poked at other vcs, but in the end I find that I love darcs. I wouldn't recommend it for multi-megabyte projects due to perf issues, but it works just fine for my thousands to tens of thousands LoC projects. It's extremely simple and I find I make cleaner patches due to the prompts and amend-record.

Chad Crabtree said...

I would highly recommend darcs. I've been using for many small projects with great success.

Brett said...

Obviously darcs is popular. Unfortunately, as I stated in an update to the post, getting darcs installed on my server to test would be a pain in the rear. Thus I am going to skip evaluating darcs.

Anonymous said...

As for the scientifics and barfs in the measurement, I read the pydebug from svn to be a python debug build?

The fact that hg has performance critical code in native code while bzr doesn't may actually be more of a significant difference if you compare them on a debug python build. Comparing the compiled hg code against a release-compiled python would be appropriate, I guess.

Axel

Brett said...

My laptop is in the shop so I can't double-check this right now, but I am pretty sure I ran the test code with a debug build, but the tools were installed with a non-debug version of Python 2.5 checked out from svn.

Anonymous said...

Thanks for taking the time to do this analysis and write it up. You made my decision clear about trying hg. I appreciate the work and the info!

David

Anonymous said...

what strange server do you have that you do not get a binary darcs - which is one exe file (linux, windows, solaris, ...)?

Anonymous said...

can you redo this test pls with the latest versions of bzr and hg.

Couldn't figure out how your script should work, so I can't do it on my own.

Anonymous said...

The fact that hg has performance critical code in native code

hg is pure python. Why do you think that it has native code?

Brett said...

Because if you look at the source code (http://selenic.com/repo/hg/file/de081fbb27fe/mercurial/) you will notice there are three C files: base85.c bdiff.c mpatch.c

gaspard bucher said...

Thanks for the nice description of these tools. I have personnaly been quite happy with svk. It has a "push" and "pull" command and works transparently with the remote subversion repository.

Josh said...

An excellent summary, thanks for doing all this benchmarking.

On another note: I always find it amusing to frustrating how people are so willing to jump down someone's throat whenever they do this sort of analysis. It doesn't usually contribute usefully to discussion- if there's a serious flaw in your procedure it should be pointed out, but otherwise they should just point to their own benchmarks. What? They didn't run any? Right. That's what I thought.

arnebab said...

I really like your comparision, and I searched for something like this for hours... Strange to thing that "mercurial ftp" got me the result :)

The kind of test you do is great, you nicely documented it, and you even mentioned that it isn't scientifically strong.

In short: Many thanks!

Besides: I also find Mercurial very intuitive to use, but that's subjective (though all comparisions I read till now (about 20 or so) showed me that my gut feeling was right when I chose it in the first place).

arnebab said...

Hi,

Inspired by this test, I created a small script which tests the speed of Mercurial and git.

It's the file distributed_version_control_system_test.py in my python_tests repo:

- http://freehg.org/u/ArneBab/python_tests/

just call it with the number of files to create.

As a first peek, I got these results ([git, hg], all data in seconds, 1000 files):

init: [0.040084338188171385, 0.35065162181854248]

initial_commit: [1.6444477796554566, 3.9022002458572387]

commit_after_append: [1.8680219650268555, 3.6192331314086914]

commit_after_append_of_same_big_data: [13.361056089401245, 25.795072078704834]

arnebab said...

I should add: this is local only, and the appended data is quite small.

Even the "big_data" is just one line containing twice as many random numbers as there are files.

Maybe I'll devise more tests later :)

arnebab said...

And here's some data with 10000 files - it seems like Mercurial gains speed on bigger repositories...

init: [0.037526702880859374, 0.33673391342163084]

initial_commit: [34.48111524581909, 32.744103097915648]

commit_after_append: [39.806859970092773, 26.222161054611206]

commit_after_append_of_same_big_data: [1870.5676879882812, 2262.3359749317169]

I'll run another test with 100000 files this night.

One million might be a bit over the top :)

But appending multiple lines is another thing I thought about.

Besides: This is the system where I run these tests:
System uname: 2.6.24-gentoo-r3 x86_64 AMD Athlon(tm) 64 Processor 3000+
(grabbed from emerge --info)

arnebab said...

I created a german article for the local speedtests:
- http://draketo.de/deutsch/freie-software/licht/mercurial/hg-vs-git-local-operations-unscientific

Thought it might interest you (or at least some of the german speaking commenters in here).

arnebab said...

Data:

= 2 Files =

init:
- git: 0.01423661708831787 s
- Mercurial: 0.13704051971435546 s

initial_commit:
- git: 0.086095404624938962s
- Mercurial: 0.32350056171417235 s

commit_after_append_small:
- git: 0.084930634498596197
- Mercurial: 0.31660361289978028

commit_after_append_of_many_lines:
- git: 0.086296844482421878
- Mercurial: 0.31294920444488528

commit_after_append_of_one_long_line:
- git: 0.08749730587005615
- Mercurial: 0.32180678844451904


= 2000 Files =

[git, hg], time in seconds, mean value.

init [0.063668489456176758, 0.14365851879119873]
- Factor: 2.25635192571

initial_commit [1.5152170658111572, 3.1502538919448853]
- Factor: 2.07907762064

commit_after_append_small [1.4661256074905396, 3.7556029558181763]
- Factor: 2.56158335727

commit_after_append_of_many_lines [16.113877415657043, 42.500172019004822]
- Factor: 2.63748885031

commit_after_append_of_one_long_line [15.808947086334229, 38.482058048248291]
- Factor: 2.43419487953

Anonymous said...

personally I think that it is a huge impact for develpers that we have to compare that many dvcs - I really would like to save the time and NOT compare hg, bzr, darcs, git... what a horrible overhead. why must everybody make his own system? I hate it. Why not ONE system that is good and does evything? How many lifes do you spend with your computer? I want to have free time. I really think, open source should make things easier not more complicated, so please get together and stop spliting up more and more tools.

zooplah said...

I'm agreeing with the most recent poster here. There are way too many distributed VCS's nowadays. In client/server systems, we basically only have Subversion (or CVS for older repositories that haven't upgraded (though I've considered downgrading to CVS to make a stand against endless VCS's, returning to a time when CVS was synonymous with version control)).

Now, my actual experience has been less than good. I wanted to try the latest code of some project to see if a problem had been fixed, but they switched from CVS to Mercurial (and didn't have a CVS gateway). Where in CVS and SVN, you just do a cvs|svn checkout and things start downloading, I did an hg clone and it just hung there forever. I assume that it was downloading the complete repository instead of just the latest files that I wanted (as there were no source files downloaded) and it would probably take days to fully download. I have a feeling the popularity of DVCS's is going to be the death of casual viewers and weekend coders sending patches.

arnebab said...

zooplah: The Mercurial devs are working on clean clones with only partial history (shallow clones), and you can already use the shallow clone extension to archieve that:
- http://www.selenic.com/mercurial/wiki/index.cgi/ShallowClone

mamelouk said...

zooplah: in bazaar you can just "checkout" a projet instead of just branching it

Wade Williams said...

While to some people it's all about speed, that's not always the whole story.

I really have enjoyed working with hg, but when I had to work with a group of developers that was centralized-model focused, I found hg fell down. There were all sorts of issues trying to run hg with a centralized repository.

bzr gave the flexibility to use either model. So despite the slower speed, the flexibility won out.

Note, I am NOT saying that bzr is "better" than hg. Both are excellent tools. In fact, hg would probably be my choice for working in a pure distributed environment. But when there's an element of centralization required, I think the extra flexibility of bzr wins out.

Ringo De Smet said...

Wade, I agree with you that it is not all about speed. I do say however that I find bzr better than hg in terms of correctness in what I expect the tool to do for me, instead of the other way around. I automated a number of working scenarios as shUnit tests. bzr gave me the largest set of successes.

See the following BitBucket repo for the implementation of these tests:
http://bitbucket.org/ringods/scm-correctness/

Feel free to add test scenarios or complete some of the non-implemented ones.

Post a Comment