Обновление 2011-10-21: почти все из этой заметки уже исправлено – см. «Вышел Fossil 1.20».

Опять про Fossil, но теперь про плохую сторону, одна из которых открылась для меня только вчера.

Во-первых, Fossil не добавляет в репозитории символьные ссылки – он следует по ним и добавляет файлы. У меня в репозиториях лежат скомпилированные фреймворки, а они очень хорошо пользуются ссылками и устроены вот так:

sparkle-framework.png

Как видите, Headers, Resources вверху ссылаются на A/Headers и A/Resources, плюс еще Versions/Current ссылается на A/, таким образом, если по всем ссылкам пойти, Versions/A/Sparkle.h и остальные файлы будут добавлены три раза. Соответственно, если добавить фреймворк в Fossil и сделать checkout, мы получим вот такую картину:

sparkle-framework-fossil.png

Хотя это и валидный фреймворк, это ужасно (заметьте, что также пропал исполняемый атрибут у бинарника – Fossil не запоминает атрибуты).

Если мне будет не лень, я может быть когда-нибудь модифицирую Fossil, чтобы он знал о ссылках и помнил атрибуты. Но тогда придется немного изменить формат репозитория, который «is kept simple so that it can endure in useful form for decades or centuries».

Во-вторых – то, что я вчера обнаружил – низкая производительность при работе с кучей файлов в куче вложенных папок.

Сейчас у меня вот такой проект:

$ tree
...
251 directories, 2178 files

Если пройтись по ссылкам, что делает Fossil, то файлов окажется еще больше:

$ tree -l
...
317 directories, 2869 files

(Для любопытствующих – нет, это не все мои файлы; в основном это исходники фреймворков, используемых в проекте. Если вы не храните их в репозитории проекта, то совершаете ошибку – через 5 лет, захотев скомпилировать код, вы задолбаетесь искать все зависимости по интернету. Горьким опытом научен.)

Дальше буду сравнивать с эталоном – Git. Добавляем файлы в чистый репозиторий:

Fossil:

$ time fossil add .
...
real    0m0.971s
user    0m0.471s
sys 0m0.191s

$ time fossil commit -m "Initial commit"
New_Version: f71b1fc6112801d8082e0c0e609d455e6a9f899a

real    0m8.639s
user    0m5.788s
sys 0m0.943s

Git:

$ time git add .

real    0m3.780s
user    0m1.430s
sys 0m0.961s

$ time git commit -m "Initial commit"
...
real    0m1.096s
user    0m0.342s
sys 0m0.289s

Итого, Fossil ~ 9.5 секунд, Git ~ 4.7 секунды. Время первого коммита не так важно – как много первых коммитов вы делаете во время работы с проектом? :)

Посмотрим, есть ли изменения (их нет):

$ time fossil changes

real    0m0.824s
user    0m0.405s
sys 0m0.183s


$ time git status
# On branch master
nothing to commit (working directory clean)

real    0m0.307s
user    0m0.038s
sys 0m0.050s

Три десятых секунды у Git против 0.8 у Fossil. Вот эта разница уже существенна. Секундная задержка, хоть и кажется маленькой, раздражает. С горячим кэшем, конечно, задержка уже меньше (если повторить те же операции):

$ time fossil changes

real    0m0.410s
user    0m0.258s
sys 0m0.151s

$ time git status
# On branch master
nothing to commit (working directory clean)

real    0m0.084s
user    0m0.036s
sys 0m0.043s

Но разница очевидна: полсекунды против «ничего».

Попробуем изменить файлик

$ echo "// Test" >> main.m

и сделать коммит:

$ time fossil commit -m "Edited main.m"
New_Version: 34721d9dca256ad55bbde601e1844c1fd874c8d0

real    0m2.987s
user    0m2.285s
sys 0m0.581s

$ time git commit -a -m "Edited main.m"
[master 5de00cc] Edited main.m
 1 files changed, 1 insertions(+), 0 deletions(-)

real    0m0.193s
user    0m0.049s
sys 0m0.089s

Три секунды у Fossil против двух десятых у Git!

Если вы не верите, что трехсекундный коммит может раздражать в ходе разработки, то подумайте еще раз: я оторвался от программирования, чтобы сравнить производительность Fossil и Git и написать эту заметку. Стал бы я это делать, если бы меня это не раздражало? :) (Да и Git, собственно, зародился частично из-за того, что monotone, который, кстати тоже построен на SQLite, оказался медленным).

Я не знаю, почему именно этот проект заставил меня задуматься об отказе от Fossil. В SQLite, который трекается в Fossil, тоже немало файлов, и Fossil работает с ним быстрее. Возможно дело в большом количестве вложенных директорий, а может в том, что Fossil читает ссылки… Учитывайте так же, что я программирую на MacBook Air (который большой тормоз, а не компьютер – ATA диск с 4200 об/мин).

Что делать? Для Git Торвальдс написал крутейшую оптимизированную версию SHA-1, а Fossil использует «reference» версию. Я заменил эту версию имплементацией из Git – производительность выросла (fossil changes работает в два раза быстрее), но все равно недостаточно. На самом деле, бенчмарки вверху как раз сделаны с помощью этой «оптимизированной» версии.

Надо заметить, что Git устроен по-другому в плане коммитов: сначала надо добавлять в индекс изменившийся файл, а потом делать коммит, но даже «git add .» картины не меняет – Git находит изменившиеся файлы и делает коммит намного быстрее.

Еще одно важное отличие – Fossil делает дельта + deflate-компрессию над всеми изменившимися файлами, а git просто делает deflate; пакование дельтой у него – отдельный процесс. Но не думаю, что изменение одной строчки одного маленького файла на примере вверху, и, соответственно, дельта-сжатие его, сильно влияет на общую картину.

Посмотрю, что еще можно сделать, но производительность Fossil разочаровывает.

Кстати, про Mercial. Когда я пользовался Git, у меня в .bash_profile была интересная штука – при заходе в директории вызывалось:

git branch --no-color 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/(\1)/'

и после имени директории показывался бранч:

~/Projects/Foo(master) $

Когда я перешел на Mercurial, я заменил эту строчку на показ бранча hg:

hg id -bn 2> /dev/null | sed -e 's/\(.*\) \(.*\)/(\2:\1)/'

Потом забыл про это и думал – какого фига у меня bash так долго в папки заходит? hg же на питоне сделан. У меня Python запускается при холодном кэше полторы секунды. Неудивительно. Стоит ли говорить, что коммиты в hg меня тоже раздражали? :)