avatarThe Pragmatic Programmers

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

3486

Abstract

n class="hljs-keyword">return</span><span class="hljs-built_in">enif_make_int</span>(env, z); ​ } ​ ​ ​<span class="hljs-type">static</span>​ ErlNifFunc nif_funcs[] = ​ { ​ {​<span class="hljs-string">"xor96"</span>​, <span class="hljs-number">0</span>, xor96} ​ }; ​ ​ <span class="hljs-built_in">ERL_NIF_INIT</span>(Elixir.Genetic, nif_funcs, <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>, <span class="hljs-literal">NULL</span>);</pre></div><p id="9831">This code defines an XOR96 random number generator and returns it to the Erlang environment using the Erlang NIF C interfaces. Most of the code you see defined here is boilerplate code necessary to create NIFs in C.</p><p id="0704">Next, you need to define a new Mix compiler in mix.exs, like this:</p><div id="30cf"><pre>​ ​<span class="hljs-class"><span class="hljs-keyword">defmodule</span><span class="hljs-title">Mix.Tasks.Compile.Genetic</span></span><span class="hljs-keyword">do</span>​ ​ ​<span class="hljs-keyword">use</span><span class="hljs-title class_">Mix</span>.<span class="hljs-title class_">Task</span>.<span class="hljs-title class_">Compiler</span> ​ ​ ​<span class="hljs-function"><span class="hljs-keyword">def</span><span class="hljs-title">run</span></span>(_args) ​<span class="hljs-keyword">do</span>​ ​ {result, _errcode} = ​ <span class="hljs-title class_">System</span>.cmd( ​ ​<span class="hljs-string">"​​gcc"</span>​, ​ [​<span class="hljs-string">"​​-fpic"</span>​, ​<span class="hljs-string">"​​-shared"</span>​, ​<span class="hljs-string">"​​-o"</span>​, ​<span class="hljs-string">"​​genetic.so"</span>​, ​<span class="hljs-string">"​​src/genetic.c"</span>​], ​ ​<span class="hljs-symbol">stderr_to_stdout:</span><span class="hljs-literal">true</span> ​ ) ​ <span class="hljs-title class_">IO</span>.puts(result) ​ ​<span class="hljs-keyword">end</span>​ ​ ​<span class="hljs-keyword">end</span></pre></div><p id="99a9">This code uses the Mix.Compiler API to create a new Mix compiler that will compile your NIFs to a shared-object library using GCC. If you’re using Windows, you’ll have to either use MingW, Windows Subsystem for Linux (WSL), or integrate this workflow with Visual Studio. MingW is probably the most straightforward option. It’s a port of the C compiler, GCC, to Windows.</p><p id="d1f2">Next, you add your compiler to your projects :compilers like this:</p><div id="fa6c"><pre>​ ​<span class="hljs-class"><span class="hljs-keyword">defmodule</span><span class="hljs-title">Genetic.Mixfile</span></span><span class="hljs-keyword">do</span>​ ​ ​<span class="hljs-keyword">use</span><span class="hljs-title class_">Mix</span>.<span class="hljs-title class_">Project</span> ​ ​ ​<span class="hljs-function"><span class="hljs-keyword">def</span><span class="hljs-title">project</span></span><span class="hljs-keyword">do</span>​ ​ [ ​ ​<span class="hljs-symbol">app:</span>​ ​<span class="hljs-symbol">:genetic</span>​, ​ ... ​ ​<span class="hljs-symbol">compilers:</span>​ [​<span class="hljs-symbol">:genetic</span>​] ++ <span class="hljs-title class_">Mix</span>.compilers, ​ ] ​ ​<span class="hljs-keyword">end</span>​ ​ ... ​ ​<span class="hljs-keyword">end</span></pre></div><p id="7ec2">Now, whenever you compile your algorithms, Mix will run your custom compiler and compile your NIF

Options

s for you. To call xor96 from your code, add the following to the top of genetic.ex:</p><div id="3e1e"><pre>​ ​<span class="hljs-class"><span class="hljs-keyword">defmodule</span><span class="hljs-title">Genetic</span></span><span class="hljs-keyword">do</span>​ ​ <span class="hljs-variable">@on_load</span><span class="hljs-symbol">:load_nif</span>​ ​ ​ ​<span class="hljs-function"><span class="hljs-keyword">def</span><span class="hljs-title">load_nif</span></span><span class="hljs-keyword">do</span>​ ​ ​<span class="hljs-symbol">:erlang</span>​.load_nif(​<span class="hljs-string">'./genetic'</span>​, <span class="hljs-number">0</span>) ​ ​<span class="hljs-keyword">end</span>​ ​ ​ ​<span class="hljs-function"><span class="hljs-keyword">def</span><span class="hljs-title">xor96</span></span>, ​<span class="hljs-keyword">do</span>​: ​<span class="hljs-keyword">raise</span>​ ​<span class="hljs-string">"​​NIF xor96/0 not implemented."</span>​ ​ ​ ... ​ ​<span class="hljs-keyword">end</span></pre></div><p id="38d3">This code defines a function to run once the module is loaded. The function load_nif/0 uses Erlang’s load_nif/2 function to load your shared object library at runtime. You also define a fallback implementation of xor96 to run if Elixir can’t find your C implementation.</p><p id="c62a">xor96/0 will generate integers. You can use it to generate integers between 0 and some number, like this:</p><div id="4ac2"><pre><span class="hljs-built_in">iex</span>(<span class="hljs-number">0</span>)> <span class="hljs-built_in">rem</span>(xor96(), <span class="hljs-number">100</span>) ​ <span class="hljs-number">42</span></pre></div><p id="37a1">In an example function like single_point_crossover/2, it would look like this:</p><div id="e302"><pre>​ ​def​ single_point_crossover(p1, p2) ​do​ ​ cx_point = rem(Genetic.xor96(), p1.size) ​ {p1_head, p1_tail} = Enum.split(p1.genes, cx_point) ​ {p2_head, p2_tail} = Enum.split(p2.genes, cx_point) ​ {c1, c2} = {p1_head ++ p2_tail, p2_head ++ p1_tail} ​ {%Chromosome{​genes:​ c1, ​size:​ length(c1)}, ​ %Chromosome{​genes:​ c2, ​size:​ length(c2)}} ​ ​end​</pre></div><p id="dc44">One issue with this RNG is that it starts from the same state every time your application is run. So it will produce the same order of random numbers every time you run your genetic algorithm. You can find other, better ways to take advantage of NIFs. For example, you could implement entire crossover or mutation functions using NIFs and use your current implementations as fallbacks in case the NIFs don’t load for some reason.</p><p id="3d2b"><i>👈 <a href="https://readmedium.com/improving-performance-with-parallelization-b277a2e133f6">Improving Performance with Parallelization</a> | <a href="https://readmedium.com/table-of-contents-879fc8614df">TOC</a> | <a href="https://readmedium.com/what-you-learned-2a063b5606d3">What You Learned</a> 👉</i></p><p id="3b31"><i>Genetic Algorithms in Elixir by Sean Moriarity can be purchased in other book formats <a href="https://pragprog.com/titles/smgaelixir">directly from the Pragmatic Programmers</a>. If you notice a code error or formatting mistake, please let us know <a href="https://readmedium.com/how-to-report-errata-4e164674347a">here</a> so that we can fix it.</i></p><figure id="2bca"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*U2E2a23SuM4520w9CUXSWw.jpeg"><figcaption></figcaption></figure></article></body>

Improving Performance with NIFs

Genetic Algorithms in Elixir — by Sean Moriarity (85 / 101)

👈 Improving Performance with Parallelization | TOC | What You Learned 👉

Native Implemented Functions (NIFs) are a great way to inject speed into your Elixir/Erlang programs. NIFs are programs implemented in compiled languages like Rust or C/C++ that are then linked and loaded into your Elixir module at runtime.

NIFs were designed to be a simpler and more efficient way of interfacing with native code than ports. Ports interact with external programs and offer a mechanism of implementing program features in a different language.

NIFs are often used in places where Elixir/Erlang alone isn’t enough to efficiently get the job done. For example, the Matrex[20] library uses NIFs to perform fast matrix operations because Elixir/Erlang isn’t optimized to perform these operations alone.

One thing to consider with NIFs is that they have the potential to crash your program. NIFs take the fault-tolerance out of applications because the code loaded is from an external program. Additionally, NIFs have a strange way of interacting with Erlang’s scheduler. The scheduler expects NIFs to return in a certain amount of time; however, if they don’t, it can dramatically impact the performance of your programs.

You can use NIFs to boost performance on your crossover and mutation functions or to augment Elixir implementations of slower functions. For example, random number generation is a notoriously expensive task. You can augment random number generation with a simple and fast C/C++ implementation of a more basic RNG like an XOR-Shift RNG. This repository[21] implements a number of XOR-shift RNGs in C.

To get a better idea of how to implement NIFs, you’ll implement a basic RNG NIF in C. Start by creating a new file genetic.c in a new directory src. In the file, add the following:

​ ​#include <erl_nif.h>
​ ​#include <inttypes.h>
​ ​#include <stdint.h>
​ 
​ ​static​ ​uint32_t​ x = 123456789, y = 362436069, z = 521288629;
​ 
​ ​static​ ERL_NIF_TERM ​xor96​(ErlNifEnv* env, ​int​ argc, ​const​ ERL_NIF_TERM argv[])
​ {
​   ​uint32_t​ t = (x^(x<<10));
​   x = y;
​   y = z;
​   z = (z^(z>>26))^(t^(t>>5));
​ 
​   ​returnenif_make_int(env, z);
​ }
​ 
​ ​static​ ErlNifFunc nif_funcs[] =
​ {
​   {​"xor96"​, 0, xor96}
​ };
​ 
​ ERL_NIF_INIT(Elixir.Genetic, nif_funcs, NULL, NULL, NULL, NULL);

This code defines an XOR96 random number generator and returns it to the Erlang environment using the Erlang NIF C interfaces. Most of the code you see defined here is boilerplate code necessary to create NIFs in C.

Next, you need to define a new Mix compiler in mix.exs, like this:

​ ​defmoduleMix.Tasks.Compile.Geneticdo​
​   ​useMix.Task.Compiler
​ 
​   ​defrun(_args) ​do​
​     {result, _errcode} =
​       System.cmd(
​         ​"​​gcc"​,
​         [​"​​-fpic"​, ​"​​-shared"​, ​"​​-o"​, ​"​​genetic.so"​, ​"​​src/genetic.c"​],
​         ​stderr_to_stdout:true
​       )
​     IO.puts(result)
​   ​end​
​ ​end

This code uses the Mix.Compiler API to create a new Mix compiler that will compile your NIFs to a shared-object library using GCC. If you’re using Windows, you’ll have to either use MingW, Windows Subsystem for Linux (WSL), or integrate this workflow with Visual Studio. MingW is probably the most straightforward option. It’s a port of the C compiler, GCC, to Windows.

Next, you add your compiler to your projects :compilers like this:

​ ​defmoduleGenetic.Mixfiledo​
​   ​useMix.Project
​ 
​   ​defprojectdo​
​     [
​       ​app:​ ​:genetic​,
​       ...
​       ​compilers:​ [​:genetic​] ++ Mix.compilers,
​     ]
​   ​end​
​   ...
​ ​end

Now, whenever you compile your algorithms, Mix will run your custom compiler and compile your NIFs for you. To call xor96 from your code, add the following to the top of genetic.ex:

​ ​defmoduleGeneticdo​
​   @on_load:load_nif​
​ 
​   ​defload_nifdo​
​     ​:erlang​.load_nif(​'./genetic'​, 0)
​   ​end​
​ 
​   ​defxor96, ​do​: ​raise​ ​"​​NIF xor96/0 not implemented."​
​ 
​   ...
​ ​end

This code defines a function to run once the module is loaded. The function load_nif/0 uses Erlang’s load_nif/2 function to load your shared object library at runtime. You also define a fallback implementation of xor96 to run if Elixir can’t find your C implementation.

xor96/0 will generate integers. You can use it to generate integers between 0 and some number, like this:

iex(0)> rem(xor96(), 100)
​ 42

In an example function like single_point_crossover/2, it would look like this:

​ ​def​ single_point_crossover(p1, p2) ​do​
​   cx_point = rem(Genetic.xor96(), p1.size)
​   {p1_head, p1_tail} = Enum.split(p1.genes, cx_point)
​   {p2_head, p2_tail} = Enum.split(p2.genes, cx_point)
​   {c1, c2} = {p1_head ++ p2_tail, p2_head ++ p1_tail}
​   {%Chromosome{​genes:​ c1, ​size:​ length(c1)},
​     %Chromosome{​genes:​ c2, ​size:​ length(c2)}}
​ ​end​

One issue with this RNG is that it starts from the same state every time your application is run. So it will produce the same order of random numbers every time you run your genetic algorithm. You can find other, better ways to take advantage of NIFs. For example, you could implement entire crossover or mutation functions using NIFs and use your current implementations as fallbacks in case the NIFs don’t load for some reason.

👈 Improving Performance with Parallelization | TOC | What You Learned 👉

Genetic Algorithms in Elixir by Sean Moriarity can be purchased in other book formats directly from the Pragmatic Programmers. If you notice a code error or formatting mistake, please let us know here so that we can fix it.

Smgaelixir
Recommended from ReadMedium