📜 ⬆️ ⬇️

Strings up to 23 characters in Ruby are processed 1.92 times faster

An interesting fact: in Ruby 1.9.3 with a 64-bit interpreter, processing lines of 23 or less characters is almost twice as fast as lines of 24 or more characters. In other words, this Ruby code:

str = "1234567890123456789012" + "x" 

... will be processed 1.92 times faster than this:

 str = "12345678901234567890123" + "x" 

For a 32-bit Ruby interpreter, the performance limit is around 11/12 characters .
')
Of course, it's pretty silly to study your code and reduce all lines to 11 or 23 characters. The difference in performance appears only on hundreds of thousands of lines. However, those wishing to delve into the insides of the wonderful Ruby language may be wondering why this is happening.

The performance difference can be seen using a simple benchmark:

 require 'benchmark' ITERATIONS = 1000000 def run(str, bench) bench.report("#{str.length + 1} chars") do ITERATIONS.times do new_string = str + 'x' end end end 

This is what the result is on strings of different lengths.
  user system total real
 21 chars 0.250000 0.000000 0.250000 (0.247459)
 22 chars 0.250000 0.000000 0.250000 (0.246954)
 23 chars 0.250000 0.000000 0.250000 (0.248440)
 24 chars 0.480000 0.000000 0.480000 (0.478391)
 25 chars 0.480000 0.000000 0.480000 (0.479662)
 26 chars 0.480000 0.000000 0.480000 (0.481211)
 27 chars 0.490000 0.000000 0.490000 (0.490404) 

In the tablet - a little more data, but the trend is clear.


The creation time of 1 million lines (ms), depending on the length of the string (characters).

Add that focus only works with the Ruby 1.9.3 interpreter, but not 1.8.

To understand this, Ruby-developer Pat Shaughnessy (Pat Shaughnessy) studied a handbook on the inner workings of Ruby Hacking Guide interpreters, including Chapter 2 , which deals with basic Ruby data types, including strings. After that, he decided to delve into the source code ruby.h (description of data types) and string.c (implementation of strings). In code C, and there was a solution.

It’s all about malloc , the standard C function that does dynamic memory allocation. In fact, this is a rather resource-intensive operation, because you need to find free memory blocks of the right size in the heap, as well as track the release of this block after the operation is completed.

The Ruby interpreter distinguishes between three kinds of strings, which can be called like this:
For all types of strings, a C RString structure is created, but the malloc function applies only to the first type of strings (heaps), but it does not apply to identical strings and embedded strings, thereby saving resources and improving performance. How does this optimization occur? The Ruby interpreter first checks the string for uniqueness: if it is a copy of an existing string, then there is no need to allocate a new memory for it. This structure RString is created the fastest.

 struct RString { long len; char *ptr; VALUE shared; }; 

Next, the interpreter checks the size of the string. If the value is 23 characters or less, then again no memory from the heap is allocated for it and malloc is not called, and the value is embedded directly into the RString structure via char ary[] .

 struct RString { char ary[RSTRING_EMBED_LEN_MAX + 1]; } 

Here lies the clue. A detailed description of the structure of RString looks like this.

 struct RString { struct RBasic basic; union { struct { long len; char *ptr; union { long capa; VALUE shared; } aux; } heap; char ary[RSTRING_EMBED_LEN_MAX + 1]; } as; }; 

Here, the size of the RSTRING_EMBED_LEN_MAX array RSTRING_EMBED_LEN_MAX set as the sum of the len / ptr / capa values, that is, just 24 bytes. Here is a line from ruby.h that defines the value of RSTRING_EMBED_LEN_MAX .

 #define RSTRING_EMBED_LEN_MAX ((int)((sizeof(VALUE)*3)/sizeof(char)-1)) 

On a 64-bit machine, sizeof (VALUE) is 8, which leads to a limit of 23 characters.

This means that, without transferring to memory directly into the RString structure, only 23 characters from the string value can fit. If the string exceeds this value, only then the data is placed in the "heap", for which malloc is called and the corresponding resource-intensive procedures occur. That is why the "long" lines are processed more slowly.

Source: https://habr.com/ru/post/135832/


All Articles