Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve iolist optimization #94

Merged
merged 3 commits into from
Dec 29, 2016
Merged

Conversation

pguillory
Copy link
Collaborator

The new function optimize_iolist combines the old merge_binaries and
simplify_iolist functions. It also improves on them slightly, for instance by
terminating iolists with a binary instead of the empty list, and by more
consistently combining adjacent binaries.

The new function `optimize_iolist` combines the old `merge_binaries` and
`simplify_iolist` functions. It also improves on them slightly, for instance by
terminating iolists with a binary instead of the empty list, and by more
consistently combining adjacent binaries.

["a", "b", ["c", [var]]] => ["abc", var]
[<<1>>, <<2>>] => <<1, 2>>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could make this a full doctest.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good thought. It's tricky to represent nicely as runnable code though, since it works on quoted expressions. That's why UtilsTest uses a macro.

Really, these functions are not something I would expect people to try to re-use. Or I hope that don't. Maybe we should @moduledoc false this whole thing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. You can use standard comments to describe what's going on here for internal consumption.

@@ -60,30 +60,47 @@ defmodule Thrift.Generator.Utils do
end

@doc """
Merge the binaries in an iolist.
Optimize an expression that returns an iolist.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you also write a little more about the optimizations that are applied while they're still fresh in your mind?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

optimize_iolist([a, b | rest])
end
def optimize_iolist([a, b]) do
[{:|, [], [a, b]}]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand this return result. What's going on in this case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A list is represented in memory as a linked list of cons cells. A cons cell is a pair of adjacent values [head | tail] where head is the value at that point in the list and tail is either the next cons cell or the empty list. For example, a list with three elements looks like:

["a" | ["b" | ["c" | []]]]

An iolist is also composed out of cons cells but one less constraint: the tail can also be a binary. This means you can save one cons when constructing an iolist. The same list above could be converted to the iolist:

["a" | ["b" | "c"]]

It uses slightly less memory. It's also slightly faster to traverse, as there's one less pointer to follow. This optimization is performed by the above optimize_iolist clause.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I see now. I wasn't connecting with the fact that this is entirely based on quoted expressions.

@@ -0,0 +1,23 @@
defmodule Thrift.Generator.UtilsTest do
use ThriftTestCase
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this just use ExUnit.Case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup.

# Optimize a quoted expression that returns an iolist. The high level strategy
# is to flatten lists and combine adjacent binaries.
#
def optimize_iolist(expr=[[a | b] | c]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

debug_optimization(expr, "flatten list")
optimize_iolist([a, b | c])
end
def optimize_iolist(expr=[a, [b | c] | d]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

debug_optimization(expr, "flatten list")
optimize_iolist([a, b, c | d])
end
def optimize_iolist(expr=[[] | a]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

debug_optimization(expr, "discard empty list")
optimize_iolist(a)
end
def optimize_iolist(expr=[a, [] | b]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

end
def merge_binaries([a, b | rest]) when is_list(b) do
merge_binaries([a] ++ b ++ rest)
def optimize_iolist(expr=[a, b, [] | c]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

end
def merge_binaries([{:<<>>, [], a}, {:<<>>, [], b} | rest]) do
merge_binaries([{:<<>>, [], a ++ b} | rest])
def optimize_iolist(expr=[{:|, _, a} | b]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

end
def merge_binaries([a | rest]) do
[a] ++ merge_binaries(rest)
def optimize_iolist(expr=[a, {:|, _, b} | c]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

end
def merge_binaries(a) do
def optimize_iolist(expr=[{:<<>>, opts, a}, {:<<>>, _, b} | rest]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

debug_optimization(expr, "merge binary expressions")
optimize_iolist([{:<<>>, opts, a ++ b} | rest])
end
def optimize_iolist(expr=[a, {:<<>>, opts, b} | rest]) when is_binary(a) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

debug_optimization(expr, "merge binary and binary expression")
optimize_iolist([{:<<>>, opts, [a | b]} | rest])
end
def optimize_iolist(expr=[{:<<>>, opts, a}, b | rest]) when is_binary(b) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

debug_optimization(expr, "merge binary expression and binary")
optimize_iolist([{:<<>>, opts, a ++ [b]} | rest])
end
def optimize_iolist(expr=[a, b | rest]) when is_binary(a) and is_binary(b) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

debug_optimization(expr, "merge binaries")
optimize_iolist([a <> b | rest])
end
def optimize_iolist(expr=[a, b]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

debug_optimization(expr, "final cons cell")
[{:|, [], [a, b]}]
end
def optimize_iolist(expr=[a]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.


def simplify_iolist([{:<<>>, _, _} = binary]) do
binary
def optimize_iolist(expr=[a | rest]) do
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spaces around operators most of the time, but not here.

@pguillory pguillory merged commit ceceea9 into pinterest:thrift_tng Dec 29, 2016
@pguillory pguillory deleted the iolist branch December 29, 2016 17:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants