Системный администратор Linux
0,0
рейтинг
15 мая 2014 в 19:19

Разработка → Как ускорить программу на Go recovery mode

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

Go — язык, компилируемый в нативный код, а посему, очевидно, должен быть быстрым. Однако, к сожалению, на данный момент это далеко не всегда соответствует действительности.
В моём же случае, Go проиграл PHP (ну, на самом деле модулю PHP на C, однако результат всё равно удручающий). Если кратко, то при вычислении Whirlpool-хеша Go проигрывал в 3,5-7.5 раз!

Во множестве источников описывается одна и та же причина — «слабый» стандартный компилятор Go (тот, что вызывается через go build). Это полностью верно. Дело в том, что компилятор довольно «молодой» и пока не имеет багажа оптимизаций, какой, например, имеет GCC.
Решение есть — существует вариант компилятора Go — gccgo.
Сборка программы через gccgo с оптимизациями производится так:
go build -compiler gccgo -gccgoflags "-march=native -O3" main.go
В данном случае будет выполнена сборка со всеми доступными оптимизациями и инструкциями на текущем оборудовании.
В целом, достаточно использовать только опцию -O2.

Результаты тестирования для моего случая:
Скрытый текст
# Standart compiler
➜ go build ./dedup.go
➜ time ./dedup > /dev/null

real 0m4.612s
user 0m4.588s
sys 0m0.020s

# gccgo compiler without optimizations
➜ go build -compiler gccgo ./dedup.go
➜ time ./dedup > /dev/null

real 0m2.110s
user 0m2.084s
sys 0m0.024s

# PHP realization
➜ time php hash.php > /dev/null

real 0m0.634s
user 0m0.608s
sys 0m0.024s

# gccgo with optimizations
➜ go build -a -gccgoflags "-march=native -O3" -compiler gccgo ./dedup.go
➜ time ./dedup > /dev/null

real 0m0.534s
user 0m0.512s
sys 0m0.020s


Итого время выполнения программы, собранной gccgo с оптимизациями, оказалось в 4.2-9.2 раза быстрее билда без оптимизаций.
Сводная «табличка»:
Вариант Относительное время
gccgo optimized 85%
PHP 100%
gc v1.2.1 210%
gccgo 350%
gc v1 750%

На этом, в общем-то, всё, спасибо за внимание.

UPD1: Компилятор в go 1.2.1 даёт значительно лучшие результаты (добавлен в таблицу):
Скрытый текст
➜ time ./dedup > /dev/null

real 0m1.550s
user 0m1.528s
sys 0m0.020s

Также, обратите внимание, что есть некоторое различие в итоговой сборке при использовании разных компиляторов для разных платформ или различных линковщиков. Информация здесь: blog.golang.org/gccgo-in-gcc-471
Вкратце, всё будет хорошо, если использовать gccgo >=4.7.1 в паре с линковщиком gold на x86 (32/64 bit). Проблемы же, пожалуй, не сильно страшные, зато появляется больше целевых платформ. Подробности в посте по ссылке выше.
@KawaiDesu
карма
9,0
рейтинг 0,0
Системный администратор Linux
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (25)

  • +1
    I've profiled the «go build» variant of program (same run time with profiling code) and saw, that 94.1% of time program was in

    github.com/jzelinskie/whirlpool.(*whirlpool).transform


    Кастинг в Go медленный. Кошерный способ оптимизации с этом случае — вынос кастинга за пределы цикла.
    • 0
      Это вот здесь:

      // Iterate over all the rounds.
      for r := 1; r >56)] ^
      _C1[byte(K[(i+7)%8]>>48)] ^
      _C2[byte(K[(i+6)%8]>>40)] ^
      _C3[byte(K[(i+5)%8]>>32)] ^
      _C4[byte(K[(i+4)%8]>>24)] ^
      _C5[byte(K[(i+3)%8]>>16)] ^
      _C6[byte(K[(i+2)%8]>>8)] ^
      _C7[byte(K[(i+1)%8])]
      }

      и

      for i := 0; i < 8; i++ {
      L[i] = _C0[byte(state[i%8]>>56)] ^
      _C1[byte(state[(i+7)%8]>>48)] ^
      _C2[byte(state[(i+6)%8]>>40)] ^
      _C3[byte(state[(i+5)%8]>>32)] ^
      _C4[byte(state[(i+4)%8]>>24)] ^
      _C5[byte(state[(i+3)%8]>>16)] ^
      _C6[byte(state[(i+2)%8]>>8)] ^
      _C7[byte(state[(i+1)%8])] ^
      K[i%8]
      }
      • 0
        А, понятно. Тогда я согласен с комментарием mc_dir ниже. Тяжелую часть можно вынести в C, через cgo. Правда это слегка подпортит жизнь тем, кто собирает Go под Windows.
        • 0
          При сборке с оптимизациями особого смысла это более не имеет. Да и запарно выносить, мне кажется.
          • 0
            У gccgo свои заморочки, например goroutines там становятся полноценными потокоми, иными словами теряется вся прелесть Go.
            • 0
              пруф, пожалуйста, в студию.
              • 0
                Цитата из The Go Programming Language Phrasebook:

                The most obvious is that gccgo uses operating system threads to implement goroutines, and will not use segmented stacks in all configurations. This means that creating a goroutine is as expensive as creating a thread in C.


                FAQ также утверждает, что:

                The gccgo compiler implements segmented stacks on Linux only, supported by recent modifications to the gold linker.


                Я не в курсе значит ли это, что и переключение между goroutines кооперативное под gccgo или нет.
  • +1
    Желтовато получается.
    В php hash (whirlpool) реализован на С [тыц], и компилируется со всеми необходимыми оптимизациями (в примере так полагаю эту функцию и тестируют),
    когда в GO whirlpool реализован на самом GO [тыц]
    Если вы уже хотите два языка сравнить, так воаля перепишите whirlpool на чистый PHP и сравните…
    • +2
      Видимо, скромного примечания в скобках оказалось недостаточно. Сейчас подправлю.
    • 0
      Заголовок статьи обещал помочь ускорить Go. Результат статьи — ускорение работы. Автор не пытался возвысить один язык над другим, более того, в итоге код на Go сработал почти со скоростью кода на Си/ПХП. Не увидел здесь желтизны.
      • +1
        Можно написать статью по ускорению С++. Из 2 предложений: «Я собирался с O0, стал собираться с O3! Чудо, стало быстрее!!»
        • 0
          Забавно, но факт — я нашёл только одно упоминание того, что человек собирает свой код через gccgo с флагами оптимизации. Причем, по теме ускорение это не гуглится.
    • 0
      Да, и про сравнение. С самого начала было понятно, что там шустрый Сишный модуль. Тем не менее такой огромный проигрыш не мог не расстроить меня. Т.е. сравнение в принципе проводилось ради того, чтобы выяснить, это whirlpool такой медленный или реализация/язык.
      • 0
        Не всем понятно где какой модуль и что использует. И похоже когда я писал коментарий название у статьи было другое. А за освещение возможности оптимизации компиляции GO спасибо.
        • 0
          Название статьи не трогал.
          Однако, как отметили выше, у gccgo есть свои особенности и не все из них приятные. Позже обновлю статью, попробую, как советовали в группе вконтакте, собрать в версии 1.2/1.3 (судя по всему у меня 1.1 или даже старее).
  • 0
    Некоторые пакеты в Go ещё недостаточно оптимизированы. Например тот же regexp обещали ускорить в 1.3.
    Кстати, какой версии Go использовался?
    • 0
      Он не признаётся наверняка:
      ➜ go version
      go version go1
      ➜ go env
      GOROOT="/usr/lib/go"
      GOBIN=""
      GOARCH=«386»
      GOCHAR=«8»
      GOOS=«linux»
      GOEXE=""
      GOHOSTARCH=«386»
      GOHOSTOS=«linux»
      GOTOOLDIR="/usr/lib/go/pkg/tool/linux_386"
      GOGCCFLAGS="-g -O2 -fPIC -m32 -pthread"
      CGO_ENABLED=«1»
      • +1
        У вас версия 1. Он был заметно ускорен, ЕМНИП, к 1.1.2. Попробуйте последнюю версию 1.2.2.
        • 0
          Попробовал, обновление в посте. gccgo всё ещё лучше.
  • 0
    Всетаки лучше смотреть на картинку в целом, а не на частный случай коня в вакууме.

    benchmarksgame.alioth.debian.org/u64q/benchmark.php?test=all&lang=go&lang2=php&data=u64q
    • 0
      Этот пост — не сравнение производительности Go и PHP.
      • +2
        о как.
        извините, видимо у меня встроенный анализатор текстов сломался.
  • 0
    Были проблемы с gccgo, когда использовал горутины. Решается использованием линкера gold замест ld (в binutils 2.22+ он есть). У меня gccgo собран из сырья, версия 4.10.0 20140719 (experimental). Рекомендую, т.к. более новая. Насчёт флагов я лично не мелочусь:
    go build -compiler gccgo -gccgoflags "-march=native -Ofast -pipe -fomit-frame-pointer -fuse-ld=gold -Wl,--strip-all"
    

    Но большие пакеты так собирать не приходилось. Так что будьте осторожны. Ещё хочу отметить, что в версии gccgo (Ubuntu 4.9.1-0ubuntu1) 4.9.1 стрип (-Wl,--strip-all) портит всё. Сама по себе опция -Ofast считается не рекомендуемой (gentoo вики), а вот насчёт -O3 vs. -O2 скажу прямо — используйте -O3 для узких мест не стесняясь. Мои исследования ассемблерного листинга с этими флагами показали меньший размер и эффективное использование хвостовой рекурсии с флагом -O3. Не мешало бы добавить, что простора для жёсткого «инлайнинга» в моём примере у компилятора не было (что значит, при иных раскладах размер может и увеличиться).
    Помимо всего вышесказанного, если использовать в своих программах пакет syscall, то gccgo (упомянутых выше версий) выдаст ошибку — такие пакеты компилируются только golang-go (или нужно подождать патчей). Ну и разумеется не получиться использовать плюшки go 1.4 (что впрочем не сильно напрягает).
  • 0
    Так-то уже и LLVM с Go потихоньку дружит (щёлк).

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.