pejrich

pejrich

I have quite a few files in my project that have many function heads(100s to 1000s). I don’t think these can easily be replaced with another way of doing it. For example, transliterating Japanese into Latin characters. Right now I have compile time code read from this Unihan_Readings.txt file[Warning, 5mb text file], and generate functions that match the Japanese character at the beginning of the string. This can’t be replaced by String.split, because some matches are multi-character, but the variation in byte length is even wider, so binary pattern matching is the most obvious solution.

Anyway, compiling a file like this, is currently taking a LONG time, and it seems to have gotten worse under OTP 26, like a lot worse. It used to take maybe 30-40 seconds, now it takes 20-30 minutes(M1 MBP running Ventura 13.4. OTP 26.0, Elixir 1.15.3-otp-26). Is there any way to optimize this ahead of time for quicker compilation?

I’m currently running a compile with ERL_COMPILER_OPTIONS=time mix compile --force --profile time, so far(45 minutes), here’s some of the output:

beam_ssa_codegen              :      0.715 s  114644.0 kB
 beam_validator_strong         :      0.195 s  114644.0 kB
 beam_a                        :      0.018 s  115052.9 kB
 beam_block                    :      0.025 s  120606.8 kB
 beam_jump                     :      0.105 s  100114.5 kB
 beam_clean                    :      0.002 s  100114.5 kB
 beam_trim                     :      0.000 s  100114.5 kB
 beam_flatten                  :      0.000 s   99683.7 kB
 beam_z                        :      0.000 s   99668.4 kB
 beam_validator_weak           :      0.099 s   99668.4 kB
 beam_asm                      :      0.191 s   95366.4 kB
 beam_ssa_opt                  :   1876.602 s  277640.0 kB
    %% Sub passes of beam_ssa_opt from slowest to fastest:
    ssa_opt_alias              :   1866.038 s  99 %
    ssa_opt_live               :      2.007 s   0 %
    ssa_opt_type_start         :      1.973 s   0 %
    ssa_opt_dead               :      1.113 s   0 %
    ssa_opt_type_continue      :      0.958 s   0 %
    ssa_opt_bsm_shortcut       :      0.621 s   0 %
    ssa_opt_merge_blocks       :      0.447 s   0 %
    ssa_opt_cse                :      0.374 s   0 %

Showing Posts 1 to 10

dimitarvp

dimitarvp

I’ve only seen such huge slowdown when I was trying to compile for ARM on my x64 Mac. Are you sure you are using fully ARM-native terminal, homebrew and everything?

pejrich

pejrich OP

It looks like everything is running on arm.

$ uname -m
arm64

And ActivityMonitor shows beam.smp running as Apple, which I believe is arm64

D4no0

D4no0

It would be nice if you showed at least some example on what your functions look like, from what I understand you are generating code based on this file?

pejrich

pejrich OP

Here’s a stripped down version of what i’m doing. This example doesn’t use the Unihan file because that part has a bit of preprocessing, but it’s ultimately similar. Here hepburn.tsv and ja_punct.tsv, are files of two columns, that look like this:

あ	a
リュ	ryu
ロー	rō
ヲー	wō
ヤ	ya

Hepburn is 604 lines, ja_punct is 91 lines.

defmodule JA do
  @priv :code.priv_dir(:my_app)
  @ja_path Path.join(@priv, "langs/ja")
  @punct_path Path.join(@priv, "ja_punct.tsv")
  @hepburn_path Path.join(@ja_path, "hepburn.tsv")
  @hepburn File.read!(@hepburn_path)
  |> String.trim()
  |> String.split("\n")
  |> Enum.map(&String.split(&1, "\t"))
  |> Enum.map(&List.to_tuple/1)

  @punct File.read!(@punct_path)
  |> String.trim()
  |> String.split("\n")
  |> Enum.map(&String.split(&1, "\t"))
  |> Enum.map(&List.to_tuple/1)

  @all Enum.sort_by(@hepburn ++ @punct, fn {a, _} -> -byte_size(a) end)

  def replace("" <> string), do: replace(string, [])

  def replace("", acc), do: acc

  Enum.each(@all, fn {k, v} ->
    def replace(<<unquote(k), rest::binary>>, acc),
      do: replace(rest, [unquote(Macro.escape(v)) | acc])
  end)

  def replace(<<char::binary-size(1), rest::binary>>, acc), do: replace(rest, [char | acc])
end
D4no0

D4no0

This might be a good place to start. From what I remember Enum.sort_by/3 is not one of the most performant of the functions. I guess a place to start debugging might be by using timer, and I would start from this sort function as is one of the possible bottleneck places.

LostKobrakai

LostKobrakai

Consider something like nimble_csv to parse the tsv files. You’re iterating a bit more often than needed through the data.

pejrich

pejrich OP

If I run those module attributes with an |> IO.inspect() at the end, they all finish within a second, which leads me to believe the issue is not so much with the parsing of the TSV or the sorting. I just logged a count, and it’s a total of 16,089 items that would be used to generate the functions dynamically. From what I can tell, the actual compile time code and the elixir side run in a matter of seconds, I think it’s the erlang compilation that’s taking 30minutes.

pejrich

pejrich OP

It’s certainly likely that this can be optimized, but from what I can tell, it’s not the culprit. I’m not sure if this is an accurate way to test how long those module attributes take to run, but if I do something like this:

defmodule JA do
  @time System.monotonic_time(:millisecond)

  @data ....
    |> ....
    |> IO.inspect(label: "Done #{System.monotonic_time(:millisecond) - @time}")
end

It shows that all my module attributes run in <200 milliseconds.

LostKobrakai

LostKobrakai

That’s useful information. In this case it’s really likely that this is due to the compilation of functions, not the compile time logic. Iirc from older discussions anything beyond like the 1000 of functions/heads would be considered problematic, so 16k is indeed large. Iirc cldr libraries did also run into this every now an then. That might be something to search for.

pejrich

pejrich OP

Yeah, it’s probably certainly beyond what they’re developing it for, but it is a fairly clean way to code it, so that’s what drew me to it. Since there’s so much variation in the byte size of what I’m matching, and ultimately I can’t ask all the worlds languages to change, so there’s going to be the number of items in the search space that the language dictates, I could recode it to do something like progressively pulling a byte off the front of the string, and accumulate that until it matches a key in a Map, which would hold the 16k items, but that seems a bit convoluted in comparison to the current solution.

But with all that said, my current solution works just fine, and in OTP 25 it compiled in a reasonable 20-30 seconds rather than 20-30 minutes, so maybe I’ll just go back down to OTP 25 for now.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews