Is Go True Faster than Ruby?

    The last few years I have been creating games for social networks. As a back-end I use a bunch of Ruby + Sinatra + Redis. Redis is used as the only database. The performance of a single Redis database is often not enough, so a cluster of several databases is used. You can read more about how the solution was created in the form of a cluster of Redis databases in this article .

    Recently, the Go programming language has been of great interest to me - too many goodies for its use promise the programmer. I would like to write a back-end for new games on it, but the existing and debugged code base in Ruby prevents this.

    So I decided to move in small iterations and started by rewriting the microservices used in games on Go.


    All microservices have a connection to Redis databases. Usually 8 Redis bases are used, where the game stores data.

    A string with part of a Redis query is converted using SHA-1 to a hexadecimal number. Then there is the remainder of dividing this number by 8. And depending on it, the data is written / read from the desired database.

    The original version on ruby, which I decided to rewrite on Go.
    def node0(text)
    	return Integer('0x'+Digest::SHA1.hexdigest(text))%8
    end
    


    Unfortunately or fortunately, this Go head-on approach did not work. The resulting hexadecimal string was too large. Int64 did not fit this number.

    How to solve a problem?

    We have 8 bases and a hexadecimal number, from which we must take the remainder of dividing by 8.

    Answer.

    The first thing that comes to mind. It is enough for us to know the last character of the hexadecimal number in order to calculate the remainder of dividing the whole number by 8.

    We can easily rewrite the solution in ruby, taking into account the above, as follows:
    def node1(text)
    	s = Digest::SHA1.hexdigest(text)
    	return Integer('0x'+s[s.size-1, 1]) % 8
    end
    


    Same thing, but on 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
    }
    


    The second thing that turned out to be successful

    In the process of writing the code presented above, it turned out that Go during SHA-1 execution represents a number as a set of decimal numbers, and then it leads to a hexadecimal form. Since we need the remainder of division, we can simply take the last decimal number instead of the hexadecimal last character and save on conversions from one number system to another.
    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
    }
    


    This feint allowed even faster to calculate what is needed. But on Ruby, such a trick failed. Therefore, it is more correct to compare the performance of node1 from Ruby and node1 from Go.

    And so what is the speed of everything presented?

    As I decided to test:

    Calculate in which database to put the data related to each of a million users.

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


    and on Go:
    for i := 1; i <= 1000000; i++ {
    	node1("user:"+string(i))	
    }
    


    The full code can be viewed on github .

    For the purity of the experiment, I ran each script 10 times, and the average values ​​are presented below.

    The code ran on versions Ruby 1.8.7 and 2.1.3. The Go version was 1.4.2

    Values ​​in seconds. Less is better.

    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

    Why are there such old versions of Ruby?

    I started creating code for many projects 4-5 years ago. To the extent possible, I ported it to newer versions of Ruby. In particular, at 2.1.3. I agree that on the latest version of Ruby 2.2.2, the results may be better, but obviously not twice.

    conclusions

    • 1. Go 1.4.2 is 3 times faster than older versions of Ruby. And 2 times faster than modern versions.
    • 2. Ruby optimized well from version 1.8.7 to 2.1.3. Speed ​​increase by 25-30%.
    • 3. Ruby, you were a true friend and ally, we went a lot together, but ... I met Go. Now I'm on his way with him.

    Also popular now: