Every time an attribute is read inside a function, Elixir takes a snapshot of its current value.
My question is:
In this function:
@money %{amount: 0, currency: :DOGE}
def reset_global_debt do
for human <- Earth.list_humans() do
update_financial_record_for_human(human, %{
"total_amount" => @money.amount,
"base_currency" => @money.currency
})
end
end
do 2 copies of @money get created, since it is “read” twice?
No. The value is basically a static constant, and in fact won’t even take up memory in the process like a normal map, since it will be just a pointer to the shared constant pool.
Yes, the runtime memory is unaffected.
I’m wondering if there are 2 copies during compile time.
The same article says:
Therefore if you read the same attribute multiple times inside multiple functions, you may end-up making multiple copies of it. That’s usually not an issue, but if you are using functions to compute large module attributes, that can slow down compilation.
If you can read erlang you can use mix decompile to turn your compiled module into erlang source. And you will see that whenever an @attribute is used, the litteral value is present in the source.
Now if you cannot read erlang I guess you will recocgnize your own data enough to verify too.
the copies are inside your def. Note that it is not two copies only,
but 2 times the elements of Earth.list_humans()
if you don’t want to create copies, you may want want refactor your code:
@money %{amount: 0, currency: :DOGE}
def reset_global_debt do
money = @money
for human <- Earth.list_humans() do
update_financial_record_for_human(human, %{
"total_amount" => money.amount,
"base_currency" => money.currency
})
end
you may even want to do
@money %{amount: 0, currency: :DOGE}
def reset_global_debt do
amount = @money.amount
currency = @money.currency
for human <- Earth.list_humans() do
update_financial_record_for_human(human, %{
"total_amount" => amount,
"base_currency" => currency
})
end
Sorry, You are right. I was wrong about the part about 2 * number of Earth.list_humans(). It is just two copies.
The rest still applies. as you save from duplicating the content of @money, and access the keys of the map for every element in list_humans at run time.
Yup I understand now.
At compile-time, every module attribute usage is effectively “written out” in full in the code.
So it’s only a concern for very large constants, or if the attribute is accessed in a compile-time loop (e.g. in macros).
Thanks.
A good analogy is what happens when you create a guard with defguard, under the hood it creates two versions of the Macro. one that is executed when is called into the guard context, so it is really limited what you can do there, So no variable assignments is allowed,
so if you want to call two different functions on the same argument, you make two copies of it (similar to what you do in your initial example),
but if the macro is called out of the guard context, is it optimized, so each argument is assigned to a variable, and then it is referred to it, as in my version of your code.
For posterity, doing this isn’t better or worse. It’s exactly the same.
Doing money = @money is better, but only slightly. It might save 0.01 nanoseconds of compile time on a 1980s relic.
I don’t understand your example.
The difference at run time is that you save to access the element (you save two calls per loop).
Why would you want to access the element every time in the loop, when you already know it is amount and it is not going to change?
Store it in the variable, and assign it to your new map key.
def reset_global_debt() do
for human <- Earth.list_humans() do
update_financial_record_for_human(human, %{
"total_amount" => %{amount: 0, currency: :DOGE}.amount,
"base_currency" => %{amount: 0, currency: :DOGE}.currency
})
end
end
And if @money would have 1million entries, you will be creating two copies of a map of one million entries, that are going to be stored in your .beam file,