Skip to content

Add missing typespecs and fix error in docs #7259

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

Merged
merged 1 commit into from
Jan 26, 2018

Conversation

devonestes
Copy link
Contributor

I tried to create a type for options and to move the documentation
around which options are available up to a typedoc so that was clear
that this is a known type for this module, but it ended up seeming more
complicated than it was worth, so I went with the more general type of
keyword instead.

I also did a small optimization of making the default options a module
attribute since that keyword list is a constant, so there's no need to
allocate a new keyword list every time that function is called.

@josevalim
Copy link
Member

josevalim commented Jan 26, 2018 via email

I tried to create a type for `options` and to move the documentation
around which options are available up to a typedoc so that was clear
that this is a known type for this module, but it ended up seeming more
complicated than it was worth, so I went with the more general type of
`keyword` instead.

I also did a small optimization of making the default options a module
attribute since that keyword list is a constant, so there's no need to
allocate a new keyword list every time that function is called.
@devonestes devonestes force-pushed the document-io-ansi-docs branch from 1a9aa86 to ef16ed8 Compare January 26, 2018 10:12
@devonestes
Copy link
Contributor Author

I could have sworn I did some benchmark on something where I extracted a module attribute and it was faster, and I assumed this was because of not having to allocate a copy, but I guess I was wrong or there was something else going on!

So, you're saying if you have a reference to a module attribute in the body of a function, that copies the value stored in that module attribute the same way as if it was returned from another function call? So this:

@numbers [1, 2, 3]

def count do
  Enum.each(@numbers, fn number -> IO.puts number end)
end

copies the same way as this:

def numbers, do: [1, 2, 3]

def count do
  Enum.each(numbers(), fn number -> IO.puts number end)
end

@michalmuskala
Copy link
Member

In both cases there's no copy (since data is immutable). When a module is loaded a special memory area for that module is allocated where all literals are stored. This data lives outside of processes so it's also never traversed during GC. In OTP 20 literals like that are not even copied when sent between processes. The cost is that when module is unloaded, every process heap is traversed to find literals from that module to properly copy them into the process heap so they become normal data like everything else.

This can be seen in the assembly for the module:

defmodule Test do
  @numbers [1, 2, 3]

  def count_attribute do
    Enum.each(@numbers, fn number -> IO.puts number end)
  end

  def count_inline do
    Enum.each([1, 2, 3], fn number -> IO.puts number end)
  end

  def numbers, do: [1, 2, 3]

  def count_call do
    Enum.each(numbers(), fn number -> IO.puts number end)
  end
end

First the count_attribute function:

{function, count_attribute, 0, 10}.
  {label,9}.
    {line,[{location,"test.ex",4}]}.
    {func_info,{atom,'Elixir.Test'},{atom,count_attribute},0}.
  {label,10}.
    {make_fun2,{f,22},0,0,0}.
    {move,{x,0},{x,1}}.
    {move,{literal,[1,2,3]},{x,0}}.
    {line,[{location,"test.ex",5}]}.
    {call_ext_only,2,{extfunc,'Elixir.Enum',each,2}}.

The move instruction has a "literal" argument, this means the whole operation is just writing the address of this literal data into the x0 register.

The count_inline function is exactly the same:

{function, count_inline, 0, 14}.
  {label,13}.
    {line,[{location,"test.ex",8}]}.
    {func_info,{atom,'Elixir.Test'},{atom,count_inline},0}.
  {label,14}.
    {make_fun2,{f,22},0,0,0}.
    {move,{x,0},{x,1}}.
    {move,{literal,[1,2,3]},{x,0}}.
    {line,[{location,"test.ex",9}]}.
    {call_ext_only,2,{extfunc,'Elixir.Enum',each,2}}.

Finally, the count_call function is a bit different since it has to call the numbers function to get the data:

{function, numbers, 0, 16}.
  {label,15}.
    {line,[{location,"test.ex",12}]}.
    {func_info,{atom,'Elixir.Test'},{atom,numbers},0}.
  {label,16}.
    {move,{literal,[1,2,3]},{x,0}}.
    return.

{function, count_call, 0, 12}.
  {label,11}.
    {line,[{location,"test.ex",14}]}.
    {func_info,{atom,'Elixir.Test'},{atom,count_call},0}.
  {label,12}.
    {allocate_zero,1,0}.
    {line,[{location,"test.ex",15}]}.
    {call,0,{f,16}}.
    {move,{x,0},{y,0}}.
    {make_fun2,{f,24},0,0,0}.
    {move,{x,0},{x,1}}.
    {move,{y,0},{x,0}}.
    {line,[{location,"test.ex",15}]}.
    {call_ext_last,2,{extfunc,'Elixir.Enum',each,2},1}.

The overhead you're seeing comes from calling the function - there's some additional bookkeeping the VM needs to do (the costliest is allocating the stack frame) and some register shuffling. The data itself, though, is produced exactly the same way with the move instruction with a literal argument in the numbers function.

@devonestes
Copy link
Contributor Author

Aha! That makes sense as to why there was a slight slowdown with the function version. Thanks for the wonderful explanation @michalmuskala!

@josevalim josevalim merged commit 7e76ece into elixir-lang:master Jan 26, 2018
@josevalim
Copy link
Member

❤️ 💚 💙 💛 💜

@devonestes devonestes deleted the document-io-ansi-docs branch February 14, 2018 14:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants