Courgette: Дизассемблер как метод уменьшения размера обновлений

Интересующихся попытками сделать жизнь лучше, рекомендую к прочтению интересный документ от инженеров Google на тему минимизации размеров обновлений для браузера Chrome.

Google очень радеет за безопасность своих продуктов. Например, для браузера Chrome - это значит регулярные обновления. А кому охота каждый день качать по десять мегов, даже если там весьма критическое обновление. Поэтому разработчики Chrome делают все, чтобы сократить размер пересылаемых по сети данных.

Применение bsdiff частичное решает проблему, и уже не надо тупо слать архив целиком.

Но теперь к самому интересному. Что происходит, когда найден досадный баг и сделано исправление, например:

Было:

...
if (packet_size < 1024) ...
...

а стало:

...
if (packet_size > 10 && packet_size < 1024) ...
...

Исправили всего одну строчку. Но после компиляции и линковки результирующий файл будет радикально другой: съедет таблица символов, где-то изменится выравнивание, возможно линкер из-за этого изменит размещение сегментов и т.д. Море факторов, из-за которых самое минимальное изменение исходного текста приведет к глобальному изменению результирующего исполняемого файла.

Что делать? А что, если в обновлении посылать модуль не в двоичном виде, а в виде исходного текста? Тогда изменение (diff) будет совсем небольшое.

Конечно, посылать нормальный исходный текст и компилировать его на стороне клиента пока несколько затруднительно. Не у всех установлена Visual Studio.

Инженеры Google придумали технологию Courgette. Суть ее в том, что посылаются изменения на уровне не оригинального исходного текста, а на уровне ассемблера. Для формирования обновления скомпилированный исполняемый файл дизассемблируется. Тоже самое делается для предыдущей версии файла. Затем производится поиск изменений в текстовом виде (например, тем же стандартным diff‘ом). И этот текстовый файл и является, собственно, обновлением. Затем он пересылается пользователю. На стороне пользователя текущий исполняемый файл тоже дизассемблируется, изменяется на основе принятого файла изменений (diff‘а) и снова ассемблируется.

Старая схема с bsdiff‘ом работала так:

сервер:
  изменения = bsdiff(оригинал, обновление)
  передать изменения

клиент:
  принять изменения
  обновление = bspatch(оригинал, изменения)

 сервер:
    ассемблер_оригинал = дизассемблировать(оригинал)
    ассемблер_обновление = дизассемблировать(обновление)
    ассемблер_обновление_кор = коррекция(ассемблер_обновление, ассемблер_оригинал)
    ассемблер_изменения = bsdiff(ассемблер_оригинал, ассемблер_обновление_кор)
    передать ассемблер_изменения

  клиент:
    принять ассемблер_изменения
    ассемблер_оригинал = дизассемблировать(оригинал)
    ассемблер_обновление_кор = bspatch(ассемблер_оригинал, ассемблер_изменения)
    обновление = ассемблировать(ассемблер_обновление_кор)

Для успешной реализации дизассемблер должен располагать таблицей символов со стадии нормальной линковки исполняемого файла. Также на специальном шаге коррекция производится оптимизация ассемблерного кода для минимизации последующих различий с предыдущей версией путем группировки неизмененных частей.

А вот и результаты на примере единичного обновления Chrome’а:

Метод                  Размер обновления
----------------------------------------
Полное обновление      10,385,920
Обновление bsdiff'ом   704,512
Courgette метод        78,848

Неплохо, да?

Вот такой вот хитрый план у разработчиков Chrome. Обещают рассказать особенности реализации, как только все будет готово.

Данный пост является вольным переводом статьи, ссылка на которую дана в начале. Уж больно меня зацепила эта тема.


Оригинальный пост | Disclaimer

Комментарии