Pull to refresh

Правда ли, что Go быстрее Ruby?

Reading time 3 min
Views 12K
Последние несколько лет я занимаюсь созданием игр для социальных сетей. В качестве back-end применяю связку Ruby + Sinatra + Redis. Redis используется в качестве единственной базы данных. Производительности одной базы Redis часто не хватает, поэтому используется кластер из нескольких баз данных. Более подробно о том, как создавалось решение в виде кластера баз Redis можно прочитать в этой статье.

В последнее время у меня большой интерес вызывает язык программирования Go — слишком много плюшек его использование сулит программисту. Хочется back-end для новых игр написать на нем, но существующая и отлаженная кодовая база на Ruby мешает этому.

Поэтому я решил двигаться небольшими итерациями и начал с переписывания микросервисов применяемых в играх на Go.


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

Строка с частью запроса к Redis преобразуется с помощью SHA-1 в шестнадцатеричное число. Затем находится остаток от деления этого числа на 8. И в зависимости от него данные записываются/читаются из нужной базы.

Исходный вариант на ruby, который я решил переписать на Go.
def node0(text)
	return Integer('0x'+Digest::SHA1.hexdigest(text))%8
end


К сожалению или к счастью такой подход в лоб на Go не сработал. Получаемая 16-ричная строка представляла слишком большое число. В тип int64 это число не помещалось.

Как решить проблему?

У нас 8 баз и 16-ричное число, от которого надо взять остаток от деления на 8.

Ответ.

Первое, что приходит на ум. Нам достаточно знать последний символ 16-ричного числа, чтобы посчитать остаток от деления всего числа на 8.

Мы легко можем переписать решение на ruby с учетом вышесказанного так:
def node1(text)
	s = Digest::SHA1.hexdigest(text)
	return Integer('0x'+s[s.size-1, 1]) % 8
end


То же самое, но на Go:
func node1 (text string) int64 {
	h := sha1.New()
	h.Write([]byte(text))
	sha1_hash := hex.EncodeToString(h.Sum(nil))

	sha1_hash_len := len(sha1_hash)
	last := sha1_hash[sha1_hash_len-1:sha1_hash_len]
	
	value, _ := strconv.ParseInt(last, 16, 64) //int64
	return value % 8
}


Второе, что удачно оказалось

В процессе написания кода, представленного выше, выяснилось, что Go в ходе выполнения SHA-1 представляет число в виде набора десятичных чисел, а потом уже их приводит к 16-ричному виду. Поскольку нам нужен остаток от деления, то мы можем просто взять последнее десятичное число вместо 16-ричного последнего символа и сэкономить на преобразованиях из одной системы счисления в другую.
func node2 (text string) int64 {
	h := sha1.New()
	h.Write([]byte(text))
	mas := h.Sum(nil) // "hello world" -> [42 174 108 53 201 79 207 180 21 219 233 95 64 139 156 233 30 232 70 237]
	return int64(mas[len(mas)-1]) % 8 // Берем последний элемент массива. Это целое десятичное число. И считаем остаток от деления на 8
}


Этот финт позволил еще быстрее вычислять то, что необходимо. Но на Ruby такой трюк выполнить не удалось. Поэтому корректнее сравнивать производительность node1 из Ruby и node1 из Go.

И так какая скорость работы у всего представленного?

Как я решил протестировать:

Посчитать, в какую базу данных положить данные, относящиеся к каждому из миллиона пользователей.

На Ruby:
for i in 1..1000000 do 
	node1("user:"+i.to_s)
end


и на Go:
for i := 1; i <= 1000000; i++ {
	node1("user:"+string(i))	
}


Полный код можно посмотреть на github.

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

Код запускался на версиях Ruby 1.8.7 и 2.1.3. Версия Go была 1.4.2

Значения в секундах. Меньше — лучше.

Ruby 1.8.7
node0 — 22.32
node1 — 17.24
node11 — 16.81

Ruby 2.1.3
node0 — 15.46
node1 — 11.15
node11 — 11.05

Go 1.4.2
node1 — 6.15
node2 — 4.36

Почему такие старые версии Ruby?

Код многих проектов я начал создавать 4-5 лет назад. По мере возможности я переносил его на новые версии Ruby. В частности на 2.1.3. Согласен, что на последней версии Ruby 2.2.2 результаты могут быть лучше, но явно не в два раза.

Выводы

  • 1. Go 1.4.2 быстрее старых версий Ruby в 3 раза. И быстрее современных версий в 2 раза.
  • 2. Ruby неплохо оптимизировали с версии 1.8.7 до 2.1.3. Прирост скорости на 25-30%.
  • 3. Ruby, ты был верным другом и соратником, мы многое прошли вместе, но… я встретил Go. Теперь мне с ним по пути.
Tags:
Hubs:
-7
Comments 38
Comments Comments 38

Articles