Skip to main content

Thinking Like a Compiler

Escape Analysis is an optimization technique whereby a compiler identifies whether a variable, created inside a function, escapes the scope of that function. If the analysis confirms that the variable doesn't escape, then it can be more efficiently allocated on the stack rather than the heap.

For example:

def build_obj
obj = Obj.new()
return obj # variable escapes this function
end

In above snippet, the variable obj escapes the scope of build_obj function since it is returned by the function. So, the obj, quite possibly, is used or can be used by the caller.

caution

This article uses Ruby code only to illustrate the concepts. Whether the snippets undergo JIT compilation (or not) is not the point that this article aims to address.

In another scenario:

def do_something
obj = Obj.new()
print(obj.id)
# obj does not escape
end

The obj is instantiated and then used within the function scope. Most importantly, the instantiated variable doesn't escape.

So, by keeping this technique in mind, what practical optimization can we achieve?

ORMs + Escape Analysis

ORMs abstract away the SQLs from the developers, but it can generate queries that are not always optimal for the use case.

In Active Record pattern, a simple fetch by ID query looks like:

def fetch_document(id)
return Document.find(id)
end

This, most likely, generates and executes a SELECT * query to fetch a row by id and then does some black magic underneath to map the result to the corresponding attributes in the Document object. The query executed is:

SELECT * FROM documents WHERE (documents.id = 1) LIMIT 1;

Retrieving all the fields is justifiable in case of the fetch_document since the Document object escapes the function, and we do not know how the caller will use the returned object. This approach ensures all the fields are available in case if some caller needs them.

In another scenario:

def send_docment(id)
document = Document.find(id)
return post(document.id)
end

The send_document still executes the same broad query as above and then utilizes only the id field from the result. However, in this case, the document doesn't escape the function, which makes it possible for us to fine-tune the query without breaking its callers. So, a more optimal approach would be:

def send_document(id)
document = Document.select(:id).find(id)
return post(document.id)
end

This utilizes a SELECT documents.id FROM documents WHERE documents.id=1 LIMIT 1 query, specifically fetching only the required id field. This is an optimal query for the send_document use case.

Why SELECT * when SELECT x do trick. - Kevin Malone, probably

Where else?

  • GraphQL queries
  • Functions in strongly typed language where the function's contract is already constrained
    export function getPostById(id: number): { title: string, body: string } {
    - const post = client.query(`SELECT * FROM posts p WHERE p.id = ${id} LIMIT 1`)
    - return { title: post.title, body: post.body };
    + const { title, body } = client.query(`SELECT p.title AS title, p.body AS body FROM posts p WHERE p.id = ${id} LIMIT 1`)
    + return { title, body }
    }