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 *
whenSELECT 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 }
}