Skip to content

Commit 536b00c

Browse files
committed
perf(nif): cache reply and dtype atoms at load
Atoms on the async-reply path (`:ok` on every op, plus `:error`/`:stopped`/`:argument`/`:runtime`/`:unknown`) and the Nx dtype "kind" atoms (`:f`/`:bf`/`:s`/`:u`/`:c`/`:pred`) were built at point of use with enif_make_atom / fine::Atom("..."), redoing the atom-table lookup on each call. Define them once as static fine::Atom objects (emily/atoms.hpp). fine registers namespace-scope atoms and creates their terms in the NIF load callback, and Encoder<Atom> then returns the cached term directly. The reply builders and from_mlx_dtype now reference these cached atoms. Correctness-neutral (verified by the dtype and async tests); removes the per-reply atom-table lookup from the hot path. Closes #135
1 parent c17a2f8 commit 536b00c

4 files changed

Lines changed: 63 additions & 22 deletions

File tree

c_src/emily/async.hpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#pragma once
3131

32+
#include "atoms.hpp"
3233
#include "worker.hpp"
3334

3435
#include <fine.hpp>
@@ -66,16 +67,16 @@ error_reason_from_current_exception(ErlNifEnv *msg_env) {
6667
try {
6768
throw; // re-raise the current exception to classify it
6869
} catch (const std::invalid_argument &e) {
69-
return enif_make_tuple2(msg_env, enif_make_atom(msg_env, "argument"),
70+
return enif_make_tuple2(msg_env, fine::encode(msg_env, emily::atoms::argument),
7071
make_binary_from_cstr(msg_env, e.what()));
7172
} catch (const std::runtime_error &e) {
72-
return enif_make_tuple2(msg_env, enif_make_atom(msg_env, "runtime"),
73+
return enif_make_tuple2(msg_env, fine::encode(msg_env, emily::atoms::runtime),
7374
make_binary_from_cstr(msg_env, e.what()));
7475
} catch (const std::exception &e) {
75-
return enif_make_tuple2(msg_env, enif_make_atom(msg_env, "runtime"),
76+
return enif_make_tuple2(msg_env, fine::encode(msg_env, emily::atoms::runtime),
7677
make_binary_from_cstr(msg_env, e.what()));
7778
} catch (...) {
78-
return enif_make_atom(msg_env, "unknown");
79+
return fine::encode(msg_env, emily::atoms::unknown);
7980
}
8081
}
8182

@@ -118,19 +119,19 @@ fine::Term async_reply(ErlNifEnv *env,
118119
// of hanging on a reply that will never come.
119120
reply = enif_make_tuple2(
120121
msg_env, ref_in_msg,
121-
enif_make_tuple2(msg_env, enif_make_atom(msg_env, "error"),
122-
enif_make_atom(msg_env, "stopped")));
122+
enif_make_tuple2(msg_env, fine::encode(msg_env, emily::atoms::error),
123+
fine::encode(msg_env, emily::atoms::stopped)));
123124
} else {
124125
try {
125126
ERL_NIF_TERM payload = build_payload(s, msg_env);
126127
ERL_NIF_TERM ok_tuple = enif_make_tuple2(
127-
msg_env, enif_make_atom(msg_env, "ok"), payload);
128+
msg_env, fine::encode(msg_env, emily::atoms::ok), payload);
128129
reply = enif_make_tuple2(msg_env, ref_in_msg, ok_tuple);
129130
} catch (...) {
130131
reply = enif_make_tuple2(
131132
msg_env, ref_in_msg,
132133
enif_make_tuple2(
133-
msg_env, enif_make_atom(msg_env, "error"),
134+
msg_env, fine::encode(msg_env, emily::atoms::error),
134135
__async::error_reason_from_current_exception(msg_env)));
135136
}
136137
}

c_src/emily/atoms.hpp

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Cached atom terms.
2+
//
3+
// fine::Atom objects defined at namespace scope register themselves on
4+
// construction and have their terms created once in the NIF load callback
5+
// (fine's load runs fine::__private__::init_atoms — see deps/fine).
6+
// Encoding such an atom (via fine::encode) then returns the prebuilt term
7+
// directly (Encoder<Atom> uses the cached `term`), with no per-call
8+
// atom-table lookup.
9+
//
10+
// Defining the reply and dtype atoms here — rather than building them at
11+
// point of use with enif_make_atom / fine::Atom("...") — keeps every atom
12+
// on the hot async-reply path (notably `:ok`, posted for every successful
13+
// op) off the lookup.
14+
15+
#pragma once
16+
17+
#include <fine.hpp>
18+
19+
namespace emily::atoms {
20+
21+
// Async reply atoms (see emily/async.hpp, emily_nif.cpp).
22+
inline auto ok = fine::Atom("ok");
23+
inline auto error = fine::Atom("error");
24+
inline auto stopped = fine::Atom("stopped");
25+
inline auto argument = fine::Atom("argument");
26+
inline auto runtime = fine::Atom("runtime");
27+
inline auto unknown = fine::Atom("unknown");
28+
29+
// Nx dtype "kind" atoms (see emily/dtype.hpp).
30+
inline auto f = fine::Atom("f");
31+
inline auto bf = fine::Atom("bf");
32+
inline auto s = fine::Atom("s");
33+
inline auto u = fine::Atom("u");
34+
inline auto c = fine::Atom("c");
35+
inline auto pred = fine::Atom("pred");
36+
37+
} // namespace emily::atoms

c_src/emily/dtype.hpp

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
#pragma once
88

9+
#include "atoms.hpp"
10+
911
#include <fine.hpp>
1012
#include <mlx/mlx.h>
1113

@@ -42,19 +44,19 @@ inline mx::Dtype to_mlx_dtype(const std::tuple<fine::Atom, int64_t> &t) {
4244
}
4345

4446
inline std::tuple<fine::Atom, int64_t> from_mlx_dtype(mx::Dtype dtype) {
45-
if (dtype == mx::float32) return {fine::Atom("f"), 32};
46-
if (dtype == mx::float16) return {fine::Atom("f"), 16};
47-
if (dtype == mx::bfloat16) return {fine::Atom("bf"), 16};
48-
if (dtype == mx::int8) return {fine::Atom("s"), 8};
49-
if (dtype == mx::int16) return {fine::Atom("s"), 16};
50-
if (dtype == mx::int32) return {fine::Atom("s"), 32};
51-
if (dtype == mx::int64) return {fine::Atom("s"), 64};
52-
if (dtype == mx::uint8) return {fine::Atom("u"), 8};
53-
if (dtype == mx::uint16) return {fine::Atom("u"), 16};
54-
if (dtype == mx::uint32) return {fine::Atom("u"), 32};
55-
if (dtype == mx::uint64) return {fine::Atom("u"), 64};
56-
if (dtype == mx::complex64) return {fine::Atom("c"), 64};
57-
if (dtype == mx::bool_) return {fine::Atom("pred"), 1};
47+
if (dtype == mx::float32) return {atoms::f, 32};
48+
if (dtype == mx::float16) return {atoms::f, 16};
49+
if (dtype == mx::bfloat16) return {atoms::bf, 16};
50+
if (dtype == mx::int8) return {atoms::s, 8};
51+
if (dtype == mx::int16) return {atoms::s, 16};
52+
if (dtype == mx::int32) return {atoms::s, 32};
53+
if (dtype == mx::int64) return {atoms::s, 64};
54+
if (dtype == mx::uint8) return {atoms::u, 8};
55+
if (dtype == mx::uint16) return {atoms::u, 16};
56+
if (dtype == mx::uint32) return {atoms::u, 32};
57+
if (dtype == mx::uint64) return {atoms::u, 64};
58+
if (dtype == mx::complex64) return {atoms::c, 64};
59+
if (dtype == mx::bool_) return {atoms::pred, 1};
5860
throw std::runtime_error("unmapped mlx dtype");
5961
}
6062

c_src/emily_nif.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// defined here via emily/tensor.hpp.
55

66
#include "emily/async.hpp"
7+
#include "emily/atoms.hpp"
78
#include "emily/tensor.hpp"
89
#include "emily/worker.hpp"
910

@@ -138,7 +139,7 @@ fine::Term eval_nif(ErlNifEnv *env,
138139
env, w,
139140
[tensor](mx::Stream &, ErlNifEnv *msg_env) {
140141
mx::eval(tensor->array);
141-
return enif_make_atom(msg_env, "ok");
142+
return fine::encode(msg_env, emily::atoms::ok);
142143
});
143144
}
144145
FINE_NIF(eval_nif, 0);

0 commit comments

Comments
 (0)