henrique-marcomini-m

henrique-marcomini-m

Is using apply a good way to approach dinamic calls?

TL;DR

Is there a more performant and clean way to call methods when I only know which methods (and modules) I will call after a response from the database


This question uses phoenix but is not about phoenix

I have a endpoint (lets call it legacy_reports) that return report files metadata to the user,like name, size, and download link.

My original problem was that when this endpoint was made, it was made for a specif report generator and to refactor everything in the back and in the front would take about 3 or 5 days. So I tweaked a bit the controller and the view of legacy_reports and added the new report to it.

To not suffer from the same problem in the future, I implemented a behaviour to extract the data from the report, and added the following code to the view.

def render("index.json", %{legacy_reports: leagacy_reports, new_reports: new_reports}) do
    %{
      legacy_reports:
        render_many(legacy_reports, LegacyReportView, "legacy_report.json", as: :legacy_report) ++
          render_many(new_reports, LegacyReportView, "new_report.json", as: :new_report)
    }
  end

  def render("show.json", %{legacy_report:legacy_report}) do
    %{legacy_report: render_one(legacy_report, LegacyReportView, "Legact_report.json")}
  end

  def render("legacy_report.json", %{new_report: new_report}) do

    module_atom =
      case new_report.service do
        "SomeService" -> :"Elixir.MyApp.Reports.SomeService"
        _ -> nil
      end

    %{
      some_key: apply(module_atom, :extract_some_key, [new_report]),
      ...
    }
  end

  def render("legacy_report.json", %{legacy_report: legacy_report}) do
    {start_date, end_date} = decode_report_info(legacy_report.report_info)

    %{
      some_key: legacy_report.some_key,
     ...
    }
  end

...

But since this a dynamic way to use apply, I’m afraid it will have some performance drawbacks. Is there a better way to do it without using a whole lot of cases?

Marked As Solved

OvermindDL1

OvermindDL1

Here, a quick benchmark that I used to test various forms of direct and indirect calls that I made in the past that I just ran again on the latest elixir/beam:

╰─➤  mix bench module_calls

Benchmarking Classifier: simple
===============================

Operating System: Linux"
CPU Information: AMD Phenom(tm) II X6 1090T Processor
Number of Available Cores: 6
Available memory: 15.67 GB
Elixir 1.8.1
Erlang 21.2.2

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 2 s
memory time: 2 s
parallel: 1
inputs: types
Estimated total run time: 42 s


Benchmarking Anon-Closure with input types...
Benchmarking Anon-Empty with input types...
Benchmarking Anon-Module with input types...
Benchmarking Direct with input types...
Benchmarking Direct-Apply with input types...
Benchmarking Indirect with input types...
Benchmarking Indirect-Apply with input types...

##### With input types #####
Name                     ips        average  deviation         median         99th %
Direct               23.04 M      0.0434 μs     ±7.60%      0.0420 μs      0.0620 μs
Direct-Apply         22.99 M      0.0435 μs     ±9.82%      0.0420 μs      0.0520 μs
Anon-Module          15.59 M      0.0641 μs   ±139.50%      0.0600 μs       0.100 μs
Anon-Closure         15.25 M      0.0656 μs   ±211.37%      0.0600 μs       0.100 μs
Anon-Empty           13.71 M      0.0729 μs     ±8.78%      0.0690 μs      0.0920 μs
Indirect-Apply       12.78 M      0.0783 μs     ±6.61%      0.0760 μs      0.0900 μs
Indirect             12.59 M      0.0794 μs     ±8.74%      0.0760 μs       0.100 μs

Comparison: 
Direct               23.04 M
Direct-Apply         22.99 M - 1.00x slower
Anon-Module          15.59 M - 1.48x slower
Anon-Closure         15.25 M - 1.51x slower
Anon-Empty           13.71 M - 1.68x slower
Indirect-Apply       12.78 M - 1.80x slower
Indirect             12.59 M - 1.83x slower

Memory usage statistics:

Name              Memory usage
Direct                   280 B
Direct-Apply             280 B - 1.00x memory usage
Anon-Module              280 B - 1.00x memory usage
Anon-Closure             280 B - 1.00x memory usage
Anon-Empty               280 B - 1.00x memory usage
Indirect-Apply           280 B - 1.00x memory usage
Indirect                 280 B - 1.00x memory usage

**All measurements for memory usage were the same**

A ‘direct’ call is when the module is known at compile-time, an indirect call is when the module is not known at compile-time, an anon-module is something like &Mod.blah/2, an anon-closure is like fn _ -> an_external_var end and an anon-empty is like fn _ -> nil end.

What this says is that both SomeMod.blah(1) and apply(SomeMod, :blah, [1]) have the same cost, because they actually do get lowered down to the same beam instructions. bound_mod.blah(1) and apply(bound_mod, :blah, [1]) are the same speed as well, and still faster than half the speed of a direct call. And anonymous calls are about the same as indirect calls too.

So 2 direct calls is barely slower than a single indirect call, so an indirect call you can consider is about equal to 2 direct calls, which are already way plenty fast. So it’s not something to worry about in other words, feel free to use apply. :slight_smile:

Also Liked

axelson

axelson

Scenic Core Team

I do have a quick note about your code. Since :Elixir.MyApp.Reports.SomeService == MyApp.Reports.SomeService I would change:

    module_atom =
      case new_report.service do
        "SomeService" -> :"Elixir.MyApp.Reports.SomeService"
        _ -> nil
      end

to:

    module_atom =
      case new_report.service do
        "SomeService" -> MyApp.Reports.SomeService
        _ -> nil
      end

Actually I would probably extract that whole snippet to a new private function

henrique-marcomini-m

henrique-marcomini-m

Damn, you rock. Thank you a lot.

Where Next?

Popular in Questions Top

pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
kostonstyle
Hi all I want to have a unix time, from the current time plus 1 hour. DateTime.now + 1 hour How to get it in elixir? Thanks
New
hpopp
To simplify some tasks at work, I wrote and published this package yesterday. It’s a simple macro that enables Access behaviour on struct...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics Top

Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 42633 214
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New

We're in Beta

About us Mission Statement