How to determine why elixir compilation w/ a macro is taking 202seconds

I’ve got a macro that wasn’t impacting my project compilation speed much. I added a small feature to it, and now it’s ~50x slower. But at least to look at it, it certainly doesn’t appear as if 50x more work is being done.

Are there any tools I can use to determine why this is the case? After the AST is generated (this part is fast), I’m not sure where things ‘go’, or what tools I might have to identify the source of my slowdown.

One piece about the updated, slow macro code, is that the new macro code calls the existing (compiled fast) macro code. The old code was all basically independent.

I’ve been gutting the macro trying to figure out where things are going awry. Turns out that adding in just this code (formatted Macro.to_string output) is enough to double my project compile time from 4-8s.

def(immediate_controls(pid, slave_id \\ 1)) do
  :ok
end
def(measurements_status(pid, slave_id \\ 1)) do
  :ok
end
def(basic_settings(pid, slave_id \\ 1)) do
  :ok
end
def(nameplate(pid, slave_id \\ 1)) do
  :ok
end
def(inverter_single_phase_float(pid, slave_id \\ 1)) do
  :ok
end
def(common(pid, slave_id \\ 1)) do
  :ok
end

If this is being called a lot of times, then yes, the amount of code you generate will have a direct impact on compilation times. If you can post a snippet, we can help reduce the compilation time, but this may very well be a hint to find another approach that generating many many functions.

In any case, it is also worth mentioning that Elixir v1.5 speeds up how function definitions for large modules, so maybe try out Elixir master and see if that improves the situation.

1 Like

Sorry @josevalim, I usually try and post more detail up front. In this case that’s a little hard and I didn’t want anyone to have to spend too much time following my business domain here. My apologies in advance.

Conceptually, I am making binary TCP calls to read data from a device using the Modbus protocol. I’ve created a macro that let’s you define these values like this:

defmodule ExModbus.SunSpecCommon do
  @doc """
  This simplest possible SunSpec device. Implements Model 1
  """
  use ExModbus
  field :manufacturer,    :string, 40005, 16, :r,  "Well known value registered with SunSpec for compliance"
  field :model,           :string, 40021, 16, :r,  "Manufacturer specific value (32 chars)"
  field :options,         :string, 40037,  8, :r,  "Manufacturer specific value (16 chars)"
  field :version,         :string, 40045,  8, :r,  "Manufacturer specific value (16 chars)"
  field :serial_number,   :string, 40053, 16, :r,  "Manufacturer specific value (32 chars)"
  field :device_address,  :uint16, 40069,  1, :rw, "Modbus device address"

  field_group :common,     [:manufacturer, :model, :options, :version, :serial_number, :device_address]
end

I was following (I hope) Chris’s guidelines in Metaprogramming Elixir, so I have the overall macro split across multiple files.

https://github.com/jeffdeville/ex_modbus/blob/fixing-group-fields/lib/ex_modbus.ex
https://github.com/jeffdeville/ex_modbus/blob/fixing-group-fields/lib/ex_modbus/macros.ex

Each field becomes either 1 method (:r) or 2 (:rw)
Reads request data starting at a memory address, and reading N bytes. Then I convert the bytes to the type
Writes of course do the same thing, but send data down the wire.

This all compiles quickly. It’s the ‘old’ code I mentioned.

But I often need to load groups of fields, and it’s more efficient to load them all at once.

field_group defines a method that will load the given fields all in 1 shot. This is the slow one.

https://github.com/jeffdeville/ex_modbus/blob/fixing-group-fields/lib/ex_modbus/macros.ex#L6

What it does, is define a composite memory block to load all at once. Then it will slice off the bytes for each field, and ask the ‘field’ generated code to deserialize each piece for me. Returning all of the data in a Map.

The small example above isn’t all that painful. But I’m doing this with about ~140 fields spread over 6 field_groups. What’s interesting, is that those 139 field calls are fast. It wasn’t until I added the field_groups that things slowed down.

Possibly Relevant
Because it’s the field_group that caused the speed issue, I don’t think this is relevant. But just in case…

The project that is actually slow to compile is this one, because of this test file:
https://github.com/jeffdeville/ex_sunspec/blob/enable-field-groups/lib/ex_sunspec/test_inverter.ex

defmodule ExSunspec.TestInverter do
  use ExSunspec, start: 40_001,
                 models: [1, 111, 120, 121, 122, 123],
                 model_1_length_65: true
  field :delete_data, :uint16, 212, 1, :rw, "Delete stored ratings of the current inverter by writing 0xFFFF"
  field :store_data, :uint16, 213, 1, :rw, "Rating data of all inverters connected to the Fronius Datamanager are persistently stored by writing 0xFFFF."
  field :active_state_code, :uint16, 214, 1, :r, "Current active state code of inverter - Description can be found in inverter manual"
  field :reset_event_flags, :uint16, 215, 1, :rw, "Write 0xFFFF to reset all event flags and active state code."
  field :model_type, :uint16, 216, 1, :rw, "Type of SunSpec models used for inverter and meter data. Write 1 or 2 and then immediately 6 to acknowledge setting.", enum_map: %{1 => "Floating point", 2 => "Integer & SF"}
end

Each of those model numbers is mapped to an XML file that contains all of the field definitions for SunSpec. So at compile time, the sunspec macros load the xml, and then invoke the ex_modbus macros.

You are generating large function bodies over and over again. You want each generated function to be something like:

def unquote(name)(arg1, arg2, arg3) do
  SomeModule.do_something(unquote(name), arg1, arg2, arg3)
end

I am not sure this will make it faster but it will certainly lead to cleaner code.

Thanks Jose. I fixed the problem and I think I’ve got something interesting to report.

First, I made the changes you suggested. Boiled each method down to 1 line in another module. The compilation time was still 108 seconds.

The fix was to inspect all of the values I was writing into my @docs that were not strings. (atoms, and ints)

I changed my @docs from:

@doc """
#{unquote(desc)}
* Field Type: #{unquote(type)}
* Units: #{unquote(units)}
* Addr: #{unquote(addr)}
* Num Bytes: #{unquote(num_bytes)}
"""

to

@doc """
#{unquote(desc)}
* Field Type: #{unquote(inspect type)}
* Units: #{unquote(inspect units)}
* Addr: #{unquote(inspect addr)}
* Num Bytes: #{unquote(inspect num_bytes)}
"""

and my compilation times sped up 43x

2 Likes

That’s very interesting. Thanks for reporting back. Which Erlang version were you using?

Can you also post what those strings would look like before and after your changes? I am wondering if inspecting is limiting something that would otherwise be very large or if it is forcing the string to be rendered as a binary which would affect the rest of compilation.

Compilation of large literal binaries/strings have been generally improved in OTP 20 though: Move expansion of strings in binaries to v3_core by josevalim · Pull Request #1131 · erlang/otp · GitHub

1 Like

erlang 18.3
elixir 1.4.1

Here’s the result of Macro.to_string for the fixed version:

[[(
  @doc("#{"Type of SunSpec models used for inverter and meter data."}\
* Field Type: #{":uint16"}\
* Units: #{"\\"\\""}\
* Addr: #{"216"}\
* Num Bytes: #{"1"}\
")
  @spec(set_model_type(pid, integer, any) :: :ok)
  def(set_model_type(pid, slave_id, data)) do
    ExModbus.Runtime.set_field(pid, slave_id, data, 215, :uint16, %{1 => "Floating Point", 2 => "Integer & SF"})
  end
), (
  @doc("#{"Type of SunSpec models used for inverter and meter data."}\
* Field Type: #{":uint16"}\
* Units: #{"\\"\\""}\
* Addr: #{"216"}\
* Num Bytes: #{"1"}\
")
  @spec(model_type(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(model_type(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 215, 1, :uint16, %{1 => "Floating Point", 2 => "Integer & SF"})
  end
)], (
  @doc("#{"Scale factor for reactive power percent."}\
* Field Type: #{":sunssf"}\
* Units: #{"\\"\\""}\
* Addr: #{"40263"}\
* Num Bytes: #{"1"}\
")
  @spec(varpct_sf(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(varpct_sf(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 40262, 1, :sunssf, %{})
  end
), (
  @doc("#{"Manufacturer specific value (32 chars)"}\
* Field Type: #{":string"}\
* Units: #{"\\"\\""}\
* Addr: #{"40021"}\
* Num Bytes: #{"16"}\
")
  @spec(model(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(model(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 40020, 16, :string, %{})
  end
), (
  @doc("#{"Well known value registered with SunSpec for compliance"}\
* Field Type: #{":string"}\
* Units: #{"\\"\\""}\
* Addr: #{"40005"}\
* Num Bytes: #{"16"}\
")
  @spec(manufacturer(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(manufacturer(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 40004, 16, :string, %{})
  end
)]"
"[(
  @doc("#{""}")
  @spec(immediate_controls(pid, integer) :: {:ok, map()} | {:error, term()})
  def(immediate_controls(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40239, 24, [{:conn_wintms, :uint16, 1, %{}}, {:conn_rvrttms, :uint16, 1, %{}}, {:conn, :enum16, 1, %{0 => "DISCONNECT", 1 => "CONNECT"}}, {:wmaxlimpct, :uint16, 1, %{}}, {:wmaxlimpct_wintms, :uint16, 1, %{}}, {:wmaxlimpct_rvrttms, :uint16, 1, %{}}, {:wmaxlimpct_rmptms, :uint16, 1, %{}}, {:wmaxlim_ena, :enum16, 1, %{0 => "DISABLED", 1 => "ENABLED"}}, {:outpfset, :int16, 1, %{}}, {:outpfset_wintms, :uint16, 1, %{}}, {:outpfset_rvrttms, :uint16, 1, %{}}, {:outpfset_rmptms, :uint16, 1, %{}}, {:outpfset_ena, :enum16, 1, %{0 => "DISABLED", 1 => "ENABLED"}}, {:varwmaxpct, :int16, 1, %{}}, {:varmaxpct, :int16, 1, %{}}, {:varavalpct, :int16, 1, %{}}, {:varpct_wintms, :uint16, 1, %{}}, {:varpct_rvrttms, :uint16, 1, %{}}, {:varpct_rmptms, :uint16, 1, %{}}, {:varpct_mod, :enum16, 1, %{0 => "NONE", 1 => "WMax", 2 => "VArMax", 3 => "VArAval"}}, {:varpct_ena, :enum16, 1, %{0 => "DISABLED", 1 => "ENABLED"}}, {:wmaxlimpct_sf, :sunssf, 1, %{}}, {:outpfset_sf, :sunssf, 1, %{}}, {:varpct_sf, :sunssf, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(measurements_status(pid, integer) :: {:ok, map()} | {:error, term()})
  def(measurements_status(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40193, 44, [{:pvconn, :bitfield16, 1, %{0 => "CONNECTED", 1 => "AVAILABLE", 2 => "OPERATING", 3 => "TEST"}}, {:storconn, :bitfield16, 1, %{0 => "CONNECTED", 1 => "AVAILABLE", 2 => "OPERATING", 3 => "TEST"}}, {:ecpconn, :bitfield16, 1, %{0 => "CONNECTED"}}, {:actwh, :acc64, 4, %{}}, {:actvah, :acc64, 4, %{}}, {:actvarhq1, :acc64, 4, %{}}, {:actvarhq2, :acc64, 4, %{}}, {:actvarhq3, :acc64, 4, %{}}, {:actvarhq4, :acc64, 4, %{}}, {:varaval, :int16, 1, %{}}, {:varaval_sf, :sunssf, 1, %{}}, {:waval, :uint16, 1, %{}}, {:waval_sf, :sunssf, 1, %{}}, {:stsetlimmsk, :bitfield32, 2, %{0 => "WMax", 1 => "VAMax", 2 => "VArAval", 3 => "VArMaxQ1", 4 => "VArMaxQ2", 5 => "VArMaxQ3", 6 => "VArMaxQ4", 7 => "PFMinQ1", 8 => "PFMinQ2", 9 => "PFMinQ3", 10 => "PFMinQ4"}}, {:stactctl, :bitfield32, 2, %{0 => "FixedW", 1 => "FixedVAR", 2 => "FixedPF", 3 => "Volt-VAr", 4 => "Freq-Watt-Param", 5 => "Freq-Watt-Curve", 6 => "Dyn-Reactive-Current", 7 => "LVRT", 8 => "HVRT", 9 => "Watt-PF", 10 => "Volt-Watt", 12 => "Scheduled", 13 => "LFRT", 14 => "HFRT"}}, {:tmsrc, :string, 4, %{}}, {:tms, :uint32, 2, %{}}, {:rtst, :bitfield16, 1, %{0 => "LVRT_ACTIVE", 1 => "HVRT_ACTIVE", 2 => "LFRT_ACTIVE", 3 => "HFRT_ACTIVE"}}, {:ris, :uint16, 1, %{}}, {:ris_sf, :sunssf, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(basic_settings(pid, integer) :: {:ok, map()} | {:error, term()})
  def(basic_settings(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40161, 30, [{:wmax, :uint16, 1, %{}}, {:vref, :uint16, 1, %{}}, {:vrefofs, :int16, 1, %{}}, {:vmax, :uint16, 1, %{}}, {:vmin, :uint16, 1, %{}}, {:vamax, :uint16, 1, %{}}, {:varmaxq1, :int16, 1, %{}}, {:varmaxq2, :int16, 1, %{}}, {:varmaxq3, :int16, 1, %{}}, {:varmaxq4, :int16, 1, %{}}, {:wgra, :uint16, 1, %{}}, {:pfminq1, :int16, 1, %{}}, {:pfminq2, :int16, 1, %{}}, {:pfminq3, :int16, 1, %{}}, {:pfminq4, :int16, 1, %{}}, {:varact, :enum16, 1, %{1 => "SWITCH", 2 => "MAINTAIN"}}, {:clctotva, :enum16, 1, %{1 => "VECTOR", 2 => "ARITHMETIC"}}, {:maxrmprte, :uint16, 1, %{}}, {:ecpnomhz, :uint16, 1, %{}}, {:connph, :enum16, 1, %{1 => "A", 2 => "B", 3 => "C"}}, {:wmax_sf, :sunssf, 1, %{}}, {:vref_sf, :sunssf, 1, %{}}, {:vrefofs_sf, :sunssf, 1, %{}}, {:vminmax_sf, :sunssf, 1, %{}}, {:vamax_sf, :sunssf, 1, %{}}, {:varmax_sf, :sunssf, 1, %{}}, {:wgra_sf, :sunssf, 1, %{}}, {:pfmin_sf, :sunssf, 1, %{}}, {:maxrmprte_sf, :sunssf, 1, %{}}, {:ecpnomhz_sf, :sunssf, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(nameplate(pid, integer) :: {:ok, map()} | {:error, term()})
  def(nameplate(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40133, 26, [{:dertyp, :enum16, 1, %{4 => "PV", 82 => "PV_STOR"}}, {:wrtg, :uint16, 1, %{}}, {:wrtg_sf, :sunssf, 1, %{}}, {:vartg, :uint16, 1, %{}}, {:vartg_sf, :sunssf, 1, %{}}, {:varrtgq1, :int16, 1, %{}}, {:varrtgq2, :int16, 1, %{}}, {:varrtgq3, :int16, 1, %{}}, {:varrtgq4, :int16, 1, %{}}, {:varrtg_sf, :sunssf, 1, %{}}, {:artg, :uint16, 1, %{}}, {:artg_sf, :sunssf, 1, %{}}, {:pfrtgq1, :int16, 1, %{}}, {:pfrtgq2, :int16, 1, %{}}, {:pfrtgq3, :int16, 1, %{}}, {:pfrtgq4, :int16, 1, %{}}, {:pfrtg_sf, :sunssf, 1, %{}}, {:whrtg, :uint16, 1, %{}}, {:whrtg_sf, :sunssf, 1, %{}}, {:ahrrtg, :uint16, 1, %{}}, {:ahrrtg_sf, :sunssf, 1, %{}}, {:maxcharte, :uint16, 1, %{}}, {:maxcharte_sf, :sunssf, 1, %{}}, {:maxdischarte, :uint16, 1, %{}}, {:maxdischarte_sf, :sunssf, 1, %{}}, {:pad, :pad, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(inverter_single_phase_float(pid, integer) :: {:ok, map()} | {:error, term()})
  def(inverter_single_phase_float(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40071, 60, [{:amps, :float32, 2, %{}}, {:amps_phasea, :float32, 2, %{}}, {:amps_phaseb, :float32, 2, %{}}, {:amps_phasec, :float32, 2, %{}}, {:phase_voltage_ab, :float32, 2, %{}}, {:phase_voltage_bc, :float32, 2, %{}}, {:phase_voltage_ca, :float32, 2, %{}}, {:phase_voltage_an, :float32, 2, %{}}, {:phase_voltage_bn, :float32, 2, %{}}, {:phase_voltage_cn, :float32, 2, %{}}, {:watts, :float32, 2, %{}}, {:hz, :float32, 2, %{}}, {:va, :float32, 2, %{}}, {:var, :float32, 2, %{}}, {:pf, :float32, 2, %{}}, {:watthours, :float32, 2, %{}}, {:dc_amps, :float32, 2, %{}}, {:dc_voltage, :float32, 2, %{}}, {:dc_watts, :float32, 2, %{}}, {:cabinet_temperature, :float32, 2, %{}}, {:heat_sink_temperature, :float32, 2, %{}}, {:transformer_temperature, :float32, 2, %{}}, {:other_temperature, :float32, 2, %{}}, {:operating_state, :enum16, 1, %{1 => "ggOFF", 2 => "ggSLEEPING", 3 => "ggSTARTING", 4 => "ggMPPT", 5 => "ggTHROTTLED", 6 => "ggSHUTTING_DOWN", 7 => "ggFAULT", 8 => "ggSTANDBY"}}, {:vendor_operating_state, :enum16, 1, %{}}, {:event1, :bitfield32, 2, %{0 => "GROUND_FAULT", 1 => "DC_OVER_VOLT", 2 => "AC_DISCONNECT", 3 => "DC_DISCONNECT", 4 => "GRID_DISCONNECT", 5 => "CABINET_OPEN", 6 => "MANUAL_SHUTDOWN", 7 => "OVER_TEMP", 8 => "OVER_FREQUENCY", 9 => "UNDER_FREQUENCY", 10 => "AC_OVER_VOLT", 11 => "AC_UNDER_VOLT", 12 => "BLOWN_STRING_FUSE", 13 => "UNDER_TEMP", 14 => "MEMORY_LOSS", 15 => "HW_TEST_FAILURE"}}, {:event_bitfield_2, :bitfield32, 2, %{}}, {:vendor_event_bitfield_1, :bitfield32, 2, %{}}, {:vendor_event_bitfield_2, :bitfield32, 2, %{}}, {:vendor_event_bitfield_3, :bitfield32, 2, %{}}, {:vendor_event_bitfield_4, :bitfield32, 2, %{}}])
  end
), (
  @doc("#{""}")
  @spec(common(pid, integer) :: {:ok, map()} | {:error, term()})
  def(common(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40004, 65, [{:manufacturer, :string, 16, %{}}, {:model, :string, 16, %{}}, {:options, :string, 8, %{}}, {:version, :string, 8, %{}}, {:serial_number, :string, 16, %{}}, {:device_address, :uint16, 1, %{}}])
  end
)]

and the slow version:

[[(
  @doc("#{"Type of SunSpec models used for inverter and meter data."}\
* Field Type: #{:uint16}\
* Units: #{""}\
* Addr: #{216}\
* Num Bytes: #{1}\
")
  @spec(set_model_type(pid, integer, any) :: :ok)
  def(set_model_type(pid, slave_id, data)) do
    ExModbus.Runtime.set_field(pid, slave_id, data, 215, :uint16, %{1 => "Floating Point", 2 => "Integer & SF"})
  end
), (
  @doc("#{"Type of SunSpec models used for inverter and meter data."}\
* Field Type: #{:uint16}\
* Units: #{""}\
* Addr: #{216}\
* Num Bytes: #{1}\
")
  @spec(model_type(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(model_type(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 215, 1, :uint16, %{1 => "Floating Point", 2 => "Integer & SF"})
  end
)], (
  @doc("#{"Scale factor for reactive power percent."}\
* Field Type: #{:sunssf}\
* Units: #{""}\
* Addr: #{40263}\
* Num Bytes: #{1}\
")
  @spec(varpct_sf(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(varpct_sf(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 40262, 1, :sunssf, %{})
  end
), (
  @doc("#{"Scale factor for power factor."}\
* Field Type: #{:sunssf}\
* Units: #{""}\
* Addr: #{40262}\
* Num Bytes: #{1}\
")
  @spec(outpfset_sf(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(outpfset_sf(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 40261, 1, :sunssf, %{})
  end
), (
  @doc("#{"Scale factor for power output percent."}\
* Field Type: #{:sunssf}\
* Units: #{""}\
* Addr: #{40261}\
* Num Bytes: #{1}\
")
  @spec(wmaxlimpct_sf(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(wmaxlimpct_sf(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 40260, 1, :sunssf, %{})
  end
), [(
  @doc("#{"Enumerated valued.  Percent limit VAr enable/disable control."}\
* Field Type: #{:enum16}\
* Units: #{""}\
* Addr: #{40260}\
* Num Bytes: #{1}\
")
  @spec(set_varpct_ena(pid, integer, any) :: :ok)
  def(set_varpct_ena(pid, slave_id, data)) do
    ExModbus.Runtime.set_field(pid, slave_id, data, 40259, :enum16, %{0 => "DISABLED", 1 => "ENABLED"})
  end
), (
  @doc("#{"Manufacturer specific value (16 chars)"}\
* Field Type: #{:string}\
* Units: #{""}\
* Addr: #{40037}\
* Num Bytes: #{8}\
")
  @spec(options(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(options(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 40036, 8, :string, %{})
  end
), (
  @doc("#{"Manufacturer specific value (32 chars)"}\
* Field Type: #{:string}\
* Units: #{""}\
* Addr: #{40021}\
* Num Bytes: #{16}\
")
  @spec(model(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(model(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 40020, 16, :string, %{})
  end
), (
  @doc("#{"Well known value registered with SunSpec for compliance"}\
* Field Type: #{:string}\
* Units: #{""}\
* Addr: #{40005}\
* Num Bytes: #{16}\
")
  @spec(manufacturer(pid, integer) :: {:ok, %{data: any(), transaction_id: integer(), unit_id: integer()}} | {:type_conversion_error, {any(), any()}} | {:enum_not_found_error, String.t()})
  def(manufacturer(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_field(pid, slave_id, 40004, 16, :string, %{})
  end
)]"
"[(
  @doc("#{""}")
  @spec(immediate_controls(pid, integer) :: {:ok, map()} | {:error, term()})
  def(immediate_controls(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40239, 24, [{:conn_wintms, :uint16, 1, %{}}, {:conn_rvrttms, :uint16, 1, %{}}, {:conn, :enum16, 1, %{0 => "DISCONNECT", 1 => "CONNECT"}}, {:wmaxlimpct, :uint16, 1, %{}}, {:wmaxlimpct_wintms, :uint16, 1, %{}}, {:wmaxlimpct_rvrttms, :uint16, 1, %{}}, {:wmaxlimpct_rmptms, :uint16, 1, %{}}, {:wmaxlim_ena, :enum16, 1, %{0 => "DISABLED", 1 => "ENABLED"}}, {:outpfset, :int16, 1, %{}}, {:outpfset_wintms, :uint16, 1, %{}}, {:outpfset_rvrttms, :uint16, 1, %{}}, {:outpfset_rmptms, :uint16, 1, %{}}, {:outpfset_ena, :enum16, 1, %{0 => "DISABLED", 1 => "ENABLED"}}, {:varwmaxpct, :int16, 1, %{}}, {:varmaxpct, :int16, 1, %{}}, {:varavalpct, :int16, 1, %{}}, {:varpct_wintms, :uint16, 1, %{}}, {:varpct_rvrttms, :uint16, 1, %{}}, {:varpct_rmptms, :uint16, 1, %{}}, {:varpct_mod, :enum16, 1, %{0 => "NONE", 1 => "WMax", 2 => "VArMax", 3 => "VArAval"}}, {:varpct_ena, :enum16, 1, %{0 => "DISABLED", 1 => "ENABLED"}}, {:wmaxlimpct_sf, :sunssf, 1, %{}}, {:outpfset_sf, :sunssf, 1, %{}}, {:varpct_sf, :sunssf, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(measurements_status(pid, integer) :: {:ok, map()} | {:error, term()})
  def(measurements_status(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40193, 44, [{:pvconn, :bitfield16, 1, %{0 => "CONNECTED", 1 => "AVAILABLE", 2 => "OPERATING", 3 => "TEST"}}, {:storconn, :bitfield16, 1, %{0 => "CONNECTED", 1 => "AVAILABLE", 2 => "OPERATING", 3 => "TEST"}}, {:ecpconn, :bitfield16, 1, %{0 => "CONNECTED"}}, {:actwh, :acc64, 4, %{}}, {:actvah, :acc64, 4, %{}}, {:actvarhq1, :acc64, 4, %{}}, {:actvarhq2, :acc64, 4, %{}}, {:actvarhq3, :acc64, 4, %{}}, {:actvarhq4, :acc64, 4, %{}}, {:varaval, :int16, 1, %{}}, {:varaval_sf, :sunssf, 1, %{}}, {:waval, :uint16, 1, %{}}, {:waval_sf, :sunssf, 1, %{}}, {:stsetlimmsk, :bitfield32, 2, %{0 => "WMax", 1 => "VAMax", 2 => "VArAval", 3 => "VArMaxQ1", 4 => "VArMaxQ2", 5 => "VArMaxQ3", 6 => "VArMaxQ4", 7 => "PFMinQ1", 8 => "PFMinQ2", 9 => "PFMinQ3", 10 => "PFMinQ4"}}, {:stactctl, :bitfield32, 2, %{0 => "FixedW", 1 => "FixedVAR", 2 => "FixedPF", 3 => "Volt-VAr", 4 => "Freq-Watt-Param", 5 => "Freq-Watt-Curve", 6 => "Dyn-Reactive-Current", 7 => "LVRT", 8 => "HVRT", 9 => "Watt-PF", 10 => "Volt-Watt", 12 => "Scheduled", 13 => "LFRT", 14 => "HFRT"}}, {:tmsrc, :string, 4, %{}}, {:tms, :uint32, 2, %{}}, {:rtst, :bitfield16, 1, %{0 => "LVRT_ACTIVE", 1 => "HVRT_ACTIVE", 2 => "LFRT_ACTIVE", 3 => "HFRT_ACTIVE"}}, {:ris, :uint16, 1, %{}}, {:ris_sf, :sunssf, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(basic_settings(pid, integer) :: {:ok, map()} | {:error, term()})
  def(basic_settings(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40161, 30, [{:wmax, :uint16, 1, %{}}, {:vref, :uint16, 1, %{}}, {:vrefofs, :int16, 1, %{}}, {:vmax, :uint16, 1, %{}}, {:vmin, :uint16, 1, %{}}, {:vamax, :uint16, 1, %{}}, {:varmaxq1, :int16, 1, %{}}, {:varmaxq2, :int16, 1, %{}}, {:varmaxq3, :int16, 1, %{}}, {:varmaxq4, :int16, 1, %{}}, {:wgra, :uint16, 1, %{}}, {:pfminq1, :int16, 1, %{}}, {:pfminq2, :int16, 1, %{}}, {:pfminq3, :int16, 1, %{}}, {:pfminq4, :int16, 1, %{}}, {:varact, :enum16, 1, %{1 => "SWITCH", 2 => "MAINTAIN"}}, {:clctotva, :enum16, 1, %{1 => "VECTOR", 2 => "ARITHMETIC"}}, {:maxrmprte, :uint16, 1, %{}}, {:ecpnomhz, :uint16, 1, %{}}, {:connph, :enum16, 1, %{1 => "A", 2 => "B", 3 => "C"}}, {:wmax_sf, :sunssf, 1, %{}}, {:vref_sf, :sunssf, 1, %{}}, {:vrefofs_sf, :sunssf, 1, %{}}, {:vminmax_sf, :sunssf, 1, %{}}, {:vamax_sf, :sunssf, 1, %{}}, {:varmax_sf, :sunssf, 1, %{}}, {:wgra_sf, :sunssf, 1, %{}}, {:pfmin_sf, :sunssf, 1, %{}}, {:maxrmprte_sf, :sunssf, 1, %{}}, {:ecpnomhz_sf, :sunssf, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(nameplate(pid, integer) :: {:ok, map()} | {:error, term()})
  def(nameplate(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40133, 26, [{:dertyp, :enum16, 1, %{4 => "PV", 82 => "PV_STOR"}}, {:wrtg, :uint16, 1, %{}}, {:wrtg_sf, :sunssf, 1, %{}}, {:vartg, :uint16, 1, %{}}, {:vartg_sf, :sunssf, 1, %{}}, {:varrtgq1, :int16, 1, %{}}, {:varrtgq2, :int16, 1, %{}}, {:varrtgq3, :int16, 1, %{}}, {:varrtgq4, :int16, 1, %{}}, {:varrtg_sf, :sunssf, 1, %{}}, {:artg, :uint16, 1, %{}}, {:artg_sf, :sunssf, 1, %{}}, {:pfrtgq1, :int16, 1, %{}}, {:pfrtgq2, :int16, 1, %{}}, {:pfrtgq3, :int16, 1, %{}}, {:pfrtgq4, :int16, 1, %{}}, {:pfrtg_sf, :sunssf, 1, %{}}, {:whrtg, :uint16, 1, %{}}, {:whrtg_sf, :sunssf, 1, %{}}, {:ahrrtg, :uint16, 1, %{}}, {:ahrrtg_sf, :sunssf, 1, %{}}, {:maxcharte, :uint16, 1, %{}}, {:maxcharte_sf, :sunssf, 1, %{}}, {:maxdischarte, :uint16, 1, %{}}, {:maxdischarte_sf, :sunssf, 1, %{}}, {:pad, :pad, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(inverter_single_phase_float(pid, integer) :: {:ok, map()} | {:error, term()})
  def(inverter_single_phase_float(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40071, 60, [{:amps, :float32, 2, %{}}, {:amps_phasea, :float32, 2, %{}}, {:amps_phaseb, :float32, 2, %{}}, {:amps_phasec, :float32, 2, %{}}, {:phase_voltage_ab, :float32, 2, %{}}, {:phase_voltage_bc, :float32, 2, %{}}, {:phase_voltage_ca, :float32, 2, %{}}, {:phase_voltage_an, :float32, 2, %{}}, {:phase_voltage_bn, :float32, 2, %{}}, {:phase_voltage_cn, :float32, 2, %{}}, {:watts, :float32, 2, %{}}, {:hz, :float32, 2, %{}}, {:va, :float32, 2, %{}}, {:var, :float32, 2, %{}}, {:pf, :float32, 2, %{}}, {:watthours, :float32, 2, %{}}, {:dc_amps, :float32, 2, %{}}, {:dc_voltage, :float32, 2, %{}}, {:dc_watts, :float32, 2, %{}}, {:cabinet_temperature, :float32, 2, %{}}, {:heat_sink_temperature, :float32, 2, %{}}, {:transformer_temperature, :float32, 2, %{}}, {:other_temperature, :float32, 2, %{}}, {:operating_state, :enum16, 1, %{1 => "ggOFF", 2 => "ggSLEEPING", 3 => "ggSTARTING", 4 => "ggMPPT", 5 => "ggTHROTTLED", 6 => "ggSHUTTING_DOWN", 7 => "ggFAULT", 8 => "ggSTANDBY"}}, {:vendor_operating_state, :enum16, 1, %{}}, {:event1, :bitfield32, 2, %{0 => "GROUND_FAULT", 1 => "DC_OVER_VOLT", 2 => "AC_DISCONNECT", 3 => "DC_DISCONNECT", 4 => "GRID_DISCONNECT", 5 => "CABINET_OPEN", 6 => "MANUAL_SHUTDOWN", 7 => "OVER_TEMP", 8 => "OVER_FREQUENCY", 9 => "UNDER_FREQUENCY", 10 => "AC_OVER_VOLT", 11 => "AC_UNDER_VOLT", 12 => "BLOWN_STRING_FUSE", 13 => "UNDER_TEMP", 14 => "MEMORY_LOSS", 15 => "HW_TEST_FAILURE"}}, {:event_bitfield_2, :bitfield32, 2, %{}}, {:vendor_event_bitfield_1, :bitfield32, 2, %{}}, {:vendor_event_bitfield_2, :bitfield32, 2, %{}}, {:vendor_event_bitfield_3, :bitfield32, 2, %{}}, {:vendor_event_bitfield_4, :bitfield32, 2, %{}}])
  end
), (
  @doc("#{""}")
  @spec(common(pid, integer) :: {:ok, map()} | {:error, term()})
  def(common(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40004, 65, [{:manufacturer, :string, 16, %{}}, {:model, :string, 16, %{}}, {:options, :string, 8, %{}}, {:version, :string, 8, %{}}, {:serial_number, :string, 16, %{}}, {:device_address, :uint16, 1, %{}}])
  end
)]"
"[(
  @doc("#{""}")
  @spec(immediate_controls(pid, integer) :: {:ok, map()} | {:error, term()})
  def(immediate_controls(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40229, 24, [{:conn_wintms, :uint16, 1, %{}}, {:conn_rvrttms, :uint16, 1, %{}}, {:conn, :enum16, 1, %{0 => "DISCONNECT", 1 => "CONNECT"}}, {:wmaxlimpct, :uint16, 1, %{}}, {:wmaxlimpct_wintms, :uint16, 1, %{}}, {:wmaxlimpct_rvrttms, :uint16, 1, %{}}, {:wmaxlimpct_rmptms, :uint16, 1, %{}}, {:wmaxlim_ena, :enum16, 1, %{0 => "DISABLED", 1 => "ENABLED"}}, {:outpfset, :int16, 1, %{}}, {:outpfset_wintms, :uint16, 1, %{}}, {:outpfset_rvrttms, :uint16, 1, %{}}, {:outpfset_rmptms, :uint16, 1, %{}}, {:outpfset_ena, :enum16, 1, %{0 => "DISABLED", 1 => "ENABLED"}}, {:varwmaxpct, :int16, 1, %{}}, {:varmaxpct, :int16, 1, %{}}, {:varavalpct, :int16, 1, %{}}, {:varpct_wintms, :uint16, 1, %{}}, {:varpct_rvrttms, :uint16, 1, %{}}, {:varpct_rmptms, :uint16, 1, %{}}, {:varpct_mod, :enum16, 1, %{0 => "NONE", 1 => "WMax", 2 => "VArMax", 3 => "VArAval"}}, {:varpct_ena, :enum16, 1, %{0 => "DISABLED", 1 => "ENABLED"}}, {:wmaxlimpct_sf, :sunssf, 1, %{}}, {:outpfset_sf, :sunssf, 1, %{}}, {:varpct_sf, :sunssf, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(measurements_status(pid, integer) :: {:ok, map()} | {:error, term()})
  def(measurements_status(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40183, 44, [{:pvconn, :bitfield16, 1, %{0 => "CONNECTED", 1 => "AVAILABLE", 2 => "OPERATING", 3 => "TEST"}}, {:storconn, :bitfield16, 1, %{0 => "CONNECTED", 1 => "AVAILABLE", 2 => "OPERATING", 3 => "TEST"}}, {:ecpconn, :bitfield16, 1, %{0 => "CONNECTED"}}, {:actwh, :acc64, 4, %{}}, {:actvah, :acc64, 4, %{}}, {:actvarhq1, :acc64, 4, %{}}, {:actvarhq2, :acc64, 4, %{}}, {:actvarhq3, :acc64, 4, %{}}, {:actvarhq4, :acc64, 4, %{}}, {:varaval, :int16, 1, %{}}, {:varaval_sf, :sunssf, 1, %{}}, {:waval, :uint16, 1, %{}}, {:waval_sf, :sunssf, 1, %{}}, {:stsetlimmsk, :bitfield32, 2, %{0 => "WMax", 1 => "VAMax", 2 => "VArAval", 3 => "VArMaxQ1", 4 => "VArMaxQ2", 5 => "VArMaxQ3", 6 => "VArMaxQ4", 7 => "PFMinQ1", 8 => "PFMinQ2", 9 => "PFMinQ3", 10 => "PFMinQ4"}}, {:stactctl, :bitfield32, 2, %{0 => "FixedW", 1 => "FixedVAR", 2 => "FixedPF", 3 => "Volt-VAr", 4 => "Freq-Watt-Param", 5 => "Freq-Watt-Curve", 6 => "Dyn-Reactive-Current", 7 => "LVRT", 8 => "HVRT", 9 => "Watt-PF", 10 => "Volt-Watt", 12 => "Scheduled", 13 => "LFRT", 14 => "HFRT"}}, {:tmsrc, :string, 4, %{}}, {:tms, :uint32, 2, %{}}, {:rtst, :bitfield16, 1, %{0 => "LVRT_ACTIVE", 1 => "HVRT_ACTIVE", 2 => "LFRT_ACTIVE", 3 => "HFRT_ACTIVE"}}, {:ris, :uint16, 1, %{}}, {:ris_sf, :sunssf, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(basic_settings(pid, integer) :: {:ok, map()} | {:error, term()})
  def(basic_settings(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40151, 30, [{:wmax, :uint16, 1, %{}}, {:vref, :uint16, 1, %{}}, {:vrefofs, :int16, 1, %{}}, {:vmax, :uint16, 1, %{}}, {:vmin, :uint16, 1, %{}}, {:vamax, :uint16, 1, %{}}, {:varmaxq1, :int16, 1, %{}}, {:varmaxq2, :int16, 1, %{}}, {:varmaxq3, :int16, 1, %{}}, {:varmaxq4, :int16, 1, %{}}, {:wgra, :uint16, 1, %{}}, {:pfminq1, :int16, 1, %{}}, {:pfminq2, :int16, 1, %{}}, {:pfminq3, :int16, 1, %{}}, {:pfminq4, :int16, 1, %{}}, {:varact, :enum16, 1, %{1 => "SWITCH", 2 => "MAINTAIN"}}, {:clctotva, :enum16, 1, %{1 => "VECTOR", 2 => "ARITHMETIC"}}, {:maxrmprte, :uint16, 1, %{}}, {:ecpnomhz, :uint16, 1, %{}}, {:connph, :enum16, 1, %{1 => "A", 2 => "B", 3 => "C"}}, {:wmax_sf, :sunssf, 1, %{}}, {:vref_sf, :sunssf, 1, %{}}, {:vrefofs_sf, :sunssf, 1, %{}}, {:vminmax_sf, :sunssf, 1, %{}}, {:vamax_sf, :sunssf, 1, %{}}, {:varmax_sf, :sunssf, 1, %{}}, {:wgra_sf, :sunssf, 1, %{}}, {:pfmin_sf, :sunssf, 1, %{}}, {:maxrmprte_sf, :sunssf, 1, %{}}, {:ecpnomhz_sf, :sunssf, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(nameplate(pid, integer) :: {:ok, map()} | {:error, term()})
  def(nameplate(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40123, 26, [{:dertyp, :enum16, 1, %{4 => "PV", 82 => "PV_STOR"}}, {:wrtg, :uint16, 1, %{}}, {:wrtg_sf, :sunssf, 1, %{}}, {:vartg, :uint16, 1, %{}}, {:vartg_sf, :sunssf, 1, %{}}, {:varrtgq1, :int16, 1, %{}}, {:varrtgq2, :int16, 1, %{}}, {:varrtgq3, :int16, 1, %{}}, {:varrtgq4, :int16, 1, %{}}, {:varrtg_sf, :sunssf, 1, %{}}, {:artg, :uint16, 1, %{}}, {:artg_sf, :sunssf, 1, %{}}, {:pfrtgq1, :int16, 1, %{}}, {:pfrtgq2, :int16, 1, %{}}, {:pfrtgq3, :int16, 1, %{}}, {:pfrtgq4, :int16, 1, %{}}, {:pfrtg_sf, :sunssf, 1, %{}}, {:whrtg, :uint16, 1, %{}}, {:whrtg_sf, :sunssf, 1, %{}}, {:ahrrtg, :uint16, 1, %{}}, {:ahrrtg_sf, :sunssf, 1, %{}}, {:maxcharte, :uint16, 1, %{}}, {:maxcharte_sf, :sunssf, 1, %{}}, {:maxdischarte, :uint16, 1, %{}}, {:maxdischarte_sf, :sunssf, 1, %{}}, {:pad, :pad, 1, %{}}])
  end
), (
  @doc("#{""}")
  @spec(inverter_single_phase(pid, integer) :: {:ok, map()} | {:error, term()})
  def(inverter_single_phase(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40071, 50, [{:amps, :uint16, 1, %{}}, {:amps_phasea, :uint16, 1, %{}}, {:amps_phaseb, :uint16, 1, %{}}, {:amps_phasec, :uint16, 1, %{}}, {:a_sf, :sunssf, 1, %{}}, {:phase_voltage_ab, :uint16, 1, %{}}, {:phase_voltage_bc, :uint16, 1, %{}}, {:phase_voltage_ca, :uint16, 1, %{}}, {:phase_voltage_an, :uint16, 1, %{}}, {:phase_voltage_bn, :uint16, 1, %{}}, {:phase_voltage_cn, :uint16, 1, %{}}, {:v_sf, :sunssf, 1, %{}}, {:watts, :int16, 1, %{}}, {:w_sf, :sunssf, 1, %{}}, {:hz, :uint16, 1, %{}}, {:hz_sf, :sunssf, 1, %{}}, {:va, :int16, 1, %{}}, {:va_sf, :sunssf, 1, %{}}, {:var, :int16, 1, %{}}, {:var_sf, :sunssf, 1, %{}}, {:pf, :int16, 1, %{}}, {:pf_sf, :sunssf, 1, %{}}, {:watthours, :acc32, 2, %{}}, {:wh_sf, :sunssf, 1, %{}}, {:dc_amps, :uint16, 1, %{}}, {:dca_sf, :sunssf, 1, %{}}, {:dc_voltage, :uint16, 1, %{}}, {:dcv_sf, :sunssf, 1, %{}}, {:dc_watts, :int16, 1, %{}}, {:dcw_sf, :sunssf, 1, %{}}, {:cabinet_temperature, :int16, 1, %{}}, {:heat_sink_temperature, :int16, 1, %{}}, {:transformer_temperature, :int16, 1, %{}}, {:other_temperature, :int16, 1, %{}}, {:tmp_sf, :sunssf, 1, %{}}, {:operating_state, :enum16, 1, %{1 => "OFF", 2 => "SLEEPING", 3 => "STARTING", 4 => "MPPT", 5 => "THROTTLED", 6 => "SHUTTING_DOWN", 7 => "FAULT", 8 => "STANDBY"}}, {:vendor_operating_state, :enum16, 1, %{}}, {:event1, :bitfield32, 2, %{0 => "GROUND_FAULT", 1 => "DC_OVER_VOLT", 2 => "AC_DISCONNECT", 3 => "DC_DISCONNECT", 4 => "GRID_DISCONNECT", 5 => "CABINET_OPEN", 6 => "MANUAL_SHUTDOWN", 7 => "OVER_TEMP", 8 => "OVER_FREQUENCY", 9 => "UNDER_FREQUENCY", 10 => "AC_OVER_VOLT", 11 => "AC_UNDER_VOLT", 12 => "BLOWN_STRING_FUSE", 13 => "UNDER_TEMP", 14 => "MEMORY_LOSS", 15 => "HW_TEST_FAILURE"}}, {:event_bitfield_2, :bitfield32, 2, %{}}, {:vendor_event_bitfield_1, :bitfield32, 2, %{}}, {:vendor_event_bitfield_2, :bitfield32, 2, %{}}, {:vendor_event_bitfield_3, :bitfield32, 2, %{}}, {:vendor_event_bitfield_4, :bitfield32, 2, %{}}])
  end
), (
  @doc("#{""}")
  @spec(common(pid, integer) :: {:ok, map()} | {:error, term()})
  def(common(pid, slave_id \\\\ 1)) do
    ExModbus.Runtime.get_fields(pid, slave_id, 40004, 65, [{:manufacturer, :string, 16, %{}}, {:model, :string, 16, %{}}, {:options, :string, 8, %{}}, {:version, :string, 8, %{}}, {:serial_number, :string, 16, %{}}, {:device_address, :uint16, 1, %{}}])
  end
)]

Bear in mind that the real files were a lot longer. I shorted these to prevent exceeding the forum length restrictions.

I’ll try it OTP 20 in the morning and update w/ my results.

Results for:
erlang 20.0
elixir 1.4.5

No ‘inspect’ compile time: 117s
With ‘inspect’ compile time: 11.42s

This was a different, larger project, which is why the timing’s a little different. Clearly though the difference remains. @jose would you like to see the AST’s generated by this version, or is that likely to be the same as what I posted previously?

/me wonders if the original version is slow because it is doing compile-time protocol dispatch for the to_string call so the protocol dispatch is not optimized yet, but why is the inspect dispatch fast, is inspect already a more simple protocol like a single module in most cases?