В воскресение я решил заняться тем, что портирую одну медленно работающую, но тем не менее выжирающую одно ядро процессора на 100% на 30 секунд утилиту с python2.6 на go 1.7.2. Ниже - цитаты твитов, возможно с небольшими правками.

Как я зря потратил воскресение на Go

Редко ругаюсь на игры, но вот Pokemon Go портят мне поиск манов по map в Go :D

Нет функционального сахарку

Кстати, как я понял функционального сахарку типа map/filter там нет, ибо не принято, то есть никаких тебе

new_list = map(myfunc, list)

Навязывание layout проекта даже тогда, когда коду ещё далеко до того, чтобы называться проектом

Питон - удобный.

Go - неудобный.

Ну вот зачем он мне мозги ебёт, если я хочу две утилитки написать, которые могут лежать и запускаться в одной папке?

Отсутствие REPL

Раз штатного REPL нет, хочу хотя бы такие мелочи отлаживать в отдельном маленьком файлике mytst.go который лежит рядом с написуемой программой.

Ладно, он просто не даёт таких богатых возможностей изучения методом тыка как python, благодаря REPL, ретроспективе или как там, всё такое.

Delayed refactoring is error

А самый большой бугурт у меня в том, что половину кода взять и закомментить нельзя, потому что не будет ничего стартовать, потому что imported and not used! ERROR! ИДИ КОММЕНТИРУЙ НАЗАД! WOW!

Ложные надежды на выигрыш в производительности от факта компилируемости языка

Не дай бог эта сучка не будет работать быстрее в миллион раз, лол.

На самом деле хз. Есть маловероятно параллелящийся код, у которого строковые операции - боттлнек.

А раз не параллелится - хз как хотя бы по процам нагрузку раскинуть.

Первые потуги тяжелы, но это нормально

я склоняюсь к тому, что у меня такое же ощущение сейчас исключительно от недостатка практики -> привычки.

Функции с несколькими аргументами не разворачивают tuple/list при возврате

И ещё бугурт: есть slice из двух строк:

slice = ['a', 'b']

Нельзя просто так взять и вернуть его, если функция возвращает две строки. В принципе это логично, писал бы явно что

func x() [2]string {
    ...
}

и было бы всё нормально.

А так результат обязательно, блин надо присвоить в переменную, а потом вернуть [0] и [1]

Строгие stdlib

И бесит сука, что в какой язык не сунусь с этой задачкой, везде приходится поверх ещё свой urllib писать, т.к. куча говна не по RFC работает

Слабая библиотека для работы со строками

По большей части бесит отсутствие

str.find(c, n=0)

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

Традиционное (привычно) > (удобно и просто)

Мб тут на си проще было бы переписать, там я как рыба в воде.

Спустя 3 часа практики

Go - Е.Б.А.Н.У.Т.Ы.Й.

БАНАЛЬНЕЙШИЕ МАТЕМАТИЧЕСКИЕ ОПЕРАЦИИ НАДО ПИСАТЬ САМОМУ

Min()/Max() for Integer

Because the math package is not polymorphic, you have to write them yourself.

Гоферы, нах так жить?

func main() {
    var x, y, z int
    x, y = 10, 15
    z = int(math.Min(float64(x), float64(y)))
    fmt.Println(z)
}

Теорема эскобара.go

func intMin(a int, b int) int {
    if a < b {
        return a
    }
    return b
}

Есть и хорошие вещи - деплоить надо один сраный бинарик

Вот по причине легкости деплоя и хочу его заюзать. Горутины вот жалко не заюзать в этом конкретно случае.

Но в деплое есть и неудобства

А вот для дебага in exactly same environment неудобно, кстати это.

Опять про шаги в сторону и строгий layout

Вот что корёжит - это layout, который диктует где у меня что должно лежать. Это прямо говорит “не дели код на модули, ебашь всё в один файл!”

Спустя 4 часа практики

RT @comrade_wolgast: Вдруг, как в сказке, скрипнула дверь. Я уже, блядь, нахуй, как зверь.

@kotchrpk хуерогая типизация :(

Slices again

Ok, remove element from slice a bit weird too:

s = append(s[:item], s[item+1:])

I hope it’s just pointer magic under hood (no new allocs)

No default values!

Damn, there is no getenv with default values! Instead of: os.Getenv(“var”, “default_value”) you should write a loooooot of code. for example:

    home := os.Getenv("HOMES")
    if len(home) == 0 {
        home = "PIDOR"
    }
    fmt.Println(home)

Strings routine

Also empty string != false! So you should explicitly call len().

Спустя 5 часов практики

Aaaaa, weird Go, weird suka blyad

там не было явно сказано что мне надо будет половину питоновой stdlib руками писать!

Верные мысли которые не хватило ума проверить сразу

Но вообще сейчас понял, что конкретно в Go мою задачу проще будет решить с помощью map, который dict, а не с помощью множеств, а в value кэшировать разобранный URL.

Немного неудобно^Wнепривычно -> неудобно, всё же

Also, damn, I can’t get rid of feeling that I’m programming in notepad++ even with atom package “go-plus”.

Верю, дело в том, что (просто && очевидно) != (просто && привычно).

Возникающие вопросы и полезные советы

А какой в Go самый быстрый способ получить y на множествах X с ≈50000 элементов?

X = { 1, 2, 3, 4, 5, ... }
y = 3 in x

я go не знаю вообще. 2 года назад прочитал Go Programming Language, но без практики оно всё забылось в хлам. У меня пока уровень “лишь бы хоть как-то заработало, но немного беспокоюсь чтобы дичь медленную совсем не писать”.

До бенчмарков пока далеко :)

Первые сравнения

Окей, заимплементил на Go 1 функцию из 15, которые по очереди проходятся по множеству в 50000 строк.

Python - 19 сек. Go - 0.8 сек.

Кто проебал инфраструктуру для хранения статистики тестов производительности - тот долбоёб (я)

Так-то там под капотом код довольно разный получился, в Go пришлось сильно изъёбываться чтобы оно работало и по факту это разные алгоритмы.

Но вообще странно, что у кода на Python 2.6 случилась такая сильная деградация. Полгода назад на 25000 было что-то около 2 сек.

Заменил time go run script.go на

go build script.go
time ./script

0.27 сек. Аааа.

Что изменилось - увеличился объём данных, которые кормятся скрипту.

Я походу плохо что-то написал давным давно, с очень большим большим O. :)

И снова про строковые операции в stdlib

во, кстати, всплыло.

isUpper / isLower не хватает питоньих!

Меряем дальше

Заимплементил 2/15:

  • Python 2: 18.278s
  • Go 1.4.2: 0.251s

Откладываю красивости и правильности на потом до последнего. Наверное не зря.

Я не знаю как и некогда разбираться. Когда в продакшн потащу, тогда и буду об этом думать.

Я к тому что код пишется для CentOS 6, но на маке, который то, блядь, ещё болото говна в плане порядка в пакетных менеджерах.

Ну, вообще первичная цель - вообще хоть на чём написать шоб было быстро. Вторичная - поныть какая боль этот конпелируемый Go.

Альтернативы

А так - хотел бы сравнивать с Cython/Pypy если бы они у меня работали под макосью :D (я рукожоп).

Dообще основная часть проекта как раз на Ansi C :D

Код который сейчас на python - так, обслуживающая утилитка готовящая данные

Используемый мной сейчас python:

Python 2.7.10 (default, Oct 23 2015, 19:19:21)
[GCC 4.2.1 Compatible Apple LLVM 7.0.0 (clang-700.0.59.5)] on darwin

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

Замер оригинала + pypy

К слову, на Linux с 15/15:

  • python2.6: 20.7s
  • pypy: 6.3s

Люди обезумели и предлагают всякую хипстерятину

вот от elixir мне толку точно не будет. У меня однопоточка которую не распараллелить никак.

а вообще, есть какая-то принципиальная разница с Go кроме того что мне придётся:

  1. Устанавливать
  2. Изучать воркфлоу работы с ним

Больше замеров богу замеров!

2/15:

  • Linux Python 2.6: 18.1s
  • Linux PyPy 2.7.3: 2.5s
  • OS X Python 2.7: 18.3s
  • OS X Go 1.4.2: 0.251s

Моя проблема

Там много повторных операций с множествами и да, O довольно высок.

И мы получили правильный ответ! Именно так!

@strangeqargo: все равно надо на код смотреть, может у тебя контейнеры неоптимальные в питоне

угадай что я делаю, пока переписываю с нуля на Go и почему я процитировал @strangeqargo ?:)

использую контейнеры несколько более подходящие под ситуацию и избавляюсь от лишнего копирования. Вместо set с url юзаю

map[url_string] = url.Parse(url_string)

изначально ест больше памяти, зато можно быстро дёргать Host.

а так - давно Go подучить хотелось, кек.

не, строчки маленькие, до войны и мира далеко - тупо URL, а то что я делаю - всякая магия с логикой, RFC и некоторыми вещами, ведущими себя не по RFC.

А можно вообще не программируя проблему решить!

А по уму - уйти нахер от идеи “всё работает из коробки на одной машине” которая нужна маркетингу и вообще никаких проблем!

(и никаких клиентов, лол)

ну, не место всякой хуйне на хостах которые крайне latency sensitive, при этом выполняют такой mission critical, что задержка с ответом может к штрафу привести.

Так что тут либо переходить в максимальный idle, либо схему работы менять.

@alexanius: А можете поставлять кастомерам и окружение и оборудование, тогда и проблем никаких не будет! :)

окружение и так поставляю. С машинами сложнее, их по инету не отправить, x86_64 куда быстрее самому купить :)

Начал играться с бенчмарками и бенчмарки в Go хороши

Забавно, но для

s = “string”

вариант:

s[len(s)-1] == “g”

быстрее аж в 8 раз, чем

strings.HasSuffix(s, “g”)

Да понятное дело, что один символ сравнить проще чем набор символов :) Но удивил порядок разницы, я думал будет что-то около 1.5-3 раз.

Бля я виннипух с горшком говна, что я делаю вообще.

Но к слову, спасибо @robert_egorov за ссылку на гайд по бенчмаркингу в Go, очень удобно.

https://t.co/PmPeQo7U6f

Спустя 6 часов практики

Ебучие процессоры со своими тиками.

Как будто лезть питонщику в Go - это что-то плохое. Я и на Python писал в сиподобном стиле :D

Ещё забавно, пока портирую код в качестве docstring у фукнции торчит её реализация на питоне.

Три часа до возвращения дамы сердца домой, а я вроде разогнался и уже 6/15 кода портировал.

Всё стало плохо и процесс встал раком

При портировании 8/15 функций всплыли баги в оригинале, теперь не знаю как тестировать сравнивая вывод обоих утилит.

А питоньевая функция на 23 строчки превратилась в 4 функции на 130 строк.

Спустя 7 часов практики

@mr_the чо мы за уёбки? в выходные какую-то ебанину разгоняем шоб пошустрее была.

Строгий urllib

Надо обработать URL вида: https://example.com/something/%

golang на разборе валится:

invalid URL escape “%”

Вот какое url.Parse() дело до того, что идёт после процентов? Не можешь понять - считай простой строкой, как по мне.

Этот URL вообще валидный?

это URL из реестра Роскомнадзора, который должен блокироваться для Ревизора. Про то, что по ссылке - знать не знаю и не хочу, мне дела нет до того, валидный он или нет. Ревизор запрос пошлёт, я должен заблокировать. Вот такие дела.

Тот же CURL такие запросы может запросто посылать:

GET /post/100896000862/% HTTP/1.1\r\n

В итоге

Отличный выходной: поизучал Go, написал более 300 строчек кода, потом поправил в оригинальном питоньем коде 4 строчки и всё работает за 3s.

Что обидно - знания Go скорее всего снова пропадут ибо практики постоянной нет.

  • python 2.6, old code: 21s
  • python 2.6, 4 lines fixed: 3.3s
  • pypy2.7, old code: 5.3s
  • pypy2.7, 4 lines fixed: 3.6s

Ну охуеть теперь! Надо было сразу профилировать, оказывается в какой-то момент я всё запорол :c

А вообще - нехуй было тесты производительности проёбывать.

diff: https://t.co/4qWvFSuLSR

Думаю на Go получится это дело разогнать где-то до 1.3 сек. Но зачем, когда и так сойдёт?

Итоговые ощущения от Go

Много кода. Очень много кода.

Ради маленькой утилиты, которая является 1/300й проекта создавать отдельный пакет, курить layout’ы Go - муторновато.

хотел получить просто:

  • utilname.go
  • utilname_lib.go
  • utilname_lib_tests.go

Как по мне Go место в вебе, бэкенде, БД, мониторингах итд, а не в утилитах.

Так что всё это было просто развлечение с потенциальной пользой.